Java单元测试实践-00.目录(9万多字文档+700多测试示例)
https://blog.csdn.net/a82514921/article/details/107969340
1. Mockito与PowerMock的功能
以下针对使用Mockito与PowerMock作为单元测试Mock框架进行说明。
1.1. Mockito
参考 https://github.com/mockito/mockito/wiki/FAQ 。
Mockito是一个Java的Mock框架。支持对类或接口进行Mock,不支持对静态方法、私有方法、构造函数等进行Mock。
1.2. PowerMock
参考 https://github.com/powermock/powermock/blob/release/2.x/README.md 。
PowerMock是一个Mock框架,以更强大的功能扩展了其他Mock库。PowerMock使用自定义的类加载器和字节码操作来对静态方法、构造函数、final类和方法、私有方法等进行Mock。PowerMock旨在通过少量方法和注解扩展现有的API,以启用额外的功能。目前,PowerMock支持EasyMock和Mockito。
PowerMock示例可查看: https://github.com/powermock/powermock-examples-maven 。
2. 添加引用
2.1. 引用Mockito
参考“Declaring mockito dependency”( https://github.com/mockito/mockito/wiki/Declaring-mockito-dependency )。
可通过以下方式引用Mockito:
"org.mockito:mockito-core:1.+"
Mockito版本已更新至2.*与3.*,可使用新版本。
2.2. 引用PowerMock
参考“Mockito 2 Maven”( https://github.com/powermock/powermock/wiki/Mockito-2-Maven )。
对于使用Mockito 2.x,JUnit 4.4及以上版本时,通过以下方式引用PowerMock:
"org.powermock:powermock-module-junit4:2.0.4",
"org.powermock:powermock-api-mockito2:2.0.4"
3. PowerMock对Mockito的支持版本
参考“Supported versions”( https://github.com/powermock/powermock/wiki/Mockito#supported-versions )。
PowerMock的2.0.0及更高版本支持Mockito 2。具体的版本支持关系可查看对应的表格。
4. 基本配置
在使用PowerMock时,通常会使用以下公共配置(@ContextConfiguration是spring-test的配置):
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@PowerMockIgnore({"javax.management.*", "javax.net.ssl.*", "javax.crypto.*", "javax.security.*", "javax.script.*"})
4.1. @RunWith(PowerMockRunner.class)
参考“Introduction”( https://github.com/powermock/powermock/wiki/mockito#introduction )。
PowerMock提供了一个名为“PowerMockito”的类,用于创建模拟/对象/类并启动验证和期望。
所有的用法都需要在类级别使用@RunWith(PowerMockRunner.class)和@PrepareForTest注解。
4.2. @PrepareForTest注解
参考“Annotation Type PrepareForTest”( https://javadoc.io/doc/org.powermock/powermock-core/latest/org/powermock/core/classloader/annotations/PrepareForTest.html )。
@PrepareForTest注解告诉PowerMock需要准备哪些类进行测试。需要使用此注解定义的类通常是需要操作字节码的类。包括final类,具有final、private、static或native方法的类,以及应在实例化时返回模拟对象的类。
@PrepareForTest注解可以添加在测试类或单个测试方法上。如果设置在一个类上,则该测试类中的所有测试方法都将由PowerMock处理,以实现可测试性。如果需要覆盖单个方法的行为,只需在特定的测试方法上设置@PrepareForTest注解。例如,在测试方法f1()中修改类C但在测试方法f2()中希望保持C不变的情况下,可以在方法f2()上设置一个@PrepareForTest注解,并从value()列表中排除类C。
有时测试时需要使用@PrepareForTest注解对内部类进行准备,可以通过提供内部类的完整名称来完成,该名称应被添加到fullyQualifiedNames()列表中。
还可以使用通配符准备整个程序包以进行测试,示例如下:
@PrepareForTest(fullyQualifiedNames = "com.mypackage.*")
@PrepareForTest注解可以添加在测试类,或测试类的超类中,子类与超类中的配置不会相互覆盖。
例如在子类中通过@PrepareForTest注解指定了A.class,在超类中指定了B.class,则A.class与B.class均会被@PrepareForTest注解处理。
可参考示例TestPrepareForTestChild、TestPrepareForTestParent类。
4.3. @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
参考“JUnit_Delegating_Runner”( https://github.com/powermock/powermock/wiki/junit_delegating_runner )。
使用@PowerMockRunnerDelegate注解,从1.6.0版开始,PowerMock支持将测试执行委托给另一个JUnit运行程序,而无需使用JUnit规则。这会将实际的测试执行留给选择的其他runner。例如,测试可以委托给“SpringJUnit4ClassRunner”,“Parameterized”或“Enclosed”等runner。
4.4. @PowerMockIgnore
参考“Global @PowerMockIgnore”( https://github.com/powermock/powermock/wiki/PowerMock-Configuration#global-powermockignore )。
默认情况下,PowerMock使用其MockClassLoader加载所有类。该类加载器加载并修改了所有类,除了以下情况:
- 系统类。它们被延期到系统类加载器
- 位于指定为忽略的程序包中的类
可以使用@PowerMockIgnore注解指定需要忽略的包,避免被PowerMock加载。
例如在调用加解密相关代码时,需要通过@PowerMockIgnore({“javax.crypto.*”})忽略上述包,否则会出现以下异常( 使用RSA私钥加密 ):
java.lang.ClassCastException: com.sun.crypto.provider.RSACipher cannot be cast to javax.crypto.CipherSpi
在调用HTTPS相关代码时,需要通过@PowerMockIgnore({“javax.security.*”})忽略上述包,否则会出现以下异常:
java.lang.LinkageError: loader constraint violation: loader (instance of org/powermock/core/classloader/javassist/JavassistMockClassLoader) previously initiated loading for a different type with name "javax/security/auth/x500/X500Principal"
若不忽略“javax.script.*”对应的包,在执行单元测试时可能会出现以下日志:
ScriptEngineManager providers.next(): javax.script.ScriptEngineFactory: Provider jdk.nashorn.api.scripting.NashornScriptEngineFactory not a subtype
5. Mock框架依赖组件、版本差别等
5.1. 依赖组件的影响
5.1.1. javassist
当在项目中添加groupId为“javassist”的javassist依赖时,会导致单元测试程序无法正常启动。
例如添加对“javassist:javassist:3.12.1.GA”的依赖,在启动单元测试程序时会出现以下异常:
java.lang.NoSuchMethodError: javassist.CtClass.getDeclaredClasses()[Ljavassist/CtClass;
at org.powermock.core.transformers.javassist.ConstructorsMockTransformer.transform(ConstructorsMockTransformer.java:50)
查看项目依赖的组件,同时依赖了“org.javassist:javassist:3.x.x-GA”与“javassist:javassist:3.12.1.GA”,前者由“org.powermock:powermock-core:2.x.x”间接依赖。
javassist从“3.12.1.GA”之后的版本“3.13.0-GA”开始,groupId由“javassist”变为“org.javassist”。
去除项目中对“javassist:javassist”的依赖后,单元测试程序启动正常。
5.2. Mockito 2与Mockito 1版本的区别
在 https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#incompatible-changes-with-110 中,介绍了Mockito 2与Mockito 1.10版本不兼容的内容,其中常用的功能如下:
- InvocationOnMock
在Mockito 1中,在Answer中使用的InvocationOnMock接口的实现类为InvocationImpl类,获取单个参数的方法为getArgumentAt();
在Mockito 2及以上版本中,InvocationOnMock接口的实现类为InterceptedInvocation类,获取单个参数的方法为getArgument()。
可参考示例TestCommonUtil类的getMockArg()方法,针对以上方法进行了兼容处理。
- Mockito.any…()等方法是否支持null
对于Mockito.any…()及Mockito.any(SomeType.class)方法,在Mockito 1中支持null,在Mockito 2及以上版本中不支持null。
- Mock/Spy对象的代理类名标志
Mock/Spy对象的代理类名标志,在Mockito 1中示例如下:
$$EnhancerByMockitoWithCGLIB$$96437470
在Mockito 2及以上版本中示例如下:
$MockitoMock$1151489917
- Matchers/ArgumentMatcher类
在Mockito 1中,Mockito类继承自Matchers类;matches方法声明为“public abstract boolean matches(Object argument)”,在Matchers类中;isNotNull方法声明为“public static Object isNotNull()”;
在Mockito 2及以上版本中,Mockito类不继承自Matchers类,与Matchers类均继承自ArgumentMatchers类;matches方法声明为“public static String matches(Pattern pattern)”或“public static String matches(String regex)”,在ArgumentMatchers类中;isNotNull方法声明为“public static <T> T isNotNull()”。
5.3. PowerMock不同版本的区别
- 需要添加的依赖组件不同
当使用Mockito 1版本时,需要添加的PowerMock依赖为“org.powermock:powermock-api-mockito”;当使用Mockito 2及以上版本时,需要添加的依赖为“org.powermock:powermock-api-mockito2”。
6. Mock与Stub
参考“Mocks Aren’t Stubs”( https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs )。Mock是用期望进行预编程的对象,这些对象构成了期望接收的调用的规范。Stub提供对测试过程中进行的调用的固定答复,通常不会对测试编程之外的内容进行响应。
参考“2. How about some stubbing?”( https://static.javadoc.io/org.mockito/mockito-core/latest/org/mockito/Mockito.html#stubbing )。Mock操作对应Mockito.mock()方法,Stub操作对应Mockito.when().thenReturn()/thenThrow()等方法。当方法被Stub后,将始终返回Stub的值,无论被调用多少次。