单元测试模拟返回数据有什么意义?

时间:2018-02-09 18:18:30

标签: java unit-testing junit mocking mockito

考虑我嘲笑某些服务及其方法的场景。

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)有什么意义一样?这些测试何时失败(除了更改返回类型和参数类型)?

5 个答案:

答案 0 :(得分:5)

正如您所指出的,这些测试毫无意义(除非您正在为Mockito本身编写单元测试,当然: - ))。

模拟的目的是消除外部依赖关系,这样您就可以对代码进行单元测试,而不依赖于其他类'码。例如,假设您有一个使用您所描述的Employee类的类:

public class EmployeeExaminer {
    public boolean isJim(Employee e, int i) {
        return "Jim".equals(e.getName(i));
    }
}

你想为它编写单元测试。当然,你可以使用实际的Employee课程,但是你的测试不再是单元 - 测试 - 它将取决于Employee&# 39;实施。这里的模拟很方便 - 它允许你用可预测的行为替换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));

答案 1 :(得分:1)

该测试没有意义。

模拟仅用于将依赖项注入类并测试特定行为是否正确地与该依赖项交互,或者允许您测试某些行为,这些行为需要您正在编写的测试中不关心的接口。

模拟正在测试的类意味着你甚至没有真正测试那个类。

如果emp变量被注入到另一个类中,然后该类正在测试,那么我可以看到它的某种指向。

答案 2 :(得分:1)

以上测试用例正在尝试测试POJO。

实际上,您可以忽略测试POJO,或者换句话说,在测试其他基本功能时会自动测试它们。 (还有一些实用程序mean-beans来测试POJO)

单元测试的目标是在不连接任何外部系统的情况下测试功能。如果要连接到任何外部系统,则认为是集成测试。

模拟对象有助于创建在单元测试期间无法创建的模拟对象,并根据返回的模拟对象(或连接到外部系统时的实际对象)数据测试行为/逻辑。

答案 3 :(得分:1)

模拟是模拟外部依赖项的行为的结构,这些外部依赖项您没有/不能拥有,或者在测试环境中无法正常运行,因为它们依赖于其他外部系统他们自己(例如与服务器的连接)。因此,你所描述的测试确实不是很有帮助,因为你基本上试图验证模拟的模拟行为而不是其他任何东西。

更好的示例是类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
}

答案 4 :(得分:0)

在你的情况下,你正在测试一个getter,我不知道你为什么要测试它,也不知道为什么你需要模拟它。从你提供的代码来看,这是没用的。

有许多场景,当你编写单元测试时,模拟是有意义的,你必须务实,你应该测试行为和模拟依赖。

在这里,你没有测试行为,而是在嘲笑被测试的课程。