每个函数/方法应编写多少个单元测试?

31

你是以每个函数/方法一个测试,还是在一个测试中包含多个检查,或者是为每个检查编写一个测试?


我认为一个函数至少应该有几个测试。如果仅有一个测试,期望特定输出,则output YourFunction(input) { return DEFAULT_OUTPUT; }将通过所有测试,但您不希望如此。 - mercury0114
12个回答

44

每次检查一个测试,并使用超级描述性的名称:

@Test
public void userCannotVoteDownWhenScoreIsLessThanOneHundred() {
 ...
}

单个断言和良好的命名,在测试失败时给我的报告更佳。它们对我喊道:“你违反了那个规则!”。


这基本上就是BDD的要点 - 尽管BDD将重点放在编写那些作为描述性断言的规则上。 - Michael Neale
1
在Java中,我实际上打破了camelCase约定,以提高可读性,并使用大写字母突出重要部分,例如主题/对象。例如:userCannotVoteDownWhenScoreIsLessThanOneHundred -> User_cannot_vote_down_when_Score_is_less_than_one_hundred - Theodor
@Theodor 我也是这样做的。由于测试方法名称通常较长,我认为使用下划线是更好的选择。 - marcospereira

7

我有一个测试针对函数提供的每个能力。然而,每个测试可能有几个断言。测试用例的名称指示正在测试的功能。

一般来说,对于一个函数,我有几个“晴天”测试和一个或几个“雨天”场景,这取决于它的复杂性。


5

BDD (行为驱动开发)

虽然我还在学习中,但它基本上是围绕软件实际使用的方式组织/聚焦于TDD...而不是聚焦于如何开发/构建软件。

维基百科 常规信息

顺便说一句,关于是否在每个测试方法中进行多个断言,我建议尝试两种方式。有时您会发现其中一种策略让您束手无策,这时您就会理解为什么您通常只在每个方法中使用一个断言。


3

我认为单一断言规则有点过于严格。在我的单元测试中,我尝试遵循单一断言组的规则 -- 你可以在一个测试方法中使用多个断言,只要你按顺序进行检查(在断言之间不改变被测试类的状态)。

因此,在Python中,我相信像这样的测试是正确的:

def testGetCountReturnsCountAndEnd(self):
    count, endReached = self.handler.getCount()
    self.assertEqual(count, 0)
    self.assertTrue(endReached)

但这个应该被分成两个测试方法:

def testGetCountReturnsOneAfterPut(self):
    self.assertEqual(self.handler.getCount(), 0)
    self.handler.put('foo')
    self.assertEqual(self.handler.getCount(), 1)

当然,在长期和频繁使用的断言组中,我喜欢创建自定义断言方法——这些方法对于比较复杂的对象特别有用。


2

每个检查都需要一个测试用例。这样更加细致。它使得很容易看到哪个具体的测试用例失败了。


2
我至少为每个方法编写一个测试,如果方法需要一些不同的setUp来测试好的情况和坏的情况,则有时会编写更多测试。
但是在一个单元测试中永远不要测试超过一个方法。这可以减少在API更改时修复测试时的工作量和错误。

1
我建议为每个检查提供一个测试用例。 保持原子性,结果会更好!
将多个检查放在单个测试中将有助于生成报告,以了解需要纠正多少功能。
保持原子测试用例将展示您的整体质量!

1
一般来说,每个检查只有一个测试用例。当测试围绕特定的函数分组时,如果要重构(例如删除或拆分)该函数,就会更加困难,因为测试也需要进行大量更改。最好为您想从类中获得的每种行为编写测试。有时,在测试特定行为时,每个测试用例有多个检查是有意义的。但是,随着测试变得越来越复杂,当类的某些内容发生更改时,它们变得更难以更改。

1
我喜欢在每个方法的检查中都有一个测试,并为测试方法取一个有意义的名称。例如:

testAddUser_shouldThrowIllegalArgumentExceptionWhenUserIsNull


1
在Java/Eclipse/JUnit中,我使用两个具有相同树的源目录(src和test)。 如果我有一个值得测试的方法(例如deleteAll(List<?> stuff) throws MyException)的src/com/mycompany/whatever/TestMePlease,我会创建一个test/com/mycompany/whatever/TestMePleaseTest,用于测试不同的用例/场景:
@Test
public void deleteAllWithNullInput() { ... }

@Test(expect="MyException.class") // not sure about actual syntax here :-P
public void deleteAllWithEmptyInput() { ... }

@Test
public void deleteAllWithSingleLineInput() { ... }

@Test
public void deleteAllWithMultipleLinesInput() { ... }

对我来说,拥有不同的检查更容易处理。

然而,由于每个测试都应该是一致的,如果我想让我的初始数据集保持不变,有时我需要在同一个检查中创建和删除内容,以确保其他测试发现数据集是原始的:

@Test
public void insertAndDelete() { 
    assertTrue(/*stuff does not exist yet*/);
    createStuff();
    assertTrue(/*stuff does exist now*/);
    deleteStuff();
    assertTrue(/*stuff does not exist anymore*/);
}

说实话,我不知道是否有更聪明的方法来做这件事...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接