概要
维基百科是这么说 单元测试 的:
在计算机编程中,单元测试是一种软件测试方法,用以测试源代码的单个单元、一个或多个计算机程序模块的集合以及相关的控制数据、使用过程和操作过程,以确定它们是否适合使用。
集成测试:
“集成测试(有时也称集成和测试,缩写为 I&T)是软件测试的一个阶段,在这个阶段中,各个软件模块被组合在一起来进行测试。”
简而言之,当我们在做单元测试时,只是测试了一个代码单元,每次只测试一个方法,不包括与正测试组件相交互的其他所有组件。
另一方面,在集成测试中,我们测试各组件之间的集成。由于单元测试,我们可知这些组件行为与所需一致,但不清楚它们是如何在一起工作的。这就是集成测试的职责。
如何配置单测
方法1:给被测类添加@RunWith(MockitoJUnitRunner.class)注解
使用Junit的Runner,使用基于Mockito的Runner,用以构造Mock的Context。
@RunWith(MockitoJUnitRunner.class)
public class UnitTestBase {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Before
public void startUp() {
}
}
方法2:在初始化方法中使用MockitoAnnotations.initMocks(this)
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
单测代码类直接继承UnitTestBase,并可以自由使用@Mock 以及@InjectMocks
注:@Mock与@InjectMocks 只能针对一个类使用,例如:
public class FooRpcImpl implements FooRpc {
@Resource
private UserServiceFacade userService ;
@Resource
private OrderServiceFacade orderService;
}
Mock配置:
public class FooRpcImplTest extends UnitTestBase {
@Mock
private UserServiceFacade userService ;
@Mock
private OrderServiceFacade orderService;
@InjectMocks
private FooRpcImpl fooRpc;
}
运行时,框架会反射创建一个FooRpcImpl对象,然后Mock UserServiceFacade,OrderServiceFacade两个对象,再注入给fooRpc,这种场景完全忽略Spring的依赖注入机制。
在实际测试时,需要定制对象的Mock行为,也就是编写如何Mock的代码,Mock行为的代码如果是公共的,可以放到Before的方法里,表示每个测试方法都会调用,生效Mock行为。
@Before
public void testBefore() {
User u = new User();
Mockito.when(userService.getUserById(anyLong)).thenReturn(u);
Order order = new Order();
Mockito.when(orderService.getOrderUserId(anyLong)).thenReturn(order);
}
@Test
public void testGetUserOrder(){
UserOrder uo = fooRpc.getUserOrder(2000);
Assert.assertThat(uo.getUserId(), Matcher.is(2000));
}
@Mock与@Spy
@InjectMocks 标签会自动填充带@Spy和@Mock标签的bean.
@Spy 被它代理的bean,默认执行原生方法。但可以在后期mock想要的方法。
@Mock 相当于mockito帮助简单的实例化bean,因此无法执行原生方法。适用于整个bean都mock,如DAO。
// 用法直接new新对象
@Spy
private ExampleService spyExampleService = new ExampleService(1);
@InjectMocks
private FooRpcImpl fooRpc;
如何配置集成测试
集成测试一般启动Spring 容器,然后使用Spring的依赖注入,与真实的系统启动机制一致,这样要测试某个业务方法,直接调用Spring容器中Bean,并测试Bean的方法。
这里贴一个使用基于SpringBoot的集成测试基类:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class) // 这是启动SpringBoot的入口类
@Rollback
@Transactional
public abstract class TransactionalTestBase implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
jdbcTemplate.setDataSource(applicationContext.getBean(DataSource.class));
}
protected JdbcTemplate jdbcTemplate = new JdbcTemplate();
@Rule
public ExpectedException thrown = ExpectedException.none();
}
实际的测试类直接继承该基类即可。
如果在Bean的依赖关系里,需要Mock调某个Bean,则使用SpringTest提供的@MockBean 注解,可以动态替换调原有Spring注入的原始Bean。举个例子:ServiceA 依赖 ServiceB,ServiceB 依赖 RpcA, 测试ServiceA的方法时,最终会调用到RpcA, RpcA 不稳定,因此需要Mock RpcA的方法,此时直接在测试类中使用@MockBean即可。或者想直接MockServiceA的ServiceB属性举个例子:
@Service
public class FooService {
@Recourse
private UserServiceFacade userService ;
@Recourse
private OrderServiceFacade orderService;
}
public class FooRpcImplTest extends UnitTestBase {
@MockBean
private UserServiceFacade userService ;
@MockBean
private OrderServiceFacade orderService;
@Resource
private FooService fooService;
// 这里注入的FooService的两个字段就是被Mock调的userService,orderService
// 测试方法,也需要定制Mock行为
}
一些用法
测试抛出异常
可以使用Junit提供的@Rule工具,把这个工具放到基类里
@Rule
public ExpectedException thrown = ExpectedException.none();
// 测试抛出异常
@Test
public void testException() {
// 先写异常预期代码
thrown.expect(RuntimeException.class);
thrown.expectMessage("异常Message");
Foo f = new Foo();
// 再写测试代码
f.check();
}
自定义Matcher
private static class CustomMatcher extends TypeSafeMatcher<ExtRuntimeException> {
private String foundSubErrorCode;
private String expectedSubErrorCode;
private CustomMatcher(SubErrorCodeEnum subErrorCode) {
this.expectedSubErrorCode = subErrorCode.getCode();
}
public static CustomMatcher hasSubError(SubErrorCodeEnum code) {
return new CustomMatcher(code);
}
@Override
protected boolean matchesSafely(ExtCapRuntimeException item) {
this.foundSubErrorCode = item.getSubCode();
return this.expectedSubErrorCode.equals(foundSubErrorCode);
}
@Override
public void describeTo(Description description) {
description.appendValue(this.foundSubErrorCode)
.appendText(" was not found instead of ")
.appendValue(expectedSubErrorCode);
}
}
使用Matcher
thrown.expect(ExtRuntimeException.class);
thrown.expect(CustomMatcher.hasSubError(SubErrorCodeEnum.RESULT_SIGNATURE_VERIFICATION_NOT_PASS));
常用断言工具:Matchers with assertThat
// Core Hamcrest Matchers with assertThat
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));