tdd 单元测试
最近,我一直在写与自动测试有关的更高级的概念(主要与Spock有关)。 但是,在进行测试培训时,我清楚地看到,通常对特定工具的知识并不是主要问题。 即使使用Spock,也可以编写肿且难以维护的测试,从而破坏(或不了解)与编写单元测试有关的良好实践。 因此,我决定写一些更基本的东西来促进它们,并且在指导经验不足的同事时准备使用一些参考材料。
介绍
编写良好的单元测试应满足几个要求,这是整个系列的主题。 在这篇博客文章中,我想提出一个相当成熟的概念,即将单元测试划分为具有严格定义的功能的3个单独的块(依次是行为驱动开发的子集)。
单元测试通常集中于测试给定单元(通常是一个给定类)的某些特定行为。 与通过UI执行的验收测试相反,在每个测试中都将存根/模拟作为其协作者,从零开始设置一个要测试的类(测试中的类)比较便宜(快速)。 因此,性能应该不是问题。
样品测试
为了演示规则,我将使用一个小示例。 ShipDictionary
是一个类,提供根据特定条件(按名称的一部分,生产年份等)搜索太空船的功能。 该词典由不同的船舶索引(在役,退役,在生产等中的船舶)提供动力。 在那个特定的测试中,它被测试了按其名称的一部分搜索飞船的能力。
private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)";@Test
public void shouldFindOwnShipByName() {
//given
ShipDatabase shipDatabase = new ShipDatabase(ownShipIndex, enemyShipIndex);
given(ownShipIndex.findByName("Enterprise")).willReturn(singletonList(ENTERPRISE_D));
//when
List foundShips = shipDatabase.findByName("Enterprise");
//then
assertThat(foundShips).contains(ENTERPRISE_D);
}
给定时间
测试驱动开发方法和行为驱动开发方法中都存在的良好习惯是“先验”知识,它将在特定测试用例中进行测试(认定)。 可以以更正式的方式(例如,用Cucumber/小Cucumber编写的用于验收测试的方案)或以自由形式(例如,特别注意的要点或只是下一步应该做什么的想法)来完成。 有了这些知识,就很容易确定整个测试将组成的三个关键部分(分开的部分)。
给定–准备
在单元测试的第一部分(称为given
)中,需要创建一个实际对象实例,在该对象实例上将执行测试的操作。 在有重点的单元测试中,仅放置一类要测试的逻辑。 另外,执行测试所需的其他对象(称为协作者)应初始化为存根/模拟,并适当存根(如果需要)。 还必须将所有协作者注入到要测试的对象中,该对象通常与该对象创建结合在一起(因为构造函数注入应该是依赖注入的首选技术)。
//given
ShipDatabase shipDatabase = new ShipDatabase(ownShipIndex, enemyShipIndex);
given(ownShipIndex.findByName("Enterprise")).willReturn(singletonList(ENTERPRISE_D));
何时–执行
在when
部分中,将执行要测试的操作。 在我们的情况下,这是一个搜索请求,然后将结果存储在变量中以供进一步声明。
//when
List foundShips = shipDatabase.findByName("Enterprise");
在大多数情况下,在该部分中仅执行一项操作是一件好事。 更多的元素可能表明尝试测试多个操作(可能)可以分为多个测试。
然后–断言
-最后一节的责任, then
-主要是先前接收到的结果的断言。 它应该等于期望值。
//then
assertThat(foundShips).contains(ENTERPRISE_D);
此外,可能有必要对声明的模拟执行方法执行的验证。 这不应该是一种常见的做法,因为在大多数情况下,对接收值的声明足以确认所测试的代码能够按预期工作(根据设置的边界)。 但是,特别是对于测试void方法,需要验证是否已使用预期参数执行了特定方法。
AAA aka 3A –一种替代语法
正如我已经提到的,BDD是一个更广泛的概念,它对于编写具有预先定义的需求(通常是非技术形式)的功能/验收测试特别方便。 一种替代的测试划分语法(对于各节而言,含义非常相似)是“ 配置行为声明”,通常缩写为AAA或3A。 如果您根本不使用BDD,并且三个字母比GWT更容易记住,那么使用它来创建相同的高质量单元测试就很好。
调整与优化
将实用工具和方法学与持续进行的技能获取过程(也称为Dreyfus模型 )进行匹配的过程已在《 实用思维与学习:重构您的湿软件 》一书中进行了很好的描述。 当然,在许多情况下,使用given
节移至setup/init/before
节或内联初始化的测试的简化变体可能很方便。 同样可以适用于when
和then
部分,其可以被合并在一起(成expect
部分,特别是在参数化测试)。 具有编写单元测试的经验和流利性,使用速记和优化(尤其是测试一些非平凡的案例)是完全有效的。 只要整个团队都了解约定,并且能够记住有关编写好的单元测试的基本假设。
摘要
根据我在软件开发方面的经验以及作为一名培训师,我清楚地看到,将(单元)测试划分为多个部分可以使它们更短,更易理解,尤其是团队中经验不足的人员。 与明确找出并立即将所有内容写入测试中相比,用明确定义的责任来填充3个部分更容易。 最后,特别是对于仅阅读本文第一部分和最后部分的人们,此处遵循以下简明规则:
-
given
–测试中的对象初始化+存根/模拟的创建,存根和注入 -
when
–在给定测试中进行测试的操作 -
then
–收到结果声明+模拟验证(如果需要)
PS最好在IDE中设置一个测试模板,以保护编写每个测试所需的许多击键。
PSS,您发现本文很有用,您可以让我知道,以鼓励我将来写更多有关单元测试的基础知识。
图片来源:Tomas Sobek,Openclipart, https ://openclipart.org/detail/242959/old-scroll
自我提升 。 您想快速有效地提高您和您的团队的测试技能以及对Spock / JUnit / Mockito / AssertJ的了解吗? 我进行了浓缩(单元) 测试培训 ,您可能会觉得有用。
翻译自: https://www.javacodegeeks.com/2017/05/importance-given-unit-tests-tdd.html
tdd 单元测试