单元测试Junit4+mockito+powermock
1.单元测试的意义
- 梳理思维逻辑
- 提升编码能力,架构能力和代码质量
- 基本思想:输入输出,期望值和真实值对比
不容易编写测试代码的代码很可能就是设计上有缺陷或者不完美,基于测试驱动理念,能写出更加符合设计更加优美的代码
1.1 常见单元测试框架
easymock, testNg, mockito
1.2 单元测试的一些原则
- 一次只关注一个功能函数。开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为
- 单元测试覆盖功能范围。主体功能,边界条件,特殊情况的处理
- 单元测试覆盖率,不追求100%。语句覆盖,分支覆盖,条件覆盖,分支-条件覆盖,条件组合覆盖,路径覆盖
- 需要练习才能掌握
2.Junit4使用
简介:单元测试框架,内部涵盖了单元测试的主要流程,为单元测试提供了模板
image.png@BeforeClass @AfterClass类级别,被标注该注解的方法只会执行一次,方法必须为public static void
@Before @After 会多次执行,每个test方法执行时都会先执行@Before,执行完test方法之后再执行@After。
单元测试启动方式
- IDEA开发平台自带的Run工具(最常用)
- 使用单元测试类JUnitCore.runClasses(研究源码的可以通过此类作为入口)
- mvn命令执行时test阶段
2.2 断言(https://hamcrest.org/JavaHamcrest/tutorial)
前期Junit4使用的是自己包内的断言(扩展性不好,无法动态扩充新的断言语句),后来转为使用hamcrest中的断言(org.hamcrest.MatcherAssert)。hamcrest的核心是引入了Matcher机制,核心类org.hamcrest.CoreMatchers和org.hamcrest.Matchers(hamcrest-library包中),另外可以自定义Matcher。
可以分为几大类
- 一般匹配断言
- assertThat(“myValue”, allOf(startsWith(“my”), containsString(“Val”)))
- assertThat( “myValue”, anything() )
- assertThat( “myValue”, not(“foo”))
- 字符串匹配
- assertThat(“myStringOfNote”, containsString(“ring”))
- assertThat(“myStringOfNote”, startsWith(“my”))
- 数值相关匹配
1.assertThat(2, greaterThan(1))
2.assertThat(1.03, is(closeTo(1.0, 0.03))) - 集合相关匹配
1.assertThat(myMap, hasEntry(“bar”, “foo”))
2.assertThat(Arrays.asList(“foo”, “bar”), hasItem(startsWith(“ba”)))
3.assertThat(myMap, hasKey(“bar”)) - Beans相关
1.hasProperty
2.samePropertyValuesAs
3.getPropertyDescriptor
2.3 Runner
@RunWith
@Ignore
@Rule
//同时执行多个测试类
@RunWith(Suite.class)
@Suite.SuiteClasses({
ParameterTest.class,
AccountLoginControllerTest.class
})
public class MultipleTestClasses {
}
2.4 Matcher机制(hamcrest)
2.5 Rule,不经常使用
常用场景:类似Before,After等功能,灵活增加或者修改类中测试方法的行为。eg.在多个测试类中都需要连接,关闭数据库;日志输出;变相支持多个RunWith (https://github.com/junit-team/junit4/wiki/Rules)
pom.xml包依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
3.Mockito
重要的思想:理解mock/stub的基本理念,具体要解决的问题。使用最小化的资源验证代码逻辑是否符合预期,减少对数据库的连接,减少对配置文件的读写,摆脱启动时对框架的依赖。
底层原理简单介绍:
关注点:org.mockito.Mockito 该类包含了大量经常使用的函数, org.mockito.ArgumentMatchers该类抽象出来方法的各种参数
3.1框架启动常见的几种方式
1第一种 @RunWith(MockitoJUnitRunner.class)
2第二种
@Before
public void init(){
MockitoAnnotations.initMocks(this);
}
3.2常用注解和函数
@Mock(answer = Answers.RETURNS_SMART_NULLS)
when().thenReturn()
when().thenThrow()
when().thenAnswer()
when().thenCallRealMethod()
3.4 spy
会真实执行
List<String> realList = new ArrayList<>();
List<String > list = spy(realList);
list.add("Mockito");
assertThat(list.get(0), equalTo("Mockito"));
3.5 verify
模拟验证方法是否执行过以及执行过的次数
doNothing().when(list).clear();
list.clear();
verify(list, times(1)).clear();
3.6 answer
如果在方法调用中需要固定的返回值,则应使用thenReturn(…)。 如果需要执行某些操作或需要在运行时计算值,则应使用thenAnswer(…)
private List<String> list;
@Before
public void init(){
this.list = mock(ArrayList.class);
}
@Test
public void stubbingWithAnswer(){
when(list.get(anyInt())).thenAnswer(invocationOnMock ->{
Integer index = invocationOnMock.getArgument(0);
return String.valueOf(index * 10);
});
assertThat(list.get(0), equalTo("0"));
assertThat(list.get(999), equalTo("9990"));
}
3.7 pom.xml包依赖
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.8.25</version>
<scope>test</scope>
</dependency>
4.Powermock的功能
概念:扩展Mockito的功能,增加mock final, static, 局部变量和private方法
关注点:org.powermock.api.mockito.PowerMockito类
常用的方法:
mockStatic模拟静态方法
whenNew 模拟局部变量时可以使用
注解
@RunWith(PowerMockRunner.class)
@PrepareForTest()
4.1 常用示例
4.1.1 mock局部变量
UserService类中测试queryUserCount方法
public int queryUserCount(){
UserDao userDao = new UserDao();
int remainCnt = 10; //虚拟额外的逻辑
return userDao.getCount() + remainCnt;
}
核心测试代码如下:
UserDao userDao = PowerMockito.mock(UserDao.class);
PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDao);
PowerMockito.when(userDao.getCount()).thenReturn(10);
UserService service = new UserService();
int result = service.queryUserCount();
assertEquals(20, result);
其中PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDao)会以局部变量对象注入到queryUserCount()方法中,另外这里的userDao是mock出来的,不能用new的方法创建。
4.1.2 模拟静态方法
public int queryUserCount(){
return UserDao.getCount(); // 调用的静态getCount方法
}
核心测试代码如下:
mockStatic(UserDao.class);
when(UserDao.getCount()).thenReturn(10);
注意mockStatic传入的是.class对应调用的方法是PowerMockito.mockStatic(Class<?> type, Class<?>... types)
4.1.3final修饰的class
final public class UserDao{}
使用mockito模拟静态类
@Mock
UserDao userDao;
@Test
public void testQueryUserCountWithMockito() throws Exception {
MockitoAnnotations.initMocks(this);
when(userDao.getCount()).thenReturn(10);
UserService userService = new UserService(userDao);
int result = userService.queryUserCount();
assertThat(10, equalTo(result));
}
会出现
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock/spy because :
- final class
修改
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class, UserDao.class})
pom.xml中包依赖
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>${powermock-version}</version>
<scope>test</scope>
</dependency>
参考链接:
代码地址(github) https://github.com/ybyao07/learnJavaUTest.git
junit4官网 https://junit.org/junit4/
mockito官网 https://site.mockito.org/
powermock 官网 https://powermock.github.io/
junit4内部依赖(hamcrest) https://hamcrest.org/
mockito内部依赖包 http://objenesis.org
学习网站:https://www.baeldung.com/
教程:https://www.tutorialspoint.com/mockito/mockito_quick_guide.htm
单元测试体系图
junit4需要了解的知识
image.png
mockito,powermock需要了解的知识
image.png
后续
2017年junit5的出现将单元测试框架整个改版,后续发展方向应该是Junit5 + Mockito4(涵盖了mockito-line,可以模拟静态,final等),powermock将会退出历史舞台(3年没人维护了,还有421个issues和17个Pull requests)。
behavior testing(行为测试): concordion, cucumber