说在前面的话:
就像阿里规约里提到, 单元测试需要满足的AIR原则 :
- A:Automatic(自动化)
- I:Independent(独立性)
- R:Repeatable(可重复)
工欲善其事,必先利其器. 单元测试三剑客:
- TestNg:单元测试框架
- AssertJ:断言工具
- Jmockit:mock工具
TestNg
testNg是个unit test/sut框架, 支持很多功能。但本篇文章重点不是介绍testNg,简单提一下我觉得比较有用的功能
- Group:测试用例分组,根据业务/逻辑分组,以更小的粒度来执行case.
-
Parallel:并行执行,可以配置多个线程来执行case.需要注意并发问题.
<suite name="My suite" thread-count="10" parallel="classes">
<test name="mocking">
<packages>
<package name="com.hz.constantine.jmockit.slideshare.mocking"/>
</packages>
</test>
<test name="faking">
<packages>
<package name="com.hz.constantine.jmockit.slideshare.faking"/>
</packages>
</test>
<test name="testng">
<packages>
<package name="com.hz.constantine.jmockit.slideshare.testng"/>
</packages>
</test>
</suite>
- Listener:通过监听器集成自定义的功能
-
DataProvider:测试用例和测试数据分离
static final String expectData = DataProviderTest.class.getSimpleName();
static final class DataProvider{
@org.testng.annotations.DataProvider(name = "str")
public static Object[][] provide(){
return new Object[][]{{expectData}};
}
}
@Test(dataProvider = "str",dataProviderClass = DataProvider.class)
public void dataProviderTest(String data){
Assert.assertEquals(data,expectData);
}
-
Timeout/ExpectExceptions:支持用例执行超时 和 异常
@Test(timeOut = 5,expectedExceptions = {ThreadTimeoutException.class,NullPointerException.class})
public void timeout(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
Tips:附上TestNg Tutorial, http://testng.org/doc/documentation-main.html
TestNg 集成Jmockit
![image image]()
- Java instrumentation:开发者可以构建一个独立于应用程序的代理程序,监测和协助运行在JVM上的程序,虚拟机级别的AOP实现
- TestNg Listener: TestNg Listener特性提供开发者扩展TestNg的能力.
Jmockit
在jmockit的世界里,它提供两套不同的语法和api. 分别是mocking和faking.下面分别针对这两套做详细的说明
-1- mocking
注解
![image image]()
特性
- Expectations.代表一组调用关联到当期的case.
- Record-Replay-Verify Mode.
- Record: 预准备依赖和数据.
- Replay:执行业务.
- Verify:校验.
- RecordingResult
- FlexibleArgumentTest
- Delegate表达式
- Caputring: 用在Verification里.
- CasCadeMock: 级联Mock
其中最重要的是Record-Replay-Verify Mode ,三段式的表述方式。即
- Record: 记录,即定义数据、并mock相关的依赖.
- Replay:回放,即执行被测试的业务逻辑.
-
Verify:校验,即校验逻辑是否正确.
废话不多说,直接上代码:
@Test(expectedExceptions = MissingInvocation.class)
public void recordReplyVerifyNotInOrder() {
//Record
ClassUnderTest classUnderTestInstance = newClassUnderTestInstance();
final String data = this.getClass().getSimpleName();
new Expectations(classUnderTestInstance.getEye(), classUnderTestInstance.getRepository()) {
{
classUnderTestInstance.getEye().find();
result = data;
classUnderTestInstance.getRepository().insert(data);
times = 1;
}
};
//Replay
classUnderTestInstance.action();
//Verify
new VerificationsInOrder(){
{
classUnderTestInstance.getRepository().insert(data);
classUnderTestInstance.getEye().find();
}
};
}
其中 ClassUnderTest 是被测试类,依赖了Eye 和 Repository, 在Record阶段被定义了mocking.
其他特性的测试类,可以参看我的github: https://github.com/cscpswang/java-practice
-2- faking
- Faking
- unspecifiedFaking
- Invocation Context
第2个和第3个特性依然可以参看我的github,重点说明一下特性1:
1.定义一个被测试对象,一个echoServer(在io编程中,echoServer表示接受到任何消息不做处理,直接返回原消息)
class EchoServer {
private Dependency dependency=new Dependency();
public String echo(String msg) {
return msg;
}
public String run(){
return dependency.run();
}
}
2.定义一个依赖类,这个类会在单测中被faking.
class Dependency {
public String run(){
return "dependency run";
}
}
3.case,测试EchoServer.
public void applyFakesWithDependency(){
final String msg = "i'm constantine";
EchoServer echoServer = new EchoServer();
final class DependencyMock extends MockUp<Dependency> {
@Mock
public String run(){
return msg;
}
}
new DependencyMock();
String actualMsg = echoServer.run();
Assert.assertEquals(actualMsg,msg);
}
tips:
- spring 中使用faking api时泛型指定到具体的impl类.
- 被jdk 代理的类(如spring bean被aop),并不是实现类的实例. 所以要么基于接口mocking(需要mock接口的所有方法),要么使用faking.
小结:
Faking和Mocking是两套jmockit的api,在其官方文档,有下面一句话:
In particular, the use of both the Faking API and the Mocking API in the same test class should be viewed with suspicion, as it strongly indicates misuse.
![image image]()
我更偏爱使用Mocking api, 因为它的特性很酷。 但它不能mocking私有方法,这点有时会不太方便。
最后,如果你要下jmockit的源码,并希望研究,注意一个坑。由于jmockit基于java agent(instrumentation),你需要在你的classpath下放置一个jmockit-xxx.jar.