概要

维基百科是这么说 单元测试 的:

在计算机编程中,单元测试是一种软件测试方法,用以测试源代码的单个单元、一个或多个计算机程序模块的集合以及相关的控制数据、使用过程和操作过程,以确定它们是否适合使用。

集成测试

“集成测试(有时也称集成和测试,缩写为 I&T)是软件测试的一个阶段,在这个阶段中,各个软件模块被组合在一起来进行测试。”

简而言之,当我们在做单元测试时,只是测试了一个代码单元,每次只测试一个方法,不包括与正测试组件相交互的其他所有组件。
另一方面,在集成测试中,我们测试各组件之间的集成。由于单元测试,我们可知这些组件行为与所需一致,但不清楚它们是如何在一起工作的。这就是集成测试的职责。

如何配置单测

方法1:给被测类添加@RunWith(MockitoJUnitRunner.class)注解
使用Junit的Runner,使用基于Mockito的Runner,用以构造Mock的Context。

  1. @RunWith(MockitoJUnitRunner.class)
  2. public class UnitTestBase {
  3. @Rule
  4. public ExpectedException thrown = ExpectedException.none();
  5. @Before
  6. public void startUp() {
  7. }
  8. }

方法2:在初始化方法中使用MockitoAnnotations.initMocks(this)

  1. @Before
  2. public void init() {
  3. MockitoAnnotations.initMocks(this);
  4. }

单测代码类直接继承UnitTestBase,并可以自由使用@Mock 以及@InjectMocks
注:@Mock与@InjectMocks 只能针对一个类使用,例如:

  1. public class FooRpcImpl implements FooRpc {
  2. @Resource
  3. private UserServiceFacade userService ;
  4. @Resource
  5. private OrderServiceFacade orderService;
  6. }

Mock配置:

  1. public class FooRpcImplTest extends UnitTestBase {
  2. @Mock
  3. private UserServiceFacade userService ;
  4. @Mock
  5. private OrderServiceFacade orderService;
  6. @InjectMocks
  7. private FooRpcImpl fooRpc;
  8. }

运行时,框架会反射创建一个FooRpcImpl对象,然后Mock UserServiceFacade,OrderServiceFacade两个对象,再注入给fooRpc,这种场景完全忽略Spring的依赖注入机制。
在实际测试时,需要定制对象的Mock行为,也就是编写如何Mock的代码,Mock行为的代码如果是公共的,可以放到Before的方法里,表示每个测试方法都会调用,生效Mock行为。

  1. @Before
  2. public void testBefore() {
  3. User u = new User();
  4. Mockito.when(userService.getUserById(anyLong)).thenReturn(u);
  5. Order order = new Order();
  6. Mockito.when(orderService.getOrderUserId(anyLong)).thenReturn(order);
  7. }
  8. @Test
  9. public void testGetUserOrder(){
  10. UserOrder uo = fooRpc.getUserOrder(2000);
  11. Assert.assertThat(uo.getUserId(), Matcher.is(2000));
  12. }

@Mock与@Spy

@InjectMocks 标签会自动填充带@Spy和@Mock标签的bean.
@Spy 被它代理的bean,默认执行原生方法。但可以在后期mock想要的方法。
@Mock 相当于mockito帮助简单的实例化bean,因此无法执行原生方法。适用于整个bean都mock,如DAO。

  1. // 用法直接new新对象
  2. @Spy
  3. private ExampleService spyExampleService = new ExampleService(1);
  4. @InjectMocks
  5. private FooRpcImpl fooRpc;

如何配置集成测试

集成测试一般启动Spring 容器,然后使用Spring的依赖注入,与真实的系统启动机制一致,这样要测试某个业务方法,直接调用Spring容器中Bean,并测试Bean的方法。
这里贴一个使用基于SpringBoot的集成测试基类:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = Application.class) // 这是启动SpringBoot的入口类
  3. @Rollback
  4. @Transactional
  5. public abstract class TransactionalTestBase implements ApplicationContextAware {
  6. @Override
  7. public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  8. jdbcTemplate.setDataSource(applicationContext.getBean(DataSource.class));
  9. }
  10. protected JdbcTemplate jdbcTemplate = new JdbcTemplate();
  11. @Rule
  12. public ExpectedException thrown = ExpectedException.none();
  13. }

实际的测试类直接继承该基类即可。

如果在Bean的依赖关系里,需要Mock调某个Bean,则使用SpringTest提供的@MockBean 注解,可以动态替换调原有Spring注入的原始Bean。举个例子:ServiceA 依赖 ServiceB,ServiceB 依赖 RpcA, 测试ServiceA的方法时,最终会调用到RpcA, RpcA 不稳定,因此需要Mock RpcA的方法,此时直接在测试类中使用@MockBean即可。或者想直接MockServiceA的ServiceB属性举个例子:

  1. @Service
  2. public class FooService {
  3. @Recourse
  4. private UserServiceFacade userService ;
  5. @Recourse
  6. private OrderServiceFacade orderService;
  7. }
  8. public class FooRpcImplTest extends UnitTestBase {
  9. @MockBean
  10. private UserServiceFacade userService ;
  11. @MockBean
  12. private OrderServiceFacade orderService;
  13. @Resource
  14. private FooService fooService;
  15. // 这里注入的FooService的两个字段就是被Mock调的userService,orderService
  16. // 测试方法,也需要定制Mock行为
  17. }

一些用法

测试抛出异常

可以使用Junit提供的@Rule工具,把这个工具放到基类里

  1. @Rule
  2. public ExpectedException thrown = ExpectedException.none();
  3. // 测试抛出异常
  4. @Test
  5. public void testException() {
  6. // 先写异常预期代码
  7. thrown.expect(RuntimeException.class);
  8. thrown.expectMessage("异常Message");
  9. Foo f = new Foo();
  10. // 再写测试代码
  11. f.check();
  12. }

自定义Matcher

  1. private static class CustomMatcher extends TypeSafeMatcher<ExtRuntimeException> {
  2. private String foundSubErrorCode;
  3. private String expectedSubErrorCode;
  4. private CustomMatcher(SubErrorCodeEnum subErrorCode) {
  5. this.expectedSubErrorCode = subErrorCode.getCode();
  6. }
  7. public static CustomMatcher hasSubError(SubErrorCodeEnum code) {
  8. return new CustomMatcher(code);
  9. }
  10. @Override
  11. protected boolean matchesSafely(ExtCapRuntimeException item) {
  12. this.foundSubErrorCode = item.getSubCode();
  13. return this.expectedSubErrorCode.equals(foundSubErrorCode);
  14. }
  15. @Override
  16. public void describeTo(Description description) {
  17. description.appendValue(this.foundSubErrorCode)
  18. .appendText(" was not found instead of ")
  19. .appendValue(expectedSubErrorCode);
  20. }
  21. }

使用Matcher

  1. thrown.expect(ExtRuntimeException.class);
  2. thrown.expect(CustomMatcher.hasSubError(SubErrorCodeEnum.RESULT_SIGNATURE_VERIFICATION_NOT_PASS));

常用断言工具:Matchers with assertThat

  1. // Core Hamcrest Matchers with assertThat
  2. assertThat("good", allOf(equalTo("good"), startsWith("good")));
  3. assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
  4. assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
  5. assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
  6. assertThat(new Object(), not(sameInstance(new Object())));