【问题标题】:What is the point in unit testing mock returned data?单元测试模拟返回的数据有什么意义?
【发布时间】:2018-07-20 13:56:51
【问题描述】:
考虑我正在模拟某些服务及其方法的场景。
Employee emp = mock(Employee.class);
when(emp.getName(1)).thenReturn("Jim");
when(emp.getName(2)).thenReturn("Mark");
//assert
assertEquals("Jim", emp.getName(1));
assertEquals("Mark", emp.getName(2));
在上面的代码中,当调用 emp.getName(1) 时,mock 将返回 Jim,当调用 emp.getName(2) 时,mock 将返回 Mark。我的问题是我要声明 Mock 的行为并检查它assertEquals 上面(或同类)断言语句有什么意义?这些显然会过去。这就像检查3==(1+2) 有什么意义?这些测试何时会失败(除了更改返回类型和参数类型)?
【问题讨论】:
标签:
java
unit-testing
junit
mocking
mockito
【解决方案1】:
正如您所说,这类测试毫无意义(当然,除非您正在为 Mockito 本身编写单元测试 :-))。
模拟的目的是消除外部依赖,这样您就可以对代码进行单元测试,而无需依赖其他类的代码。例如,假设您有一个使用您描述的 Employee 类的类:
public class EmployeeExaminer {
public boolean isJim(Employee e, int i) {
return "Jim".equals(e.getName(i));
}
}
并且您想为它编写一个单元测试。当然,您可以使用实际的 Employee 类,但是您的测试将不再是 unit 测试 - 这将取决于 Employee 的实现。这就是 mock 派上用场的地方 - 它允许您将 Employee 替换为可预测的行为,以便您可以编写稳定的单元测试:
// The object under test
EmployeeExaminer ee = new EmployeeExaminer();
// A mock Employee used for tests:
Employee emp = mock(Employee.class);
when(emp.getName(1)).thenReturn("Jim");
when(emp.getName(2)).thenReturn("Mark");
// Assert EmployeeExaminer's behavior:
assertTrue(ee.isJim(emp, 1));
assertFalse(ee.isJim(emp, 2));
【解决方案2】:
在您的情况下,您正在测试一个吸气剂,我不知道您为什么要测试它,也不知道您为什么需要模拟它。从您提供的代码来看,这是没有用的。
在许多情况下,当你编写单元测试时,模拟是有意义的,你必须务实,你应该测试行为和模拟依赖关系。
在这里,您不是在测试行为,而是在模拟被测类。
【解决方案3】:
那个测试没有意义。
Mocks 仅用于将依赖项注入类并测试特定行为是否与该依赖项正确交互,或者允许您测试一些需要在您正在编写的测试中不关心的接口的行为。
模拟被测类意味着您甚至没有真正测试该类。
如果emp 变量被注入到另一个类中,然后该类正在被测试,那么我可以看到某种指向它。
【解决方案4】:
上面的测试用例正在尝试测试 POJO。
实际上,你可以忽略测试 POJO,或者换句话说,在测试其他基本功能时会自动测试它们。 (也有 mean-beans 等实用程序来测试 POJO)
单元测试的目标是在不连接任何外部系统的情况下测试功能。如果您连接到任何外部系统,则被视为集成测试。
模拟对象有助于创建在单元测试期间无法创建的模拟对象,并根据返回的模拟对象(或连接到外部系统时的真实对象)数据来测试行为/逻辑。
【解决方案5】:
Mocks 是模拟外部依赖项的行为的结构,这些依赖项您没有/不能拥有或在测试上下文中无法正常运行,因为它们依赖于其他外部系统自己(例如与服务器的连接)。因此,您描述的测试确实不是很有帮助,因为您基本上只是尝试验证模拟的模拟行为,而不是其他任何东西。
一个更好的例子是类EmployeeValidator,它依赖于另一个系统EmployeeService,它向外部服务器发送请求。服务器可能在您的测试的当前上下文中不可用,因此您需要模拟发出请求的服务并模拟其行为。
class EmployeeValidator {
private final EmployeeService service;
public EmployeeValidator(EmployeeService service) {
this.service = service;
}
public List<Employee> employeesWithMaxSalary(int maxSalary) {
List<Employee> allEmployees = service.getAll(); // Possible call to external system via HTTP or so.
List<Employee> filtered = new LinkedList<>();
for(Employee e : allEmployees) {
if(e.getSalary() <= maxSalary) {
filtered.add(e);
}
}
return filtered;
}
}
然后您可以编写一个模拟EmployeeService 并模拟对外部系统的调用的测试。之后,您可以验证一切是否按计划进行。
@Test
public void shouldContainAllEmployeesWithSalaryFiveThousand() {
// Given - Define behaviour
EmployeeService mockService = mock(EmployeeService.class);
when(mockService.getAll()).thenReturn(createEmployeeList());
// When - Operate the system under test
// Inject the mock
EmployeeValidator ev = new EmployeeValidator(mockService);
// System calls EmployeeService#getAll() internally but this is mocked away here
List<Employee> filtered = ev.employeesWithMaxSalary(5000);
// Then - Check correct results
assertThat(filtered.size(), is(3)); // There are only 3 employees with Salary <= 5000
verify(mockService, times(1)).getAll(); // The service method was called exactly one time.
}
private List<Employee> createEmployeeList() {
// Create some dummy Employees
}