您专注于单元测试的一个方面,即模拟。当您不能(或不想)想要测试该代码的一小部分时,您可以模拟 - 例如数据库查询或日志记录实用程序。
根据我的经验,单元测试有两种形式:集成和单元测试。
最重要的是,无论哪种类型的单元测试,您都必须了解:
- 快乐路径上的方法的行为是什么,以及
- 边缘情况下的行为是什么。
在编写单元测试时,我将使用 Mockito 之类的模拟库,但我不仅仅将自己排除在此之外。有时使用 Mockito 不适合您想要执行的测试(100 次中有 99 次在集成测试中。不要那样做。),但当我只想模仿来自用于快乐路径或边缘案例测试的重型 bean/object/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());
}
}
注意测试没有真正改变吗?这是一件好事。使用单元测试,您一次只应该真正断言或验证一件事。对测试一个或两个边缘案例进行重大更改可能是代码异味。
编写测试时,请注意以下几点:
代码异味:如果您的代码难以进行单元测试,您应该考虑重构。 这将使测试工作更容易,并且您的代码更易于维护。
不要假装高兴。只模拟你不需要验证的东西。 我不需要验证数据库中的任何内容;不是查询的长度,不是我得到了我想要的确切名称,任何东西 - 所以我嘲笑它。我确实需要验证的是转换是否适当地进行了。
遵循有关测试驱动开发、缺陷驱动测试和普通单元测试的常见做法。 维基百科是一个很好的起点,但也有关于该主题的书籍和闪存卡。