26

在我的项目中,我在进行单元测试时遇到了麻烦。一个问题是,仅进行集成测试编写起来要快得多,并且还可以测试组件是否实际协同工作。单元测试新颖的“算法”左右似乎要容易得多。单元测试服务类它只是感觉错误和无用。

我正在使用 mockito 来模拟 spring 数据存储库(因此是数据库访问)。问题是,如果我告诉模拟存储库在方法调用 getById 上返回实体 A,它显然会返回它,并且服务也会返回它。是的,该服务做了一些额外的事情,但非常小的事情,比如加载惰性集合(来自休眠)。显然我在单元测试中没有任何惰性集合(代理)。

例子:

@Test
public void testGetById() {
    System.out.println("getById");
    TestCompound expResult = new TestCompound(id, "Test Compound", "9999-99-9", null, null, null);

    TestCompoundRepository mockedRepository = mock(TestCompoundRepository.class);
    when(mockedRepository.findOne(id)).thenReturn(expResult);

    ReflectionTestUtils.setField(testCompoundService, "testCompoundRepository",
            mockedRepository, TestCompoundRepository.class);

    TestCompound result = testCompoundService.getById(id);
    assertEquals(expResult, result);
}

万岁,其余的成功。多么惊喜!不是真的没有。

有人可以向我解释我做错了什么吗?否则这种测试的意义何在?我的意思是我告诉返回 expResult 然后它被返回。哇。多么惊喜!感觉就像我在测试 mockito 是否有效,而不是我的服务。

编辑:

如果出现一些愚蠢的错误,我看到的唯一好处就是在那里留下了一条不需要的行,将返回值设置为 null 或类似的愚蠢的东西。这种情况将被单元测试捕获。“回报-努力”比率仍然看起来很糟糕吗?

4

6 回答 6

15

问题可能有点老,但我会回答以防有人偶然发现。

  • 我正在使用 Mockito 和 JUnit。
  • AccountRepository 是一个扩展 JPARepository 的普通 Spring 数据存储库。
  • Account 是一个普通的 JPA 实体。

要测试您的服务和模拟 Spring Data 存储库,您需要以下内容。

package foo.bar.service.impl;

import foo.bar.data.entity.Account;
import foo.bar.data.repository.AccountRepository;
import foo.bar.service.AccountService;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {

    @Mock
    private static AccountRepository accountRepository;

    @InjectMocks
    private static AccountService accountService = new AccountServiceImpl();

    private Account account;

    @Test
    public void testFindAccount() {

        Integer accountId = new Integer(1);

        account = new Account();
        account.setId(accountId);
        account.setName("Account name");
        account.setCode("Accont code");
        account.setDescription("Account description");

        Mockito.when(accountRepository.findOne(accountId)).thenReturn(account);

        Account retrivedAccount = accountService.findAccount(accountId);

        Assert.assertEquals(account, retrivedAccount);

    }

}
于 2014-09-15T04:23:14.633 回答
9

我喜欢测试我的 Spring Data 存储库的原因之一是测试我是否正确定义了我的 JPA 映射。我没有为这些测试使用模拟框架,我使用 Spring Test 框架,它实际上引导容器允许我将实际存储库自动连接到 Junit 测试中,以便我可以针对它执行测试。

我同意你的想法,即嘲笑存储库是毫无用处的。由于您使用 Spring,我建议您利用 Spring Test 框架对您的存储库执行真实测试,这些测试可以以更基于单元测试的方式对嵌入式数据库(如 H2)或您的实际数据库实现(如 Oracle 或 MySql)执行,以进行更多的集成测试。(针对开发数据库的副本执行这些测试)这些测试将揭示您的 JPA 映射和其他项目中的谬误,例如数据库中不正确的级联设置。

这是我在 GitHub 上进行的一项测试的示例。注意框架实际上是如何将存储库自动装配到测试中的。该存储库还包含一个如何配置 Spring Test 框架的示例,我也在这篇文中进行了演示。

总之,我不相信你会从使用存储库的模拟中获得我讨论过的测试存储库的任何好处。

我想补充的另一点是,模拟并不是真正打算在实际测试类中使用。它们的用途是为被测类提供所需的依赖项。

于 2013-03-15T16:25:28.483 回答
2

你可以使用这个库:https ://github.com/agileapes/spring-data-mock

这将为您模拟您的存储库,同时允许您为任何方法以及您的本机查询方法实现自定义功能。

于 2015-02-21T06:47:20.563 回答
1

你完全正确。这是明确的单元测试。而且它永远不会失败(所以,它没用)我认为您需要在集成测试中使用真实数据库(例如在内存中)测试真实的JPA 存储库(就像我一直做的那样)。H2

最好测试您的服务(他们的接口)。如果一段时间后您将更改您的存储(例如 Mongo) - 您将能够使用您的服务测试来确保所有工作都像以前一样。

一段时间后,您会惊讶地发现有多少与 DB\JPA 相关的问题(约束、乐观锁、延迟加载、重复 id、一些休眠问题等等)。

此外,尝试通过测试进行开发 - 而不仅仅是在实施后编写测试。而不是在服务中创建新方法之前 - 为它创建测试,实现服务方法,并且只有在实际应用程序中重新检查它之后。至少启动测试比服务器快得多。

所以,不要创建有很多测试的测试。了解他们如何帮助您。

对存储库使用模拟不是一个好主意。测试您的服务如何与 Hibernate\JPA\Database 一起工作。大部分问题位于两层之间

于 2013-03-15T16:54:43.083 回答
0

您可以模拟存储库并将其注入服务,这就是方式;但是,如果您只是使用@Mock存储库实例化服务,最好将存储库定义为private final服务中的字段并使用所有存储库的构造函数。这样,如果您向服务添加另一个存储库,测试将失败,您必须更改它,这就是目的。

想象一下这个服务:

class OrderService {
    private final UserRepository userRepos;

    public OrderService(UserRepository userRepos) {
        this.userRepos = userRepos;
    }

    ...

}

而这个测试:

class OrderServiceTests {
    @Mock
    private UserRepository userRepos;

    private OrderService service;

    private OrderServiceTests() {
        this.service = new OrderService(this.userRepos);
    }
}

现在,如果我们向服务添加另一个依赖项:

class OrderService {
    private final UserRepository userRepos;
    private final AddressRepository addRepos;

    public OrderService(UserRepository userRepos, AddressRepository addRepos) {
        this.userRepos = userRepos;
        this.addRepos = addRepos;

    ...

}

之前的测试将失败,因为构造函数已更改。如果你使用@InjectMocks这将不会发生;注射发生在幕后,我们不清楚会发生什么;这可能是不可取的。

另一件事是,我不同意集成测试将涵盖单元测试将涵盖的所有情况。可能但并非总是如此。甚至控制器也可以使用 mock 进行单元测试;毕竟所有的测试都是为了覆盖我们编写的所有代码,所以它们必须是细粒度的;想象一下,当我们遵循 TTD 并且只完成控制器和服务级别时:如果没有控制器单元测试,我们将如何进行?

于 2018-09-03T08:45:58.570 回答
0

假设我们有以下服务

@Service 
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

测试类:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

       @Bean
       public EmployeeService employeeService() {
           return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

// write test cases here
}

要检查 Service 类,我们需要创建一个 Service 类的实例并将其作为@Bean使用,以便我们可以在测试类中@Autowire它。此配置是通过使用@TestConfiguration注释来实现的。

在组件扫描过程中,我们可能会发现仅为特定测试创建的组件或配置意外地随处可见。为了帮助防止这种情况,Spring Boot 提供了@TestConfiguration注释,可以在src/test/java中的类上使用,以指示它们不应该被扫描拾取。

这里另一个有趣的事情是@MockBean的使用。它为 EmployeeRepository 创建了一个 Mock,可用于绕过对实际 EmployeeRepository 的调用:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

设置完成后,我们可以轻松地测试我们的服务,例如:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

    assertThat(found.getName())isEqualTo(name);
}

如需更深入的知识检查: https ://www.baeldung.com/spring-boot-testing

于 2018-09-19T11:50:59.980 回答