单位测试
在计算机编程中,单元测试是一种软件测试方法。这是开发和实施方法(例如敏捷)的标准步骤。
历史
在单元测试之前,捕获和重播测试工具是常态。 1997年,肯特·贝克(Kent Beck)和埃里希·加玛(Erich Gamma)开发并发布了Junit ,这是一个单位测试框架,在Java开发人员中很受欢迎。 Google在2005 - 2006年左右接受了自动测试。
单元
单位将是可以在应用程序的复杂结构中隔离的最小组件。它可能是一个函数,子例程,方法或属性。
单位测试是由软件开发人员编写和运行的自动测试或手动测试,以确保应用程序(称为“单位”)的一部分符合其设计并按预期行为。他们通常由最初编写代码的开发人员执行,作为第一道防线,然后进行进一步的测试。
程序编程
在程序编程中,一个单元可能是整个模块,但更常见的是单个功能或过程。
面向对象的编程
在面向对象的编程中,一个单元通常是整个接口,例如类或单个方法。通过首先为最小的可测试单元编写测试,然后是这些单元之间的复合行为,可以建立对复杂应用程序的全面测试。
测试标准
在开发过程中,软件开发人员可能会在测试中编码已知的标准或已知的结果,以验证单元的正确性。在测试案例执行过程中,框架日志测试未能使任何标准失败并在摘要中报告。为此,最常用的方法是测试 - 函数 - 期望值。
测试用例
为了隔离可能出现的问题,应独立测试每个测试案例。诸如方法存根,模拟对象,假货和测试线束之类的替代物可用于隔离模块。
参数化测试
参数化测试是一种声称缩短编写和维护单元测试过程的技术。参数化测试允许多次执行一个具有不同输入集的测试,从而减少了测试代码的重复。与通常是封闭方法和测试不变条件的传统单元测试不同,参数化测试采用任何集合。参数化测试由TestNG , Junit及其.NET对应物, Xunit和Nunit以及各种JavaScript测试框架支持。
可以手动提供用於单位测试的合适参数,或者在某些情况下是由测试框架自动生成的。近年来,添加了支持更强大(单位)测试的支持,利用理论的概念,执行相同步骤但使用运行时生成的测试数据的测试用例,与常规参数化测试不同,该测试使用使用相同的执行步骤,该测试使用输入集相同的执行步骤预定义。
敏捷
在敏捷开发过程中,每个用户的故事进行了单元测试,并且在要求收集和开发完成后,在Sprint的后半段进行。通常,开发人员或开发团队的其他成员(例如顾问)将逐步编写“测试脚本”,以便开发人员在工具中执行。通常编写测试脚本以证明该工具中特定开发功能的有效和技术操作,而不是由最终用户连接的全脚业务流程,这通常是在用户接受测试中进行的。如果可以从头到尾完全执行测试标记而没有事件,则将单位测试视为“通过”,否则会注意到错误,并且用户故事将转移到“过程中”状态下的开发。成功通过单元测试的用户故事将转移到Sprint的最后一步 - 代码审核,同行评审,然后进行了“表演”会话,将开发的工具展示到利益相关者。
优点
单元测试的目的是隔离程序的每个部分,并表明各个部分是正确的。单位测试提供了严格的书面合同,该法规必须满足。结果,它带来了一些好处。
开发周期中问题的早期发现
单元测试在开发周期的早期发现问题。这包括程序员的实现中的错误和缺陷或单元规范的缺少部分。编写一系列测试的过程迫使作者通过输入,输出和错误条件进行思考,因此更清晰地定义了单位所需的行为。
成本降低
在编码开始之前或第一次编写代码之前查找错误的成本大大低于以后检测,识别和纠正错误的成本。已发布的代码中的错误也可能会给软件的最终用户带来昂贵的问题。如果编写不佳,代码可能是不可能或难以单位测试的,因此单位测试可以迫使开发人员以更好的方式构建功能和对象。
测试驱动的开发
在测试驱动的开发(TDD)中,经常在极端编程和Scrum中使用,在编写代码本身之前,请创建单元测试。测试通过时,该代码被视为完整。随着代码的更改或通过构建的自动化过程而开发较大的代码库,则经常对该功能进行相同的单元测试。如果单元测试失败,则认为它是更改代码或测试本身中的错误。然后进行单位测试允许故障或未能轻松追踪的位置。由于该单元测试在将代码移交给测试人员或客户之前提醒开发团队问题,因此在开发过程的早期就会发现潜在的问题。
更频繁的版本
单元测试可以在软件开发中更频繁地发布。通过隔离测试各个组件,开发人员可以快速识别和解决问题,从而导致更快的迭代和释放周期。
允许代码重构
单元测试使程序员可以在以后重组代码或升级系统库,并确保该模块仍然可以正常工作(例如,在回归测试中)。该过程是为所有功能和方法编写测试用例,以便每当变更导致故障时,都可以快速识别。
检测可能打破设计合同的变化
单位测试检测可能会破坏设计合同的变化。
减少不确定性
单位测试可能会减少单位本身的不确定性,并且可以以自下而上的测试方式使用。首先测试程序的部分,然后测试其零件的总和,集成测试变得更加容易。
系统行为的文档
单元测试提供了系统的一种生活文档。希望了解单元提供的功能以及如何使用该功能的开发人员可以查看单元测试以获得对单元接口( API )的基本了解。
单位测试案例体现了对单位成功至关重要的特征。这些特征可以表明单位的适当使用以及被单位捕获的负面行为。尽管许多软件开发环境并不仅仅依靠代码来记录开发产品,但单位测试用例本身就记录了这些关键特征。
当使用测试驱动的方法开发软件时,编写单元测试以指定接口以及测试通过后执行的重构活动的组合可能代替正式设计。每个单元测试都可以看作是指定类,方法和可观察行为的设计元素。
局限性和缺点
测试不会捕获程序中的所有错误,因为它无法评估除最琐碎的程序以外的任何执行路径。这个问题是停止问题的超集,这是不确定的。单位测试也是如此。此外,按定义进行单位测试仅测试单元本身的功能。因此,它不会捕获集成错误或更广泛的系统级误差(例如跨多个单元执行的功能,或性能等非功能性测试区域)。单位测试应与其他软件测试活动一起进行,因为它们只能显示出特定错误的存在或不存在;他们无法证明完全没有错误。为了确保每个执行路径和每个可能输入的正确行为,并确保缺乏错误,需要其他技术,即应用正式方法来证明软件组件没有意外行为。
单位测试的详细层次结构并不等于集成测试。与外围单元的集成应包括在集成测试中,而不是单位测试中。集成测试通常仍然在很大程度上依赖于人类手动测试。高级或全球检查测试可能很难自动化,因此手动测试通常看起来更快,更便宜。
软件测试是一个组合问题。例如,每个布尔决策声明都需要至少两个测试:一项具有“ True”的结果,另一个结果为“ false”。结果,对于编写的每一行,程序员通常需要3到5行测试代码。这显然需要时间,而且投资可能不值得。有一些问题根本不容易测试 - 例如那些非确定性或涉及多个线程的问题。此外,单位测试的代码与正在测试的代码一样有可能是故障的。弗雷德·布鲁克斯(Fred Brooks)在神话般的人月引用中:“永远不要和两个天文组织一起出海;拿一三个。”意思是,如果两个天文典礼矛盾,那么您怎么知道哪一个是正确的?
难以设置现实和有用的测试
与编写单元测试有关的另一个挑战是难以设置现实和有用的测试。有必要创建相关的初始条件,因此被测试的应用程序的一部分表现得像完整系统的一部分一样。如果这些初始条件未正确设置,则测试将不会在现实的上下文中行使代码,这会降低单位测试结果的价值和准确性。
在整个发展过程中需要纪律
为了从单位测试中获得预期的好处,在整个软件开发过程中都需要严格的纪律。
需要版本控制
不仅要保留已执行的测试的仔细记录,而且还要记录有关软件中该单元或任何其他单元的源代码所做的所有更改。使用版本控制系统至关重要。如果该单元的后期版本未能通过先前通过的特定测试,则版本控制软件可以提供自那时以来已应用于设备的源代码更改(如果有)的列表。
需要定期评论
实施可持续的过程,以确保定期审查测试案例失败并立即解决。如果未实施此类过程并根深蒂固地进入团队的工作流程,则该应用程序将与单位测试套件不同步,增加了误报并降低了测试套件的有效性。
嵌入式系统软件的限制
单元测试嵌入式系统软件提出了一个独特的挑战:因为该软件是在与最终将运行的平台不同的平台上开发的,因此您无法在实际部署环境中轻松运行测试程序,桌面程序可以使用桌面程序。
测试与外部系统集成的限制
当方法具有输入参数和一些输出时,单位测试往往最容易。当该方法的主要功能是与应用程序外部的事物交互时,创建单元测试并不容易。例如,一种将与数据库一起使用的方法可能需要创建数据库交互的模拟,这可能不会像真实的数据库交互那样全面。
例子
爪哇
这是Java中的一组测试用例,该测试用例指定了实施的许多元素。首先,必须有一个称为adder的界面,以及一个带有零题的构造函数的实现类,称为adderimpl。它继续断言,加法器接口应具有一种称为add的方法,带有两个整数参数,该参数返回另一个整数。它还为多种测试方法的少量值指定了此方法的行为。
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class TestAdder {
// can it add the positive numbers 1 and 1?
@Test
public void testSumPositiveNumbersOneAndOne() {
Adder adder = new AdderImpl();
assertEquals(2, adder.add(1, 1));
}
// can it add the positive numbers 1 and 2?
@Test
public void testSumPositiveNumbersOneAndTwo() {
Adder adder = new AdderImpl();
assertEquals(3, adder.add(1, 2));
}
// can it add the positive numbers 2 and 2?
@Test
public void testSumPositiveNumbersTwoAndTwo() {
Adder adder = new AdderImpl();
assertEquals(4, adder.add(2, 2));
}
// is zero neutral?
@Test
public void testSumZeroNeutral() {
Adder adder = new AdderImpl();
assertEquals(0, adder.add(0, 0));
}
// can it add the negative numbers -1 and -2?
@Test
public void testSumNegativeNumbers() {
Adder adder = new AdderImpl();
assertEquals(-3, adder.add(-1, -2));
}
// can it add a positive and a negative?
@Test
public void testSumPositiveAndNegative() {
Adder adder = new AdderImpl();
assertEquals(0, adder.add(-1, 1));
}
// how about larger numbers?
@Test
public void testSumLargeNumbers() {
Adder adder = new AdderImpl();
assertEquals(2222, adder.add(1234, 988));
}
}
在这种情况下,本单元测试首先写成,充当设计文档,指定所需解决方案的形式和行为,而不是为程序员留下的实现详细信息。遵循“做可能有效的最简单的事情”练习,下面显示了最简单的解决方案。
interface Adder {
int add(int a, int b);
}
class AdderImpl implements Adder {
public int add(int a, int b) {
return a + b;
}
}
作为可执行规格
使用单位测试作为设计规范比其他设计方法具有一个重要的优势:设计文档(单位测试本身)本身可用于验证实现。除非开发人员根据设计实施解决方案,否则测试将永远不会通过。
单元测试缺乏图形规范(例如UML图)的一些可访问性,但可以使用自动化工具从单元测试中生成它们。大多数现代语言都有免费的工具(通常可作为IDE的扩展)。免费工具,例如基于Xunit框架的工具,将人类消费视图的图形渲染外包给另一个系统。
申请
极端编程
单元测试是极端编程的基石,它依赖于自动化的单元测试框架。该自动化单元测试框架可以是在开发组中创建的第三方,例如, XUNIT或创建。
Extreme编程使用单元测试的创建进行测试驱动的开发。开发人员编写了一个单位测试,该测试揭示了软件要求或缺陷。该测试将失败,因为该要求尚未实现,或者因为它故意暴露了现有代码中的缺陷。然后,开发人员编写了最简单的代码,以使测试以及其他测试通过。
系统中的大多数代码都经过单元测试,但不一定是通过代码的所有路径。 Extreme编程要求“测试所有可能破坏”策略的“测试每个执行路径”方法。这导致开发人员比经典方法开发的测试更少,但这并不是一个问题,更重述事实,因为经典方法很少有条不紊地有条不紊地进行了足够的遵循,以至于所有执行路径都无法进行彻底测试。极端的编程简单地意识到测试很少详尽(因为它通常太昂贵且耗时,无法在经济上可行),并提供有关如何有效关注有限资源的指导。
至关重要的是,测试代码被认为是一流的项目伪像,因为它的质量与实施代码相同,并删除了所有重复。开发人员将单元测试代码与IT测试的代码结合使用。 Extreme编程的彻底单元测试允许上述好处,例如更简单,更自信的代码开发和重构,简化的代码集成,准确的文档和更模块化的设计。这些单元测试也经常作为回归测试形式运行。
单位测试对于紧急设计的概念也至关重要。由于紧急设计在很大程度上取决于重构,因此单位测试是一个不可或缺的组件。
单元测试框架
单位测试框架通常是第三方产品,这些产品不作为编译器套件的一部分分布。它们有助于简化用于各种语言的单元测试过程。
通常可以通过编写遵守测试的单位并使用断言,异常处理或其他控制流动机制来发出信号失败的客户代码来执行特定框架的情况下执行单元测试。没有框架的单位测试很有价值,因为采用单位测试的进入障碍。进行单位测试很少要比根本没有的测试要好,而一旦建立框架,添加单元测试就变得相对容易。在某些框架中,许多高级单元测试功能缺失或必须手工编码。
语言级单元测试支持
一些编程语言直接支持单元测试。他们的语法允许直接声明单位测试,而无需导入库(无论是第三方还是标准)。此外,单位测试的布尔条件可以与非单元测试代码中使用的布尔表达式相同的语法表达if
和while
语句。
具有内置单元测试支持的语言包括:
具有标准单元测试框架支持的语言包括:
某些语言没有内置的单元测试支持,但已经建立了单元测试库或框架。这些语言包括: