说在前面的话:
就像阿里规约里提到, 单元测试需要满足的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
- Java instrumentation:开发者可以构建一个独立于应用程序的代理程序,监测和协助运行在JVM上的程序,虚拟机级别的AOP实现
- TestNg Listener: TestNg Listener特性提供开发者扩展TestNg的能力.
Jmockit
在jmockit的世界里,它提供两套不同的语法和api. 分别是mocking和faking.下面分别针对这两套做详细的说明
-1- mocking
注解
特性
- 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.
我更偏爱使用Mocking api, 因为它的特性很酷。 但它不能mocking私有方法,这点有时会不太方便。
最后,如果你要下jmockit的源码,并希望研究,注意一个坑。由于jmockit基于java agent(instrumentation),你需要在你的classpath下放置一个jmockit-xxx.jar.