跳到主要内容

保存的文章

单元测试的范围是什么?

Dave Nicolette |领导敏捷
Dave Nicolette 高级顾问
读: 单元测试的范围是什么?

单元测试的范围
“单元测试”是一个有趣的术语。这似乎是各种争论的主题。大多数争论都是无法解决的,因为它涉及个人观点和情感高于一切。人们对这个术语的大多数问题归结为两件事:单词“单元”和单词“测试”。人们可以接受短语中的其他词,只要我们去掉这两个词。

有些人反对这个术语单元测试因为“测试”这个词。可执行单元测试真的是一个功能查看;不一样测试真实意义上的软件。我同意这一点。However, most of the world doesn’t use that terminology, and I’ve learned it’s all but impossible to change entrenched industry lingo, so I’m going to write “unit test” in this piece, even though you and I know all the examples here are really检查.它只是必须这样做。

有些人发现价值在单位测试和其他人.好的,生活和生活。我在“找到价值”组中。我不会在这里辩论它。这是一个“给定”的目的,单位测试是有价值的。事实上,我将在编写生产代码之前宣称在编写生产代码之前编写单元测试更有价值。如果您从始于不同意单元测试很有用,那么您可以节省一些时间;祝你今天过得愉快。

每个人都知道什么是代码的“单元”,所以每个人都知道什么是“单元测试”。问题是关于这个问题有很多不同的观点。我见过覆盖整个可执行文件、协作对象集合、端到端事务、整个批处理作业流、ETL过程中的一系列步骤,以及所有其他范围很大且具有许多紧密耦合依赖关系的东西的单元测试。客观地说,称这些东西为“单元测试”并不是“错误的”,因为没有普遍接受的定义。

一些我认为非常有用的关于单元测试的想法。

迈克尔的羽毛

Michael Feathers提出了一套设计单元测试的指导方针,可以追溯到众所周知的《时间之雾》是这样的:

“如果以下情况,测试不是单位测试:

  • 它与数据库交谈
  • 它通过网络进行通信
  • 它触及文件系统
  • 它不能与任何其他单元测试同时运行
  • 您必须为您的环境(例如编辑配置文件)做特殊的东西来运行它“

这是来自2005年的文章。下一个发表于2018年,但这不是一个全新的想法。

Michael“Geepaw”山

这是GeePawTDD Pro-Tip#5:“不,小”

“In TDD, almost without exception, we want everything: a test, a method, a class, a step, a file, really, almost everything, to be as small as it can be and still add value to what we’re doing.”

如果这听起来像一个相当松散的定义,那就是因为它是一个相当松散的定义。它一定要是。最小的单位仍然会通过编程语言和用于运行测试用例的可用工具来增加所做的值。有时,它也取决于你如何编写解决方案。

java.

让我们以Java为例,它是一种广泛用于业务应用程序编程的语言。亚博vip9通道我在过去用过一个我发现的斐波那契问题的迭代解的例子在StackOverflow.这里有一个稍微修改过的版本:

公共类纤维{public int fib(int n){int x = 0,y = 1,z = 1;for(int i = 0; i 
         

如果我们想为该代码编写一个单元测试,我们可以使用JUnit这样做:

@test public void first_10_values(){list期望= arrays.aslist(新整数[] {0,1,1,2,3,5,8,13,21,34});assertequals(预期,fib.fibseries(10));}

这似乎很好。如果实现出现任何错误,测试将失败。但如果我们采用GeePaw的"不,更小"方针呢?从技术上讲,它看起来是这样的:

[1]“足够小吗?”

[2]“不,把它变小。”

如果这太专业了,我很抱歉。

关于Fibonacci系列的事情是第一几个值是排序的,并且从一定位置,基于前面的值计算值。授予,这有点微不足道,但模式发生在许多更现实的业务应用程序方案中。亚博vip9通道我们希望有单位测试在逻辑的确切部分上零是不正确的。节省了一些分析时间,并帮助我们的插头并播放不同的实现,而无需撕掉我们的测试套件。如果在丢弃新实现之前和之后相同的测试,那么我们知道新的表现与旧的表现相同。在这个疯狂的老世界中毫不担心的少一点。

考虑到这一点,我们可以编写单元测试以验证计算每个数字的方法:

@test public void the_2nd_value_is_1(){assertequals(1,fib.fib(1));@test public void the_5th_value_is_3(){assertequals(3,fib.fib(4));}

如果我们决定抽查几个值,我们可以编写一个数据驱动的测试用例,它相当于一堆这些小测试用例(省略一些样板代码):

@ parameterized.parameters(name =“{index}:{0}之后的下一个值是{1}”)公共静态可迭代<对象[]> data(){return arrays.aslist(新对象[] [] [] [] {{1,1},{4,3},{9,21},{11,55},});}。..public fibonacciparameterizeTestize(int之后,int下一个){this.After =之后;这个.next = next;}。..@test public void iTcomputestHenextValueIntHefibonAcciseries(){assertequals(下一个,fib.fib(之后)); }

这些测试用例都很小。再小一点,就没有意义了。但是如果产品代码以不同的方式实现呢?下面是一个使用lambda表达式的Java实现:

public List generate(int series){返回流。迭代(新int [] {0,1}, s - >新int[]{[1],[0] +年代[1]}).limit(系列). map (n - > n [0]) .collect (Collectors.toList ());}

在这里,我们有一系列生产整个系列的代码。那些小小的单位测试仍然有意义吗?我不这么认为。此代码未重复调用用于计算每个值的方法。所有工作都由迭代,限制,地图和收集方法处理。我们不需要单独测试这些,因为它们由Java开发套件提供;他们不是我们的代码。我们在C中编写相同的逻辑,没有图书馆,这将是一个不同的故事;但在这种情况下,库是我们工具的一部分。(如果您缺乏对您的工具工作的信心,那么您完全有不同的问题。)

在这种情况下,我们看待第一个单元测试将是最小的有意义的。这样的东西,也许:

@Test public void it_generates_the_first_10_values() {List expected = Arrays。asList(new Integer[] {0, 1, 1, 2, 3, 5, 8, 13, 21, 34});FibLambda fib = new FibLambda();assertequal(预期,fib.generate (10));}

我认为这与Gape足够小的标准符合Geepaw的标准。我们可能能够强制解决问题并提出较小的单元测试,但由于编写了生产代码的方式,这样做不会产生任何价值。我们正在测试一行源代码。执行该代码时发生了很多东西,但我们不直接控制任何一个,并且它不会以一种使我们分别测试这些部分的方式分开。所以,我们已经完成了。

cobol.

大多数现代语言都很适合单元测试框架。在旧平台上运行的传统语言就不那么重要了。大型机环境中的“单元”是什么?除非您想赤手空拳地将COBOL代码提交,否则可能是这样像这样,你会使用IBM Rational Developer for zSeries with zUnitCompuware Topaz Workbench.或滚动自己的测试线束。您可能独立运行的最小的代码单位是整个负载模块。(孩子们:“负载模块”就像一个“可执行文件”。)

您不一定需要特殊的工具。您可以设置批次测试运行更多或更少:

除了标准的JCL和实用程序之外,不需要其他任何东西。如果你把整个批处理程序称为“单元”,很多人都不会介意,因为那是他们能够使用的最小的单元。对大多数大型机来说,单独执行单个COBOL段落的想法有点奇怪。我甚至听到过“不可能”这个词,尽管这不是真的。

所以,如果我们可以接受整个负载模块是我们可以孤立测试的最小实用单元的想法,我们很高兴。

除了我们违反了迈克尔羽毛原则之一:我们正在触摸文件系统。我们触摸了这一点真见鬼退出文件系统。

大型机批处理程序通常会针对文件和数据库运行。这就是批量流程所做的,毕竟:他们处理大批数据。数据在哪里生活?在文件和数据库中。

我们可以嘲笑吗?好吧,也许我们可以弄清楚一些方法来嘲笑它,其中一些时间。但是作为一个实际问题,考虑到执行环境的现实,运行一个与完全在我们的控制下的测试文件的测试作业并不那么糟糕。我们不会在控制之外的数据源上引入依赖性,因此测试用例是可重复的并且是一致的。这就是重要的,高于和超越规则和指导方针。

我认为在上下文中称这个“单位测试”称之为公平。

微服务

如果我们沿着时间前进而不是倒退会怎样?我一直在阅读和听到很多人说没有必要对微服务进行单元测试,因为它们已经很小了,我们所需要做的就是运行api级别的检查,这样就完成了。

就像欧内斯特·海明威(Ernest Hemingway)可能会说的那样,“这样想不是很好吗?”哦,等等;他做过比如说.现在我也在说。

取决于编程语言、逻辑如何实现以及微服务到底有多“微”(毕竟这是那些被过度使用的流行术语之一)可以想象没有比整个服务更小的部分需要检查,但更有可能的情况是,API背后有不止一行代码。

使用RESTful API在HTTP上调用代码的事实并不会使有关什么是单元测试、什么不是单元测试以及单元测试多小就足够小的所有常见考虑失效。如果它是Java或c#或Python或Ruby或类似的东西,在那里将有方法,我们可以(并且应该)单独验证。

这是一个反向波兰表示法计算器服务通过斯泰西vetzal,写在JavaScript中,用摩卡写的细粒度单位测试。这是我在Ruby中的实现,分为三个项目,每个项目都是核心功能,服务,UI.,使用Rspec中的单元测试。

这两种解决方案都用米热滴水。所以,它看起来像你能够毕竟,关注羽毛和希尔的微野营服务指南。

嵌入式系统和物联网

由于已经涵盖了基本信息,我将在此处光明详细信息。关于嵌入式系统和内容互联网的设计和构建软件,没有任何神奇或根本不同。

建议分开关注,保持执行核心功能的代码与与外部接口交互的代码分开。

许多代码是有限状态机的性质,当设备处于给定状态时采取动作,并且检测到某个事件。适用于此类别的通常设计考虑。根据状态机的复杂程度,您可以从一系列从A的实施方法中进行选择switch语句到了状态模式

在任何情况下,我们通常都希望将用于处理每个状态的功能,以小方法,功能或子程序分离,该功能很好地为隔离的芯片和单元测试。没有理由以这种方式设计代码。有原因是它的原因通常不是这样设计的,但这些原因不是技术性。

The rules of thumb for writing unit tests for embedded and IoT solutions are the same as those for conventional applications: A unit test doesn’t interact with external dependencies (Feathers) and its scope is as small as we can make it without losing the value the test case provides (Hill).

细节可能存在几个差异,具体取决于上下文。对于测试驾驶嵌入式解决方案,我们通常会在不同的硬件平台上开发而不是目标。我们的快速TDD周期将发生在开发环境中。额外的步骤超出标准的红色绿色重构循环,以编译目标环境的编译器设置可以在TDD周期中提前公开积分问题。

测试驱动或单元测试物联网解决方案通常涉及两种测试:单元测试(与任何其他环境中的单元测试没有区别)和仪器测试(模拟来自设备的输入信号)。检测测试在概念上等同于提供用户界面的应用程序的UI测试或服务的API测试。这是一个Nilesh Jarad写的好文章在测试驱动Android应用程序。

结论

有些人不同意,其中一些人非常聪明,但如果你问我,我们应该在单元/微观层面上编写可执行的检查或测试,遵循Feathers和Hill的指导方针,而不管我们使用的领域或技术堆栈。我和其他成千上万的开发人员的经验是,收益远远大于成本。

下一个;你工作的价值有回声

留下你的评论

您的电子邮件地址不会被公开。必需的地方已做标记