测试是开发过程中必不可少的, 但是实际工作中严格按照标准, 测试用例能够覆盖大部分业务逻辑的, 估计连一半都不到.
每天的工作流程应该是这样的:
git
上把代码更新到本地, 跑通所有单元测试, 确保代码修改前是正确的git
单元测试主要使用Junit
工具, 已经是很古老的技术了, 现在的Junit4
直接通过注解就可以实现, 特别方便.
另一个测试的必备利器就是数据模拟工具Mockito
. 假设我们在代码中有如下的调用关系:
我们要对A
进行单元测试的时候需要整个调用树都构建出来, 即BCDE
的示例都需要, 但是显然我们只关心A
, 这个时候我们可以使用Mockito
模拟BC
的返回, 使用mock对象
来代替BC
.
Junit
使用详解Junit4
使用方式特别简单, 只需要加一些注解就可以. 下面对一些常用的注解进行简单说明:
@Test
: 标识一个普通的测试方法, @Test(timeout = 1000)
表示测试方法执行超过1000毫秒后算超时, 测试将失败; @Test(expected = Exception.class)
表示测试方法期望得到的异常类, 如果方法执行没有抛出指定的异常, 则测试失败@Before
: 初始化方法, 对每一个方法都执行一次@BeforeClass
: 在所有测试方法前执行一次@After
: 在每个测试方法后执行一次@AfterClass
: 在所有测试方法后执行一次@Ignore("not ready yet")
: 表示暂时不执行该测试方法@RunWith
: 在JUnit中有很多个Runner
, 他们负责调用你的测试代码, 该注解用于指定一个Runner
@Suite.SuiteClasses
: 打包测试, 需要与@RunWith(Suite.class)
联合使用, 比如下面的例子:@RunWith(Suite.class)
@SuiteClasses({ATest.class, BTest.class, CTest.class})
public class ABCSuite {
// 类中不需要编写代码
}
Parameterized.Parameters
: 参数化测试, 这个稍微复杂一些, 一般有这么几个条件:
@RunWith(Parameterized.class)
@Parameters
注解@Test
到方法下面是参数化测试的一个例子:
@RunWith(Parameterized.class)
public class ParameterTest {
private String name;
private boolean result;
/**
* 该构造方法的参数与下面@Parameters注解的方法中的Object数组中值的顺序对应
*/
public ParameterTest(String name, boolean result) {
super();
this.name = name;
this.result = result;
}
// 将对参数构建出的每个对象都执行一遍, 即本例中将会执行三遍
@Test
public void test() {
Assert.assertTrue(name.contains("小") == result);
}
/**
* 相当于通过不通参数构建出了三个对象
*/
@Parameterized.Parameters
public static Collection<?> data(){
// Object 数组中值的顺序注意要和上面的构造方法ParameterTest的参数对应
return Arrays.asList(new Object[][]{
{"小明2", true},
{"坏", false},
{"莉莉", false},
});
}
}
另外单测中还常用到一些断言方法, 比较简单, 不再介绍.
Mockito
使用详解使用Mockito
可以设定当调用哪个对象的哪个方法时, 返回什么数据; 还可以验证调用了某对象的某方法几次.
创建mock对象不能对final, Anonymous, primitive类
进行mock
下面是一个简单的例子及相关说明
@Test
public void testMock() {
// 创建mock对象,参数可以是类,也可以是接口
List<String> list = mock(List.class);
// 设置方法的预期返回值
when(list.get(0)).thenReturn("helloworld");
// 当调用 list.get(0) 时, 将返回之前设置的值
String result = list.get(0);
// junit 测试
Assert.assertEquals("helloworld", result);
// 设定方法返回某异常
when(list.get(1)).thenThrow(new RuntimeException("test excpetion"));
// stubbing 形式, doXXX 返回的是一个 Stubber 对象
doReturn("helloworld").when(list).get(0); // 效果与最上面那个when一样
doNothing().doThrow(new RuntimeException("void exception")).when(list).clear(); // 返回 void 的方法
/**
* 验证方法调用, 不关心返回值, 只关心调用了几次
*/
verify(list).get(0); // 验证是否调用了 list.get(0)
verify(list, times(1)).get(0); // 默认调用一次,times(1)可以省略
verify(list, times(3)).get(0); // 验证调用三次
verify(list, times(0)).get(0); // 一次也没调用
verify(list, never()).get(0); // 一次也没调用, 同上
verify(list, atLeastOnce()).get(0); // 至少一次
verify(list, atLeast(2)).get(0); // 至少两次
verify(list, atMost(5)).get(0); // 最多5次
}
Mockito
类继承于Matchers
Matchers
类中有许多参数匹配器用于匹配一种类型, 比如anyInt, anyString, anyMap...
get(0)
又有get(anyInt())
如下例子:
when(list.get(anyInt())).thenReturn("helloworld");
when(map.get(anyString())).thenReturn("hello");
when(list.get(0)).thenReturn("helloworld"); // 再这么用, 就会报错
与单元测试不通, 在Spring中需要初始化完整的应用程序上下文, 因此这种测试常称为集成测试.
Spring Boot提供了一个测试相关的starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
我们在之前的Spring项目中使用JUnit
测试时, 测试类是这样写的:
@RunWith(SpringJUnit4ClassRunner.class) // Spring JUnit支持
@ContextConfiguration(classes = Application.class) // 指定启动类
@WebAppConfiguration // 如果是Web项目, Junit需要模拟ServletContext, 因此需要加上这个注解
public class ExampleServiceTest {
}
而在Spring Boot 1.4
已经做了优化, 变成了这样:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class ExampleServiceTest {
}
SpringRunner
是SpringJUnit4ClassRunner
的新名字, 这个名字只是让名字看起来简单些.SpringBootTest
将使用你的SpringApplication
来创建ApplicationContext
, classes
属性可以不指定,会自动发现, 另外还有一个webEnvironment
属性用于指定web的测试环境Spring Boot 1.3
中的@SpringApplicationConfiguration
和@WebIntegrationTest
已经被废弃掉了, 被@SpringBootTest
替代了在Spring项目中做测试的时候会发现大部分情况下都需要模拟特定的bean, 使某个bean的特定方法返回你想要的数据. 在Spring Boot中变的特别简单, 比如:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
@MockBean
private RemoteService remoteService; // 这个bean是被mock出来的, 会替换掉spring中的那个bean
@Autowired
private Reverser reverser; // 这个bean是spring中的
@Test
public void exampleTest() {
// RemoteService 已经被注入到了 reverser bean里了
when(remoteService.someCall()).thenReturn("mock");
String reverse = reverser.reverseSomeCall();
}
}
@MockBean
是把原来的bean用mock的bean整个替换掉了, 而@SpyBean
还会执行原来bean的方法, 但是后面可以mock想要的方法