近期,公司为了提高代码开发质量,减少线上bug出现概率 ,要求开发人员引入单元测试以及sonrQube检测,对代码质量进行检测与测试。为了响应公司的要求,遂开始了学习单元测试之旅。
初识单元测试
1.什么叫做单元测试
单元测试是对呈现代码单元进行函数级别测试,是面向最小软件设计单元的验证工作。
他是软件最小组成单位的测试,是软件开发过程中的最基本的测试。
它处于软件开发过程中实施的最低级别的测试活动,即检查单元程序模块有无错误。
它是在编码完成后必须进行的测试工作,也可称之为模块测试。
2.为什么要做单元测试?
单元测试的目的,是将应用程序的所有源代码,隔离成最小的可测试的单元,保证每个单元的正确性。理想情况下,如果每个单元都能保证正确,就能保证应用程序整体相当程度的正确性。
另一方面,单元测试也是一种特殊类型的文档,相对于书面的文档,测试脚本本身往往就是对被测试代码的实际的使用代码,对于帮助开发人员理解被测试单元的使用是相当有帮助。
3.单元测试原则
需要验证的方法、函数体都可以写单元测试
单元测试只验证测试方法的逻辑结果是否能否满足期望返回值
单元测试一般测试当前方法的逻辑,不测试被依赖的类方法、属性、字段或自己的私有方法
4.单元测试框架选取
Java相关的单元测试框架还是很多的,常见的有junit、mockito、Jmockito、easyMock、powerMockito等,这些框架在mock方面都具有比较强大的功能与比较广泛的使用量。本文采用powerMockito框架进行单元测试。
powerMockito是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架,PowerMock旨在用少量的方法和注解扩展现有的API来实现额外的功能。目前PowerMock支持EasyMock和Mockito。
实战
了解单元测试和powerMockito之后,我们开始着手搭建单元测试(PowerMockito)相关环境
单元测试框架:Junit+Mockito+PowerMockito搭配使用
1.引入依赖包
<!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.4.9</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
2.安装Junit插件
本文采用开发IDE为IntelliJ IDEA,通过File ->settings(或者使用快捷键Ctrl + Alt + S)打开配置界面,找到Plugins插件管理,搜索junit后,勾选安装junit,如下图所示:

安装完成后,重新进入settings界面,选择junit配置,选择“junit4”,配置junit生成的test文件路径:${SOURCEPATH}/../../test/java/${PACKAGE}/${FILENAME}

3.准备测试代码
public class GetRollBackReasonType extends ServiceBase {private List<Map<String,Object>> list;protected String getsql() {String sql = "";if (CommUtil.isCommMySqlType()) {sql = "call proc_queryRollBackReasonType";} else {sql = "EXEC proc_queryRollBackReasonType";}log.info("获取退单原因SQL:"+sql);return sql;}@Overrideprotected boolean handle() {try {list = jdbcTemplate.queryForList(getsql());addOutput("data",list);return true;}catch (Exception ex){log.info("查询数据库失败," + ex);}return false;}}
4.生成Test代码文件
在被测试代码类中,使用快捷键(Alt+Insert)出现提示框,快速添加对应的Test文件。


5.单元测试模拟解析
/*** GetRollBackReasonType Tester.** @author <Authors name>* @since <pre>六月 20, 2019</pre>* @version 1.0*/// powerMockito特殊注解,使用必须添加@RunWith(PowerMockRunner.class)//powerMockito特殊注解 当你需要使用PowerMock强大功能(Mock静态、final、私有方法等)的时候,就需要加注解@PrepareForTest。@PrepareForTest({CommUtil.class,GetRollBackReasonType.class})//忽略打桩时产生异常@PowerMockIgnore("javax.management.*")public class GetRollBackReasonTypeTest {private GetRollBackReasonType rollBackReasonType;//得到JdbcTemplae对象private JdbcTemplate jdbcTemplate;@Beforepublic void before() throws Exception {rollBackReasonType = new GetRollBackReasonType();}@Afterpublic void after() throws Exception {}/**** Method: getsql()**/@Testpublic void testGetsql() throws Exception {//powerMockito 模拟静态类CommUtilPowerMockito.mockStatic(CommUtil.class);//模拟CommUtil.isCommMySqlType()返回为truePowerMockito.when(CommUtil.isCommMySqlType()).thenReturn(true);//模拟PowerMockito.spy(rollBackReasonType) 实例化rollBackReasonType对象,这是实际存在的对象GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);//Assert断言 用于判断代码方法返回值是否与预期相同Assert.assertEquals("call proc_queryRollBackReasonType",temp.getsql());//验证静态方法调用次数PowerMockito.verifyStatic(Mockito.times(1));}/**** Method: output()**/@Testpublic void testHandle() throws Exception {//powerMockito 模拟 JdbcTemplate对象JdbcTemplate jdbcTemplate = PowerMockito.mock(JdbcTemplate.class);//调用jdbcTemplate.queryForList时模拟返回空的集合PowerMockito.when(jdbcTemplate.queryForList(Matchers.anyString())).thenReturn(new ArrayList<>());//实例化GetRollBackReasonType的一个对象GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);//模拟GetRollBackReasonType私有变量jdbcTemplate的值PowerMockito.field(temp.getClass(),"jdbcTemplate").set(temp,jdbcTemplate);//模拟GetRollBackReasonType父类私有变量outputMap的值PowerMockito.field(temp.getClass(),"outputMap").set(temp,new HashMap<>());//模拟GetRollBackReasonType私有方法addOutput的结果PowerMockito.method(temp.getClass(),"addOutput").invoke(temp,Matchers.anyString(),Matchers.anyObject());//断言返回结果是否成功Assert.assertTrue(temp.handle());}@Testpublic void testHandle1(){try {//手动加载spring bean 配置文件ApplicationContext context = new ClassPathXmlApplicationContext("spring-servlet.xml");//实例化JdbcTemplate对象jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate");//调用jdbcTemplate.queryForList时模拟返回空的集合PowerMockito.when(jdbcTemplate.queryForList(Matchers.anyString())).thenReturn(new ArrayList<>());//实例化GetRollBackReasonType的一个对象GetRollBackReasonType temp = PowerMockito.spy(rollBackReasonType);//模拟GetRollBackReasonType私有变量jdbcTemplate的值PowerMockito.field(temp.getClass(),"jdbcTemplate").set(temp,jdbcTemplate);//模拟GetRollBackReasonType父类私有变量outputMap的值PowerMockito.field(temp.getClass(),"outputMap").set(temp,new HashMap<>());//模拟GetRollBackReasonType私有方法addOutput的结果PowerMockito.method(temp.getClass(),"addOutput").invoke(temp,Matchers.anyString(),Matchers.anyObject());//断言返回结果是否成功Assert.assertTrue(temp.handle());}catch (Exception ex){System.out.println("数据库执行出错..." + ex);}}
以上代码运用了PowerMockito模拟普通对象、模拟静态类方法、模拟私有变量等手段来解决被测试方法的外部依赖,使得被测方法的逻辑不受外部影响,达到充分测试的效果。
除了以上的使用的模拟情况,powerMockito还支持:
模拟void方法
@Testpublic void testMockVoidMethod() {Hello hello = new Hello();Kitty kitty = PowerMockito.mock(Kitty.class);hello.setKitty(kitty);PowerMockito.doNothing().when(kitty).say3();hello.mockVoidMethod();}
模拟普通private方法
public class MockPrivateClass {private String privateFunc() {return "private func";}}@Testpublic void testPrivateFunc() throws Exception {MockPrivateClass privateClass = PowerMockito.spy(new MockPrivateClass());PowerMockito.when(privateClass, "privateFunc").thenReturn("test");}@Testpublic void testPrivateMethod() {MockPrivateClass mockPrivateClass = new MockPrivateClass ();PowerMockito.verifyPrivate(ockPrivateClass ).invoke("privateFunc");}
模拟构造方法
GetRollBackReasonType type= PowerMockito.mock(GetRollBackReasonType .class)PowerMockito.whenNew(type.class).thenReturn(type);
总结
单元测试是每个开发人员都应该掌握的职业技能,它能使我们的代码质量显著提升,减少线上bug产生,提高产品质量。而使用PowerMockito作为单元测试框架,简单易懂语法,能够有效帮助开发人员使用。




