wiki了一下hamcrest和项目的tutorial,发现是个很不错的小框架。
从历史说起的话,第一代单元测试框架提供了断言表达式
assert(x == y)
但是这样对错误信息很不友好,于是第二代单元测试框架提供了一组测试语句
assert_equal(x , y)
assert_not_equal(x, y)
这样对测试是友好了,但是测试语句的数量暴涨
于是第三代单元测试框架就使用如hamcrest的库来支持assert_that的操作
assert_that(x, equals(y))
assert_that(x, is_not(equals(y)))
这样就能优美的输出错误信息。并且通过组合不同的匹配器,可以获得极大的拓展性。
匹配器封装了一种匹配操作,可以是简单的equals操作,也可以是原来几种封装器的再封装,譬如
Matcher a = equals(10)
Matcher b = greatThan(0)
Matcher c = allOf(a, b)
Matcher a1 = equals(-20)
Matcher b1 = lessThan(0)
Matcher c1 = allOf(a1, b1)
Matcher d = anyOf(c, c1)
a, b, a1, b1 就是比较简单的匹配器,c,c1,d就是通过其他封装器组合而成的。
我用的junit-4.10.jar已经集成了hamcrest。至于具体是用独立的hamcrest,还是junit集成版的,后面用用看喽。
简单的过一遍junit的文档,记录一些自己不熟悉的东西。
从4.11版本开始,可以指定固定的method顺序了。但是仍然不能指定test执行的顺序。目前只有两种固定顺序。 通过给测试类制定@FixMethodOrder
@FixMethodOrder(MethodSorters.JVM): 返回jvm给的方法顺序,所以每次跑的顺序可能不一样。
@FixMethodOrder(MethodSorters.NAME_ASCENDING): Sorts the test methods by method name, in lexicographic order.
以前从没用过也不知道Rule这个annonation。
Rule是用来为了给一个类中所有的测试增加某种功能的。比如说ExternalResource这个类,如果希望测试类里面的每一个测试都能申请到某数据库连接,然后在测试完之后释放这个连接的话,就可以自己实现一个ExternalResource类来完成。这样就能够不使用Before和After了。
比较带感的是:
TemporaryFolder 能够为测试创建临时文件或文件夹,测试函数结束后会删除
ExternalResource 抽象类,实现了before和after之后,就能在测试函数前后执行这两个函数了
ErrorCollector 收集throwble,然后完成测试后一次性报告所有的error
TestWatcher 主要是测试过程记录作用,starting finished功能跟Before After相近,成功的case会进入successed,失败的case会进入failed,还有一个apply函数,暂时不知道是干什么的。
TimeOutRule 类如其名
还有就是自定义的了
为了保证程序和预期的那样抛出异常,所以要有异常测试。
如果是简单的小测试,可以用annotation参数来完成。
@Test(expected= IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
还有一种方法就是用上面提到的Rule, ExpectedException类
除了junit自带的和hamcrest自带的,还有一些第三方的
加上ignore annonation就行了
@Ignore("Test is ignored as a demonstration")
前面讲到Rule的时候有说道,有TimeOut rule,这个是对一个测试类里面的所有test适用的,还有一个对单个test作用的timeout的设置
@Test(timeout=1000)
给出一篇文章里面的测试方法, assertConcurrent,来源在assertConcurrent,完整的实现不长,这里可以大致说说实现的方法。
public static void assertConcurrent(final String message, final List<? extends Runnable> runnables, final int maxTimeoutSeconds) throws InterruptedException {
final int numThreads = runnables.size();
final List<Throwable> exceptions = Collections.synchronizedList(new ArrayList<Throwable>());
final ExecutorService threadPool = Executors.newFixedThreadPool(numThreads);
try {
final CountDownLatch allExecutorThreadsReady = new CountDownLatch(numThreads);
final CountDownLatch afterInitBlocker = new CountDownLatch(1);
final CountDownLatch allDone = new CountDownLatch(numThreads);
for (final Runnable submittedTestRunnable : runnables) {
threadPool.submit(new Runnable() {
public void run() {
allExecutorThreadsReady.countDown();
try {
afterInitBlocker.await();
submittedTestRunnable.run();
} catch (final Throwable e) {
exceptions.add(e);
} finally {
allDone.countDown();
}
}
});
}
// wait until all threads are ready
assertTrue("Timeout initializing threads! Perform long lasting initializations before passing runnables to assertConcurrent", allExecutorThreadsReady.await(runnables.size() * 10, TimeUnit.MILLISECONDS));
// start all test runners
afterInitBlocker.countDown();
assertTrue(message +" timeout! More than" + maxTimeoutSeconds + "seconds", allDone.await(maxTimeoutSeconds, TimeUnit.SECONDS));
} finally {
threadPool.shutdownNow();
}
assertTrue(message + "failed with exception(s)" + exceptions, exceptions.isEmpty());
}
大致讲解下原理,首先CountDownLatch是java concurrent包里面的一个同步工具,用一个整数初始化,初始化之后调用await()是会持续block那里的,通过另外的线程不断的调用countDown,调用输入整数个countDown之后,之前block在await的线程会继续运行。
上面的代码建立了一个线程池,把输入的Runnable封装成Runnalbe提交到线程池里,同时保证所有的线程都不在运行,记录有多少Runnable已经被提交了,有多少Runnable已经完成了。
然后assert所有的线程在规定时间内都提交完成了。开始启动这些线程,再最后确保这些线程在规定时间内完成。当然,运行过程中所产生的exception都被记录在一个list中,最后assert有没有产生excpetion。
基本上完成了测试的需求,美中不足的是如果有timeout或者exeption,单从测试的结果很难定位timeout或者exception的runnalbe。有改进的空间。
The tests are run automatically after every change, usually in an intelligent order so that newest tests or most recently failed tests are run first。
看起来很不错。有个叫infinitest(无尽的测试.....好冷)。可以放到intellij idea上面运行。
至于infinitest的使用可以放到后面一个博客上面写