跳到主要内容

保存的文章

单元测试的范围是什么?

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

单元测试的范围
“单元测试”是一个有趣的名词。这似乎是各种辩论的主题。大部分争论的是无法解决的,因为它涉及到个人的意见和情感高于一切。大多数人都有与术语的问题归结为只是两件事情:守信用“单位,”字“的考验。”人们可以接受换言之一语中的,只要我们删除这两个。

有些人反对这个词单元测试因为“测试”这个词。可执行单元测试实际上是功能测试;不是一回事测试真正意义上的软件。我同意这一点。然而,世界上大多数地方都不使用这个术语,而且我知道要改变根深蒂固的行业术语几乎是不可能的,所以我将在本文中编写“单元测试”,尽管您和我知道这里的所有示例都是真的检查。它只能这样。

有些人发现价值在单元测试及其他。好吧,活也让别人活。我是“的发现价值”组中。我不打算在这里讨论它。这是一个“给定”这一职位,单元测试是有价值的目的。事实上,我会去竟然声称这是更有价值的编写产品代码之前编写单元测试了。如果您不同意根本单元测试是有用的,你可以节省自己一些时间;祝你今天愉快。

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

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

迈克尔的羽毛

Michael Feathers提出了一套设计单元测试的指导原则,早在那个众所周知的年代是这样的:

测试不是单元测试,如果:

  • 它谈论到数据库
  • 它通过网络进行通信
  • 倒是文件系统
  • 它不能在同一时间运行为您的任何其他单元测试
  • 你要做特别的东西到你的环境(如编辑配置文件)来运行它”

这是从2005年的文章。这下一个出版于2018年,但它不是一个全新的概念。

迈克尔“GeePaw”希尔

这是GeePawTDD人提示#5:“没有,更小的”

“在TDD中,几乎毫无例外,我们希望所有的东西:测试、方法、类、步骤、文件,真的,几乎所有的东西都尽可能的小,同时还能增加我们正在做的事情的价值。”

如果这听起来是一个相当松散的定义,那只是因为它是一个相当松散的定义。它必须是。仍然为我们所做的事情增加价值的最小单元因编程语言和运行测试用例可用的工具而有所不同。有时候,它也取决于你怎么写解。

Java

让我们考虑Java,一种广泛用于业务应用程序编程的语言。亚博vip9通道我在过去使用了我发现的迭代解决Fibonacci问题的一个例子在StackOverflow。这里有一个稍微修改的版本:

公共类FibIterative {公众诠释FIB(INT N){INT X = 0,Y = 1,Z = 1;对于(中间体I = 0;我
         

如果我们想写代码单元测试,我们可以做这样的使用JUnit:

@测试公共无效first_10_values(){列表预期= Arrays.asList(新的整数[] {0,1,1,2,3,5,8,13,21,34});的assertEquals(预期,fib.fibSeries(10));}

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

[1]“够小吗?”

[2]“不,使其变小。”

抱歉,如果这太专业了。

关于斐波纳契数列的事情是,第一对值都排序的预定的,和从某一点以后的值基于前面的值进行计算。当然,这是一个有点微不足道,但在许多更现实的商业应用场景发生模式。亚博vip9通道我们希望有单元测试在这不正确的逻辑的确切部分为零。这为我们节省了一些分析时间,也有助于我们插上,也不必撕裂了我们的测试套件发挥不同的实现。如果相同的测试之前,并在新的实施下探后传球,那么我们就知道新的行为一样老。少了一个东西不用担心在这个疯狂的旧世界。

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

@测试公共无效the_2nd_value_is_1(){的assertEquals(1,fib.fib(1));} @测试公共无效the_5th_value_is_3(){的assertEquals(3,fib.fib(4));}

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

@Parameterized。public static Iterable data(){返回数组。asList(新对象[][]{{1 1},{4 3},{9日21},{11日55},});} . .public FibonacciParameterizedTest(int after, int next) {this。后=后;这一点。下一个=下一个;} . .assertEquals(next, fib.fib(after));}

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

公共列表生成(INT系列){返回Stream.iterate(新INT [] {0,1},S  - >新INT [] {S [1],S [0] + S [1]}).limit(系列).MAP(N  - > N [0]).collect(Collectors.toList());}

这里只有一行代码可以生成整个系列。那些小小的单元测试还有意义吗?我不这么想。这段代码不会重复调用方法来计算每个值。所有这些工作都由iterate、limit、map和collect方法处理。我们不需要单独测试它们,因为它们是由Java开发工具包提供的;它们不是我们的准则。如果我们在C语言中编写相同的逻辑,而没有库,那将是一个不同的故事;但在本例中,库是我们工具的一部分。(如果你对自己的工具是否有效缺乏信心,那么你的问题就完全不同了。)

我们看着那个第一单元测试将是在这种情况下,最小的意义之一。事情是这样的,也许:

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

我认为这符合GeePaw的标准,足够小。我们可以强制解决这个问题,并在这里使用更小的单元测试,但是由于产品代码的编写方式,这样做不会产生任何价值。我们只测试了一行源代码。当代码执行时,会发生很多事情,但我们不直接控制这些事情,它也不会以一种让我们能够分别测试各个部分的方式分离出来。所以,我们就完成了。

COBOL

最现代的语言借给自己很好的单元测试框架。传统的语言在旧平台上运行...没有这么多。什么是“单位”在大型机环境?除非你想击败COBOL代码就范用裸手,也许像这样,您将使用IBM Rational Developer for zSeries with zUnit,Compuware公司黄玉工作台或滚动你自己的测试工具。你可能能够独立运行的代码的最小单位是一个整体加载模块。(儿童:A“加载模块”是像一个“可执行”。)

您不一定需要特殊的工具。您可以设置一个批处理测试运行或多或少如下:

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

所以,如果我们可以接受的想法,整个负载模块的代码,我们可以在隔离测试的最小实用单位,我们是好去。

除了我们违反了Michael Feathers的原则之一:我们正在接触文件系统。我们接触真见鬼文件系统之外。

大型机批处理程序通常针对文件和数据库运行。毕竟,这就是批处理的作用:它们处理大量的数据。数据存在于何处?在文件和数据库中。

我们可以把它模拟一下吗?好吧,也许我们可以想出一个方法来模拟它,有时候。但是作为一个实际的问题,考虑到执行环境的现实情况,运行带有完全在我们控制之下的测试文件的测试作业并不是那么糟糕。我们没有引入我们控制之外的对数据源的依赖,因此测试用例是可重复的和一致的。这才是最重要的,超越了规则和指导方针。

我认为在上下文中称之为“单元测试”是公平的。

Microservices

如果我们在时间上向前旅行而不是向后旅行会怎样?我听到很多人说,没有必要对微服务进行单元测试,因为它们已经非常小,我们只需要运行api级别的检查就可以了。

正如欧内斯特·海明威(Ernest Hemingway)可能说过的那样:“这样想不是很好吗?”“哦,等;他做了说,。现在我说这一点。

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

该代码使用一个RESTful API不会全部失效的常见考虑什么是或通过HTTP调用的其实不是一个单元测试,以及如何小型足够小。如果是Java或C#或Python或Ruby或类似的东西,都会有个别存在,我们可以(也应该)验证方法。

这是一个逆波兰式计算器服务作者Stacey Vetzal,用JavaScript编写,用Mocha编写细粒度的单元测试。这是我用Ruby实现的,分为三个项目,一个是核心功能,服务UI与在Rspec的单元测试。

这两种解决方案都带有微测试。所以,它看起来像你能够遵循羽毛和希尔对微服务准则,毕竟。

嵌入式系统和物联网

这里我将忽略细节,因为基本信息已经介绍过了。为嵌入式系统和物联网设计和构建软件并没有什么神奇的或根本的不同。

明智的做法是将关注点分离,将执行核心功能的代码与与外部接口交互的代码分离开来。

很多代码本质上都是有限状态机,当设备处于给定状态并检测到某个事件时,就会执行某个操作。这类问题通常的设计考虑适用。根据状态机的复杂程度,您可以从a中的一系列实现方法中选择switch语句状态模式

在任何情况下,我们将通常喜欢在小型方法,函数,子程序或借自己很好地分离的microtesting和单元测试处理每一状态隔离的功能。我们没有理由不来设计这样的代码。是有原因的,为什么它是通常不是这样设计的,但是这些原因都不是技术。

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的指导原则在单元/微观层面上编写可执行的检查或测试,而不管我们使用的是什么领域或技术堆栈。我和成千上万的其他开发人员的经验是,收益远远大于成本。

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

Dave Nicolette自1977年以来一直是IT专业人员。他曾担任过各种技术和管理职务。自1984年以来,他主要从事咨询工作,一只脚踏在技术阵营,一只脚踏在管理阵营。

留下你的评论

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