模式
有趣的问题。首先 - 我在IDE中配置的终极测试模式:
@Test
public void shouldDoSomethingWhenSomeEventOccurs() throws Exception
{
}
我总是以这段代码为起点(聪明人称之为BDD)。
在given
中,我放置了每个测试唯一的测试设置。
when
理想情况下只有一行——你正在测试的东西。
then
应该包含断言。
我不是一个单一断言的支持者,但是你应该只测试行为的单个方面。例如,如果方法应该返回某些内容并且还有一些副作用,请使用相同的given
和when
部分创建两个测试。
此外,测试模式包括throws Exception
。这是为了处理Java中令人讨厌的受检异常。如果您测试会引发异常的代码,则不会被编译器干扰。当然,如果测试引发异常,则失败。
设置
测试设置非常重要。一方面,提取公共代码并将其放置在setup()
/@Before
方法中是合理的。然而,请注意,当阅读测试(可读性是单元测试中最重要的价值!)时,很容易忽略在测试用例开头某个地方悬挂的设置代码。因此,相关的测试设置(例如,您可以以不同的方式创建小部件)应该放在测试方法中,但是基础结构(设置常见的模拟对象、启动嵌入式测试数据库等)应该被提取。再次提高可读性。
另外,您知道JUnit会为每个测试案例类创建新的实例吗?因此,即使您在构造函数中创建了CUT(待测类),构造函数也会在每次测试之前调用。有点烦人。
粒度
首先给测试命名,并思考您要测试的用例或功能,永远不要从以下角度考虑:
这是一个Foo
类,具有bar()
和buzz()
方法,因此我创建了FooTest
,其中包括testBar()
和testBuzz()
。天哪,我需要测试bar()
中的两个执行路径——所以我们创建testBar1()
和testBar2()
。
shouldTurnOffEngineWhenOutOfFuel()
是好的,testEngine17()
是不好的。
命名
testGetBuzzWhenFooIsNullAndFizzIsNonNegative
这个名称告诉我们有关测试的什么?我知道它测试了什么,但为什么?而且你不认为细节过于亲密吗?如何使用:
@Test shouldReturnDisabledBuzzWhenFooNotProvidedAndFizzNotNegative`
它既以有意义的方式描述输入,也描述您的意图(假设禁用buzz是某种buzz
状态/类型)。此外,请注意,我们不再硬编码getBuzz()
方法名称和Foo
的null
契约(而是说:当Foo
未提供时)。如果将来用null对象模式替换null
怎么办?
还不要害怕20个不同的getBuzz()
测试方法。相反,考虑测试的20种不同用例。但是,如果测试用例类变得太大了(因为它通常比被测试的类大得多),请将其拆分成几个测试用例。再次强调:FooHappyPathTest
、FooBogusInput
和FooCornerCases
很好,Foo1Test
和Foo2Test
则不好。
可读性
力争使用简短且具有描述性的名称。在given
和then
中少写几行代码即可。创建构建器和内部DSL,提取方法,编写自定义匹配器和断言。测试应比生产代码更易读。不要过度mock。
我发现先编写一系列空的命名良好的测试用例方法很有用。然后我回到第一个测试用例。如果我仍然明白我应该在什么条件下进行测试,那么我同时构建类API实现测试。然后我实现这个API。聪明人称其为TDD(见下文)。
推荐阅读: