是否有创建JUnit测试用例的过程?

时间:2013-04-18 03:48:50

标签: java unit-testing

我是Java开发的新手,今天我第一次配对程序来创建测试用例。他们非常友好地大声说出他们想要解释为什么要做某些事情的事情(例如创建一个带有参数的构造函数而不是只有一个默认的构造函数等)。

虽然我可能会保留今天看到/听到的40%,但我想知道是否有一系列步骤/指导方针要遵循,最终结果是一个测试用例,这将是一个很好的起点。

我知道通常的反应
  1. 每家商店都有自己的标准,
  2. 有个人偏好和工具,
  3. 取决于测试是针对您拥有的课程还是遗留课程等。
  4. 无论哪种方式,我想并询问SO成员。

    以下是我采取的一些说明的片段,用以表明我的想法,就回应而言。

    1. 使用Mockito,您无法模拟使用方法创建的对象,因此请尝试使用构造函数来获取这些对象。这将允许您模拟它们,然后将它们传递给方法以便于测试。
    2. Mockito不处理静态方法,但您可以创建一个调用静态方法的包装类。现在,这个新的包装类不使用任何静态方法,而您的测试将使用它而不是静态方法。现在,您可以使用/ thenReturn而无需直接处理静态方法。
    3. 在模拟时,声明将在类级别的所有测试用例使用的变量。然后在setUp()中模拟这些类;这种方法可确保常用的模拟始终处于已知状态,并且不会被先前的测试修改过。
    4. 不能说,使用好的变量名(例如listWithOneCar,listWithNoCars等)
    5. 由于测试用例有时基于真值表,因此请尝试将其包含在测试方法名称中(例如,EnsureCanDrive_Car_0_withValidKey_0(),ensureCanDrive_Car_1_withValidKey_1()等)。如果您通过快速可视扫描捕获了大量测试用例,这应该可以帮助您直观地扫描。完成后,只需添加边缘情况。
    6. 'verify'未使用,请尝试在测试用例中使用更多。
    7. 单个测试用例的代码应该只在测试用例之间有所不同,而且不能重复代码。这使得理解和维护/扩展更容易。
    8. 你明白了。 这些只是我的笔记,但我想知道是否有人有一个已经进化的列表?

2 个答案:

答案 0 :(得分:1)

列表中的内容非常不同。甚至对于其中一个主题,“完整”列表可能不太可能。大多数人会组成一本书,可能是倍数。所以这里是我的书籍清单(和其他文档),它们汇总了一个完整的列表。

  • 有些似乎是关于一般良好的编码实践。例如,建议通过构造函数使用依赖注入(#1);好名字的重要性(#4)。很难列出关于这些的完整清单,但有一些重要的书籍。我推荐

    • Robert C. Martin的清洁代码

    • Andrew Hunt和David Thomas的实用程序员

    • Joshua Bloch的有效Java

  • 有些是关于Mockito的限制,只需查看文档和一些博客文章即可。您可能还想查看PowerMock,它试图修复Mockito和其他模拟框架的一些(大多数?)技术限制。只要阅读他们所做的事情,就可以提高您对Mockitos限制的理解。

  • 有些是关于设计测试,使代码可测试等等。您可以考虑以下书籍:

    • xUnit测试模式:Gerard Meszaros重构测试代码

    • 测试驱动:Lasse Koskela的Java开发人员TDD和验收TDD

    • 测试驱动开发:Kent Beck的例子

答案 1 :(得分:1)

你专注于单元测试的一个方面,即嘲弄。当你不能(或不想)测试该代码的一小部分时(例如数据库查询或日志记录实用程序),您可以进行模拟。

根据我的经验,单元测试有两种形式:集成和单元测试。

最重要的是,无论是哪种单元测试,你都必须明白:

  • 该方法在快乐路径上的行为是什么,
  • 边缘情况下的行为是什么。

在编写单元测试时,我将使用像Mockito这样的模拟库,但我不仅仅是将自己排除在外。有些时候使用Mockito不适合你想要做的测试(100次中有99次,它是在集成测试中。不要这样做。),但是当我只想模仿来自的行为时它很方便用于快乐路径或边缘案例测试的重豆/对象/ DAO。

我对测试的偏好是将它们分成三个不同的部分:

  • 我的赠品,包括设置要测试的方法,包括模拟时的行为,
  • 我的“当我这样做”这个子句,它基本上只是执行方法,而且
  • 我的验证步骤,其中涉及我测试结果的一个方面。

作为一个例子,假设我想测试这种方法的行为。

public List<String> shortenNamesFromDatabase(final int maxLength) {
    List<String> names = dao.executeQuery("SELECT name from dbNames");
    List<String> result = new ArrayList<String>();
    for(String name : names) {
        result.add(name.substring(0, maxLength);
    }
    return result;
}

我有一个对数据库的调用,以及一些转换逻辑。我不想在此层验证数据库信息,所以我会嘲笑它。在集成测试中,我确保调用数据库,从数据库中获取每个名称。

//happy path test!
@Test
public void shortenNamesFromDatabase_namesAreOnly3CharactersLong() {
    //given
    final int length = 3;
    List<String> dbResult = Arrays.asList("Alpha","Beta", "Gamma", "Del", "Zeta12345");
    Dao daoMock = mock(Dao.class);
    when(daoMock.executeQuery("SELECT name from dbNames")).thenReturn(dbResult);
    SomeClass testObj = new SomeClass();

    //when
    List<String> result = testObj.shortenNamesFromDatabase(length);

    //then
    for(String name : result) {
        assertTrue("Name was too long!", length <= name.length());
    }
}

现在假设我想测试一些边缘案例行为。如果我将最大长度设置为零,我会期待一大堆空字符串。不理想,但我最好确保边缘情况下的行为符合我的预期。

//edge-case-why-would-you-ever-do-this-for-real test
@Test
public void shortenNamesFromDatabase_zeroLengthStringsTransformed() {
    //given
    final int length = 0;
    List<String> dbResult = Arrays.asList("Alpha","Beta", "Gamma", "Del", "Zeta12345");
    Dao daoMock = mock(Dao.class);
    when(daoMock.executeQuery("SELECT name from dbNames")).thenReturn(dbResult);
    SomeClass testObj = new SomeClass();

    //when
    List<String> result = testObj.shortenNamesFromDatabase(length);

    //then
    for(String name : result) {
        assertTrue("Name was WAY too long!", length <= name.length());
    }
}

注意测试没有真正改变?这是的事情。使用单元测试,您应该只是一次断言或验证一件事。对一个或两个边缘情况进行重大更改可能会产生代码异味。

编写测试时,请注意以下事项:

  • 代码味道:如果您的代码难以进行单元测试,则应考虑重构。这将使测试工作更轻松,并且您的代码更易于维护。

    < / LI>
  • 不要模仿快乐。只模拟你不需要验证的东西。我不需要验证数据库中的任何内容;不是查询的长度,不是我得到了我想要的确切名称,任何东西 - 所以我嘲笑它。我所做的需要验证的是转换发生得恰当。

  • 遵循有关测试驱动开发,缺陷驱动测试和普通单元测试的常见做法。 Wikipedia是一个很好的起点,但有关于主题的书籍和闪存卡。