我知道有很多关于模拟和测试的问题,但我没有找到任何可以完美帮助我的问题,所以我仍然无法理解以下内容:
如果我弄错了,请纠正我,但据我所知,单元测试用于单独测试一个特定类的业务逻辑,如果有任何外部需要的对象,它们将被模拟。因此,例如,如果我有一个简单城市的公民管理系统,该系统将公民添加到列表中并按其姓名返回公民(假设:公民仅包含一些基本的个人信息),如下所示:
public class ProcessClass {
ArrayList<Citizen> citizenList = new ArrayList<Citizen>();
public void addCitizen(Citizen citizen) {
citizenList.add(citizen);
}
public Citizen getByName(String name) {
for (Citizen c : citizenList) {
if (c.getName().equals(name)) {
return c;
}
}
return null;
}
}
Run Code Online (Sandbox Code Playgroud)
如果现在我想对我进行单元测试,我ProcessClass是否将其Citizen视为必须被模拟的外部功能,还是只是Citizen为了测试目的而创建一个?如果它们被模拟,我将如何测试通过名称获取对象的方法,因为模拟对象不包含参数?
当您正在编写新代码(以及新的单元测试)或重构现有代码时,您希望能够一遍又一遍地运行单元测试,以合理地确信现有功能没有被破坏。因此,单元测试必须稳定且快速。
假设要测试的类依赖于一些外部资源,例如数据库。您进行了代码更改,但单元测试突然失败了。单元测试是否因为您刚刚引入的错误而中断,或者因为外部资源不可用?无法保证外部资源始终可用,因此单元测试不稳定。模拟外部资源。
此外,连接到外部资源可能需要太多时间。当您最终有数千个连接到各种外部资源的测试时,连接到外部资源的毫秒数加起来,这会减慢您的速度。模拟外部资源。
现在添加 CI/CD 管道。在构建期间,单元测试失败。外部资源是否已关闭,或者您的代码更改是否破坏了某些内容?也许构建服务器无权访问外部资源?模拟外部资源。
回答你问题的第一部分
如果现在我想对我的 ProcessClass 进行单元测试,我是否将 Citizen 视为必须模拟的外部功能,或者我只是创建一个 Citizen 来进行测试?
如果不了解更多信息,Citizen很难说清楚。然而,一般规则是,嘲笑应该是有原因的。好的理由是:
例如,您(通常)不会模拟 sin 或 cos 等标准库数学函数,因为它们不存在任何上述问题。对于您的情况,您需要判断仅仅使用是否Citizen会导致上述任何问题。如果是这样,很可能最好嘲笑它,否则你最好不要嘲笑。
如果它们被模拟,由于模拟对象不包含参数,我将如何测试通过名称获取对象的方法?
您可以使用mockito模拟对 的调用getName,例如:
Citizen citizen = mock(Citizen.class);
when(citizen.getName()).thenReturn("Bob");
Run Code Online (Sandbox Code Playgroud)
这是您的方法的测试示例
ProcessClass processClass = new ProcessClass();
Citizen citizen1 = mock(Citizen.class);
Citizen citizen2 = mock(Citizen.class);
Citizen citizen3 = mock(Citizen.class);
@Test
public void getByName_shouldReturnCorrectCitizen_whenPresentInList() {
when(citizen1.getName()).thenReturn("Bob");
when(citizen2.getName()).thenReturn("Alice");
when(citizen3.getName()).thenReturn("John");
processClass.addCitizen(citizen1);
processClass.addCitizen(citizen2);
processClass.addCitizen(citizen3);
Assert.assertEquals(citizen2, processClass.getByName("Alice"));
}
@Test
public void getByName_shouldReturnNull_whenNotPresentInList() {
when(citizen1.getName()).thenReturn("Bob");
processClass.addCitizen(citizen1);
Assert.assertNull(processClass.getByName("Ben"));
}
Run Code Online (Sandbox Code Playgroud)
笔记:
我建议嘲笑。假设您编写了 100 个测试,并以Citizen这种方式实例化一个类
Citizen c = new Citizen();
Run Code Online (Sandbox Code Playgroud)
几个月后,您的构造函数更改为接受参数,该参数是对象本身,City例如类。现在你必须返回并更改所有这些测试并编写:
City city = new City("Paris");
Citizen c = new Citizen(city);
Run Code Online (Sandbox Code Playgroud)
如果你一Citizen开始就嘲笑,那么你就不需要这样做。
现在,因为它是 POJO 并且它的 getName 方法的构造函数可能不会改变,所以不模拟应该仍然可以。