我知道关于模拟和测试有很多问题,但是我发现没有什么问题可以完美地帮助我,所以我仍然在理解以下方面有困难:
如果我弄错了,请纠正我,但是据我所知,单元测试用于隔离地测试一个特定类的业务逻辑,并且如果有外部需要的任何对象,它们将被模拟。 因此,例如,如果我有一个简单城市公民的管理系统,该系统将公民添加到列表中并按其姓名返回公民(假设:公民仅包含一些基本的个人信息),如下所示:
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;
}
}
如果现在我想对ProcessClass
进行单元测试,我是否认为Citizen
是必须模拟的外部功能,还是我只是为了测试目的而创建Citizen
?
如果它们是模拟的,那么由于模拟对象不包含参数,我将如何测试该方法以按其名称获取对象?
答案 0 :(得分:3)
在编写新代码(以及新的单元测试)或重构现有代码时,您希望能够一遍又一遍地运行单元测试,以确信现有功能是没有损坏。因此,单元测试必须稳定且快速。
假设要测试的类依赖于某些外部资源,例如数据库。您更改了代码,单元测试突然失败。单元测试是否由于您刚引入的错误或由于外部资源不可用而中断?无法保证外部资源将始终可用,因此单元测试不稳定。模拟外部资源。
此外,连接到外部资源可能会花费太多时间。当您最终有成千上万个连接到各种外部资源的测试时,连接到外部资源的毫秒数加起来,这会降低您的速度。模拟外部资源。
现在添加CI / CD管道。在构建期间,单元测试失败。是外部资源不足还是您的代码更改破坏了某些东西?也许构建服务器无权访问外部资源?模拟外部资源。
答案 1 :(得分:2)
通常,模拟用于替换在测试中难以重复的实际调用。例如,假设ProcessClass进行REST调用以检索公民信息。对于简单的单元测试,将很难复制此REST调用。但是,您可以“模拟” RestTemplate并指定不同的返回类型,以确保您的代码可以处理200、403等。此外,您可以更改信息的类型,然后还可以测试您的代码以确保处理了错误的数据,例如信息丢失或为空。
在您的情况下,您实际上可以创建一个Citizen,然后测试该Citizen是列表中的对象还是getByName返回正确的对象。因此,在此示例中无需模拟。
答案 2 :(得分:2)
在您的特定示例中,不,您不需要模拟任何东西。
让我们专注于要测试的内容:
等等等
您已经可以看到许多可以编写的不同测试。
为使它更有趣,您可以在您的类中添加一些代码,以公开公民列表的只读版本,然后可以检查列表是否包含正确的内容。
因此,在您的方案中,您不需要模拟任何东西,因为您对某种其他系统没有外部依赖性。公民似乎只是一个简单的模型类。
答案 3 :(得分:1)
如果它们是模拟的,由于模拟对象不包含参数,我将如何测试该方法按名称获取对象?
例如,您可以使用Mockito模拟对getName
的调用:
Citizen citizen = mock(Citizen.class);
when(citizen.getName()).thenReturn("Bob");
以下是您的方法测试的示例
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"));
}
注意:
我建议嘲笑。假设您以这种方式实例化Citizen
类的地方编写了100个测试
Citizen c = new Citizen();
,几个月后,您的构造函数更改为采用一个参数,这是一个对象本身,例如类City
。现在,您必须返回并更改所有这些测试并编写:
City city = new City("Paris");
Citizen c = new Citizen(city);
如果您嘲笑Citizen
,则无需这样做。
现在,因为它是POJO,并且其getName方法的构造函数可能不会更改,所以不应该进行模拟。
答案 4 :(得分:1)
回答问题的第一部分
如果现在我要对ProcessClass进行单元测试,我是否将Citizen视为必须模拟的外部功能,还是只是出于测试目的而创建Citizen?
在不了解Citizen
的情况下很难说出来。但是,一般规则是,应出于某种原因进行模拟。充分的理由是:
例如,您(通常)不模拟诸如sin或cos之类的标准库数学函数,因为它们没有上述任何问题。在您的情况下,您需要判断仅使用Citizen
是否会导致上述任何问题。如果是这样,最好模拟一下,否则最好不要模拟。