为什么要写单元测试
- 优点:单元测试可以减少bug率,提升代码的质量。还可以通过单元测试来熟悉业务。
- 公司硬性要求:有些公司可能还会强制要求,每次新增代码、或者变更代码单测覆盖率要达到多少比例才能申请代码合并请求。
选择哪个单元测试框架
目前应用比较普遍的java单元测试工具 junit4+Mock(Mockito、jmock、EasyMock、powermock)。为什么会选择powermock? 在做单元测试的时候,我们会发现我们要测试的方法会有很多外部依赖的对象或者一些其他服务的调用比如说(发送邮件,网络通讯,soa调用)。 而我们没法控制这些外部依赖的对象。 为了解决这个问题,我们需要用到Mock来模拟这些外部依赖的对象,从而控制它们。只关心我们自己的业务逻辑是否正确。而这时powermock就起作用了,它不仅可以mock外部的依赖,还可以mock私有方法、final方法,总之它的功能很强大。
什么是powerMocker
PowerMock是一个框架,它以更强大的功能扩展了其他模拟库,例如EasyMock。 PowerMock使用自定义的类加载器和字节码操作来模拟静态方法,构造函数, 最终类和方法,私有方法,删除静态初始化程序等。通过使用自定义类加载器,无需对IDE或持续集成服务器进行任何更改,从而简化了采用过程。熟悉受支持的模拟框架的开发人员会发现PowerMock易于使用,因为整个期望API都是相同的, 无论是静态方法还是构造函数。PowerMock 旨在通过少量方法和注释扩展现有的API,以启用额外的功能。
常用注解
- @RunWith(PowerMockRunner.class) 告诉JUnit使用PowerMockRunner进行测试
- @PrepareForTest({DemoDao.class}) 所有需要测试的类列在此处,适用于模拟final类或有final, private, static, native方法的类
- @PowerMockIgnore({"javax.management.", "javax.net.ssl."}) 为了解决使用powermock后,提示classloader错误
- @SuppressStaticInitializationFor 不让静态代码加载 其他更多注解可以参考:https://github.com/powermock/powermock/wiki/Suppress-Unwanted-Behavior
如何开始
JUnit 4.4及以上
<properties> <powermock.version>2.0.2</powermock.version> </properties> <dependencies> <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-api-mockito2</artifactId> <version>${powermock.version}</version> <scope>test</scope> </dependency> </dependencies>
powerMock样例
这是一个需要被mock的类里面有私有方法、静态方法、等等下面一一来演示各个方法的mock功能。
/** * * @Date: 2020/3/31 * @Description: */ @Repository public class DemoDao { public String mockPublicMethod(String type) throws Throwable { throw new Throwable(); } public final String mockFinalMethod(String type) throws Throwable { throw new Throwable(); } public static String mockStaticMethod(String type) throws Throwable { throw new Throwable(); } } /** * @Date: 2020/3/31 11:34 * @Description: */ @Component public class DemoService extends AbstractDemo{ @Autowired private DemoDao demoDao; public String mockPublicMethod() throws Throwable { return demoDao.mockPublicMethod("demo"); } public String mockFinalMethod() throws Throwable { return demoDao.mockFinalMethod("demo"); } public String mockStaticMethod() throws Throwable { return DemoDao.mockStaticMethod("demo"); } private String callPrivateMethod(String type) { return type; } public String mockPublicMethodCallPrivateMethod(String type) throws Throwable { return callPrivateMethodThrowable(type); } private String callPrivateMethodThrowable(String type) throws Throwable { throw new Throwable(); } public String mockExtendMethod(String type) throws Throwable { return getExtendMethod(); } public static String UUID = "uuid"; }
mock普通公共方法
/** * @Date: 2020/4/24 14:22 * @Description: */ @RunWith(PowerMockRunner.class) public class DemoServiceTest { @InjectMocks private DemoService demoService; @Mock private DemoDao demoDao; /** * mock 普通方法 * @throws Throwable */ @Test public void mockPublicMethod() throws Throwable { String type = UUID.randomUUID().toString(); PowerMockito.when(demoDao.mockPublicMethod(any())).thenReturn(type); String result = demoService.mockPublicMethod(); Assert.assertEquals(type, result); } }
mock Final方法
跟普通方法是一样的,唯一的区别是需要在类上加入PrepareForTest注解
@RunWith(PowerMockRunner.class) @PrepareForTest(DemoDao.class) public class DemoServiceTest { @InjectMocks private DemoService demoService; @Mock private DemoDao demoDao; /** * mock final方法 * @throws Throwable */ @Test public void mockFinalMethod() throws Throwable { String type = UUID.randomUUID().toString(); PowerMockito.when(demoDao.mockFinalMethod(any())).thenReturn(type); String result = demoService.mockFinalMethod(); Assert.assertEquals(type, result); } }
mock静态方法
(使用 PowerMockito.mockStatic)被mock的类也要用PrepareForTest注解修饰。
@RunWith(PowerMockRunner.class) @PrepareForTest(DemoDao.class) public class DemoServiceTest { @InjectMocks private DemoService demoService; @Mock private DemoDao demoDao; /** * mock 静态方法 * @throws Throwable */ @Test public void mockStaticMethod() throws Throwable { String type = UUID.randomUUID().toString(); PowerMockito.mockStatic(DemoDao.class); PowerMockito.when(DemoDao.mockStaticMethod(any())).thenReturn(type); String result = demoService.mockStaticMethod(); Assert.assertEquals(type, result); } }
调用 private方法
/** * 调用私有方法 * * @throws Throwable */ @Test public void callPrivateMethod() throws Throwable { // 第一种方式 String type = UUID.randomUUID().toString(); Method method = PowerMockito.method(DemoService.class, "callPrivateMethod", String.class); String result = (String) method.invoke(demoService, type); Assert.assertEquals(type, result); //第二种方式 String result1 = Whitebox.invokeMethod(demoService, "callPrivateMethod", type); Assert.assertEquals(type, result1); }
mock 私有方法
(被mock的类也要用PrepareForTest注解修饰。)
/** * mock私有方法 * @throws Throwable */ @Test public void mockPrivateMethod() throws Throwable { String type = UUID.randomUUID().toString(); // 重点这一句 demoService = PowerMockito.spy(demoService); PowerMockito.doReturn(type).when(demoService,"callPrivateMethodThrowable",type); String result = demoService.mockPublicMethodCallPrivateMethod(type); Assert.assertEquals(type, result); }
mock父类方法
/** * mock父类方法 * @throws Throwable */ @Test public void mockExtendMethod() throws Throwable { String type = UUID.randomUUID().toString(); // 需要mock的父类的方法 Method method = PowerMockito.method(AbstractDemo.class, "getExtendMethod"); // InvocationHandler PowerMockito.replace(method).with((proxy, method1, args) -> type); String result = demoService.mockExtendMethod(type); Assert.assertEquals(type, result); }
mock构造方法
public DemoService() { throw new NullPointerException(); } @Test public void mockConstructorMethod() throws Throwable { PowerMockito.whenNew(DemoService.class).withNoArguments().thenReturn(demoService); }
mock字段
/** * mock 字段 */ @Test public void mockFiled(){ String uuid = UUID.randomUUID().toString(); Whitebox.setInternalState(DemoService.class, "UUID",uuid); Assert.assertEquals(DemoService.UUID, uuid); } //mock私有map Field field = CoreBookingDeliveryCache.class.getDeclaredField("coreBookingDeliveryDtoMap"); FieldSetter.setField(coreBookingDeliveryCache, field, builderCoreBookingDeliveryMap());