Vertx unit

异步多语言单元测试。

介绍

Vert.x Unit 用多语言的异步API在JVM上 运行单元测试。 Vert.x Unit 借鉴了已有的测试框架,例如 JUnit or QUnit 并同时遵循了Vert.x的做法。

所以 Vert.x Unit 自然就成为了测试 Vert.x 应用的选择。

要使用 Vert.x Unit, 那么请添加如下依赖:

  • Maven (在您的 pom.xml 文件):

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-unit</artifactId>
 <version>4.2.7</version>
 <scope>test</scope>
</dependency>
  • Gradle (在您的 build.gradle 文件):

testCompile ${io.vertx}:${vertx-unit}:4.2.7

Vert.x Unit 能够以各种方式使用并可以在您代码中任何位置运行,它仅以正确的方式报告执行结果。 以下示例展示了一个最简的测试用例套件。

TestSuite suite = TestSuite.create("the_test_suite");
suite.test("my_test_case", context -> {
  String s = "value";
  context.assertEquals("value", s);
});
suite.run();

run 方法执行了整个套件并且执行套件中所有的测试用例。 测试用例套件可能失败也可能通过。如果外界不关心测试结果, 那么测试用例的通过与否便不重要。

TestSuite suite = TestSuite.create("the_test_suite");
suite.test("my_test_case", context -> {
  String s = "value";
  context.assertEquals("value", s);
});
suite.run(new TestOptions().addReporter(new ReportOptions().setTo("console")));

执行之时,测试套件会在控制台报告每一步的结果:

Begin test suite the_test_suite
Begin test my_test
Passed my_test
End test suite the_test_suite , run: 1, Failures: 0, Errors: 0

reporters 选项配置了套件执行器的reporter,用以报告测试用例执行结果, 更多信息详见 [reporting] 章节

编写一个测试套件

测试套件是一系列测试用例组成的有名称的集合,测试用例则是一个直接回调。 套件生命周期中可以设置回调函数,这些函数可以在执行测试用例前后执行, 也可以在用作初始化/发布服务的套件前后。

TestSuite suite = TestSuite.create("the_test_suite");
suite.test("my_test_case_1", context -> {
  // Test 1
});
suite.test("my_test_case_2", context -> {
  // Test 2
});
suite.test("my_test_case_3", context -> {
  // Test 3
});

流式API让测试用例可以作链式调用:

TestSuite suite = TestSuite.create("the_test_suite");
suite.test("my_test_case_1", context -> {
  // Test 1
}).test("my_test_case_2", context -> {
  // Test 2
}).test("my_test_case_3", context -> {
  // Test 3
});

测试用例的声明顺序并不保证其执行顺序,所以测试用例不可以依赖于其他测试用例来运行。 这不是一个好的做法。

Vert.x Unit 提供了 before 和 after 回调来作总体的启动或清理资源操作:

TestSuite suite = TestSuite.create("the_test_suite");
suite.before(context -> {
  // 测试套件启动
}).test("my_test_case_1", context -> {
  // Test 1
}).test("my_test_case_2", context -> {
  // Test 2
}).test("my_test_case_3", context -> {
  // Test 3
}).after(context -> {
  // 测试套件清理资源
});

这些方法的声明顺序并不重要,示例中在用例之前声明 before , 在用例之后声明 after , 然而在运行套件之前,他们可以在任何位置声明,

before 回调函数执行于所有测试用例之前,如果其运行失败, 则整个测试套件将停止运行并报告失败结果。 after 回调函数在整个测试套件中最后执行, 除非 before 回调函数运行失败。

类似的,Vert.x Unit 提供了 beforeEach 和 afterEach 回调, 在每一个测试用例前后执行:

TestSuite suite = TestSuite.create("the_test_suite");
suite.beforeEach(context -> {
  // 测试用例启动
}).test("my_test_case_1", context -> {
  // Test 1
}).test("my_test_case_2", context -> {
  // Test 2
}).test("my_test_case_3", context -> {
  // Test 3
}).afterEach(context -> {
  // 测试用例资源清理
});

beforeEach 回调函数执行于每一个测试用例之前,一旦其运行失败,那测试用例将不再被执行, 并报告失败结果。 afterEach 回调函数仅在测试用例之后被执行, 除非 beforeEach 函数运行失败。

断言

Vert.x Unit 提供了 TestContext 对象用来在测试用例中作断言操作。 context 对象提供了用于断言的常用方法。

assertEquals

断言两个对象相等,适用于 基本类型 和 json类型 。

suite.test("my_test_case", context -> {
  context.assertEquals(10, callbackCount);
});

有一个重载的方法用于提供测试信息:

suite.test("my_test_case", context -> {
  context.assertEquals(10, callbackCount, "Should have been 10 instead of " + callbackCount);
});

通常地,每一个断言函数都提供了重载的版本。

assertNotEquals

与 assertEquals 相反。

suite.test("my_test_case", context -> {
  context.assertNotEquals(10, callbackCount);
});

assertNull

断言一个对象是null,适用于 基本类型 和 json类型 。

suite.test("my_test_case", context -> {
  context.assertNull(null);
});

assertNotNull

与 assertNull 相反。

suite.test("my_test_case", context -> {
  context.assertNotNull("not null!");
});

assertInRange

assertInRange 方法作用于实数范围。

suite.test("my_test_case", context -> {

  // 断言0.1 在误差范围 +/- 0.5范围内等于0.2

  context.assertInRange(0.1, 0.2, 0.5);
});

断言成功以及断言失败

对于布尔表达式的断言。

suite.test("my_test_case", context -> {
  context.assertTrue(var);
  context.assertFalse(value > 10);
});

失败

最后但并非最不重要的,test 提供了一个 fail 方法用来抛出断言错误的异常:

suite.test("my_test_case", context -> {
  context.fail("That should never happen");
  // 剩下的代码不会执行。
});

失败 对象本身既可以是 string 也可以是一个 error 。 error 对象取决于所使用的编程语言,对于 Java 或者 Groovy 而言,可以是任何 Throwable 的子类, 对于 JavaScript 则是一个 error ,对于 Ruby 则是一个 Exception。

使用第三方断言框架

您也可以用任何其他的断言框架,例如比较流行的 hamcrest 和 assertj 。 我们建议您用 verify , 使用我们提供的 处理器(Handler) 来执行断言。 这样的话,我们才能正确的处理异步测试的结束动作。

suite.test("my_test_case", context -> context.verify(v -> {
  // 这里 junit中的 Assert 对象可以是 assertj hamcrest或其他库中的Assert对象
  // 甚至可以手动抛出 AssertionError
  Assert.assertNotNull("not null!");
  Assert.assertEquals(10, callbackCount);
}));

异步测试

前述示例的前提是,假设所有的测试用例在各自回调之后会结束, 这是测试用例回调函数的默认行为。在测试用例回调函数之后结束测试是可取的, 例如:

Async 对象异步的完成测试用例
suite.test("my_test_case", context -> {
  Async async = context.async();
  eventBus.consumer("the-address", msg -> {
    (2)
    async.complete();
  });
  (1)
});
  1. 回调函数结束,但是测试用例没结束

  2. 总线事件的回调函数来终止测试

用 async 方法创建一个 Async 对象表示测试用例还未结束。 当执行 complete 方法时, 测试用例才算结束。

注意
当 complete 回调函数未被执行时,测试用例会在指定超时时间之后失败。

同一个测试用例中可以创建多个 Async 对象, 必须调用所有Async对象的 completed 方法来终止测试。

多个Async对象合作
suite.test("my_test_case", context -> {

  HttpClient client = vertx.createHttpClient();
  client.request(HttpMethod.GET, 8080, "localhost", "/", context.asyncAssertSuccess(req -> {
      req.send(context.asyncAssertSuccess(resp -> {
        context.assertEquals(200, resp.statusCode());
      }));
    }));

  Async async = context.async();
  vertx.eventBus().consumer("the-address", msg -> {
    async.complete();
  });
});

Async 对象也可以用在 before 或 after 回调中, 这将很方便的在 before 回调中,实现依赖于多个异步结果的启动操作:

执行测试用例之前异步启动 http server
suite.before(context -> {
  Async async = context.async();
  HttpServer server = vertx.createHttpServer();
  server.requestHandler(requestHandler);
  server.listen(8080, ar -> {
    context.assertTrue(ar.succeeded());
    async.complete();
  });
});

您也可以等待 Async 直到其结束, 类似Java的 count-down latch:

等待完成
Async async = context.async();
HttpServer server = vertx.createHttpServer();
server.requestHandler(requestHandler);
server.listen(8080, ar -> {
  context.assertTrue(ar.succeeded());
  async.complete();
});

// Wait until completion
async.awaitSuccess();
警告
该方法不应该在事件循环(event loop)上执行!

Async 可以创建一个初始计数值, 当用 countDown 方法将 count-down 的值减到0时:

等待,直到count-down值为0
Async async = context.async(2);
HttpServer server = vertx.createHttpServer();
server.requestHandler(requestHandler);
server.listen(8080, ar -> {
  context.assertTrue(ar.succeeded());
  async.countDown();
});

vertx.setTimer(1000, id -> {
  async.complete();
});

// Wait until completion of the timer and the http request
async.awaitSuccess();

调用Async的 complete() 方法会像往常一样结束,实际上它将count-down值直接设置为 0 。

异步断言

TestContext 提供了很有用的方法,这些方法提供了强大的异步测试框架:

asyncAssertSuccess 方法返回一个类似 Async {@literal Handler<AsyncResult<T>>} 对象, 它在运行成功时执行 Async 对象,并在失败时, 让整个测试用例失败,并返回失败原因。

Async async = context.async();
vertx.deployVerticle("my.verticle", ar -> {
  if (ar.succeeded()) {
    async.complete();
  } else {
    context.fail(ar.cause());
  }
});

// 可用如下代替

vertx.deployVerticle("my.verticle", context.asyncAssertSuccess());

asyncAssertSuccess 方法返回一个类似于 Async 的 {@literal Handler<AsyncResult<T>>} 对象, 运行成功时它执行了代理 {@literal Handler<T>} , 而在运行出错时让整个测试用例失败,并返回错误原因。

AtomicBoolean started = new AtomicBoolean();
Async async = context.async();
vertx.deployVerticle(new AbstractVerticle() {
  public void start() throws Exception {
    started.set(true);
  }
}, ar -> {
  if (ar.succeeded()) {
    context.assertTrue(started.get());
    async.complete();
  } else {
    context.fail(ar.cause());
  }
});

// Can be replaced by

vertx.deployVerticle("my.verticle", context.asyncAssertSuccess(id -> {
  context.assertTrue(started.get());
}));

当 Handler 退出时,Async对象同时也会处于 completed 状态,除非Async对象在函数调用期间被创建, 这会在 链式 异步操作时提供方便:

Async async = context.async();
vertx.deployVerticle("my.verticle", ar1 -> {
  if (ar1.succeeded()) {
    vertx.deployVerticle("my.otherverticle", ar2 -> {
      if (ar2.succeeded()) {
        async.complete();
      } else {
        context.fail(ar2.cause());
      }
    });
  } else {
    context.fail(ar1.cause());
  }
});

// Can be replaced by

vertx.deployVerticle("my.verticle", context.asyncAssertSuccess(id ->
        vertx.deployVerticle("my_otherverticle", context.asyncAssertSuccess())
));

asyncAssertFailure 方法返回了一个类似 Async 的 {@literal Handler<AsyncResult<T>>} 对象, 它在运行失败时执行 Async , 并在成功时让测试用例失败。

Async async = context.async();
vertx.deployVerticle("my.verticle", ar -> {
  if (ar.succeeded()) {
    context.fail();
  } else {
    async.complete();
  }
});

// 可用如下代替

vertx.deployVerticle("my.verticle", context.asyncAssertFailure());

asyncAssertFailure 方法返回一个类似 Async 的 {@literal Handler<AsyncResult<T>>} 对象, 它在运行失败时, 执行代理处理器 {@literal Handler<Throwable>} , 并在执行成功时让测试用例失败。

Async async = context.async();
vertx.deployVerticle("my.verticle", ar -> {
  if (ar.succeeded()) {
    context.fail();
  } else {
    context.assertTrue(ar.cause() instanceof IllegalArgumentException);
    async.complete();
  }
});

// 可用如下代替

vertx.deployVerticle("my.verticle", context.asyncAssertFailure(cause -> {
  context.assertTrue(cause instanceof IllegalArgumentException);
}));

Handler 退出时,这个Async也会处于完成状态,除非在执行过程中,创建了新的Async对象

重复测试

当测试随机条件或测试不经常失败时,例如在竞争条件下, 为了增加测试失败的可能性,多次运行同一测试是个方便的方式。

重复一个测试
TestSuite.create("my_suite").test("my_test", 1000, context -> {
  // 这里会执行1000次
});

如果声明了 beforeEach 和 afterEach 回调,那么每当测试用例执行一次,它们也会被执行一次。

注意
重复的测试用例是顺序执行的

共享对象

TestContext 对象提供了 get/put/remove 操作,用于在回调函数之间共享状态。

在 before 回调函数中添加的任何对象都可以在其他回调函数中访问到。 每一个测试用例都会操作一个共享状态的副本。所以,对于共享状态的更新仅仅在一个测试用例之内有效。

在回调函数之间共享状态
TestSuite.create("my_suite").before(context -> {

  // cases host对于所有的测试用例可见
  context.put("host", "localhost");

}).beforeEach(context -> {

  // 为每一个测试用例生成一个port
  int port = helper.randomPort();

  // 获取host
  String host = context.get("host");

  // 启动服务
  Async async = context.async();
  HttpServer server = vertx.createHttpServer();
  server.requestHandler(req -> {
    req.response().setStatusCode(200).end();
  });
  server.listen(port, host, ar -> {
    context.assertTrue(ar.succeeded());
    context.put("port", port);
    async.complete();
  });

}).test("my_test", context -> {

  // 获取共享状态
  int port = context.get("port");
  String host = context.get("host");

  // 发送请求
  HttpClient client = vertx.createHttpClient();
  client.request(HttpMethod.GET, port, host, "/resource", context.asyncAssertSuccess(req -> {
    req.send(context.asyncAssertSuccess(resp -> {
      context.assertEquals(200, resp.statusCode());
    }));
  }));
});
警告
只有java才能共享所有类型的对象,其他语言只能共享基本类型和json类型。 其他类型对象的共享操作应该利用当前编程语言的特性来实现。

运行

当创建了一个测试套件,除非调用 run 方法,否则测试用例不会执行。

运行一个测试套件
suite.run();

这个测试套件也可以用指定的 Vertx 来运行:

提供一个Vertx对象来执行测试用例
suite.run(vertx);

当使用 Vertx 对象来运行时,测试套件则会使用该Vert.x对象的 eventloop 来运行测试用例, 更多信息详见 [event_loop] 一章。

一个测试套件可以用Vert.x命令行接口来执行,即使用 vertx test 命令:

用Vert.x CLI 执行测试用例
> vertx test the_test_suite.js
Begin test suite the_test_suite
Succeeded in deploying verticle
Begin test my_test_case
Passed my_test_case
End test suite my_suite , run: 1, Failures: 0, Errors: 0

这种测试套件仅仅需要通过 run 命令来执行, vertx test 命令则负责配置报告和超时等。 例如:

TestSuite suite = TestSuite.create("the_test_suite");
suite.test("my_test_case", context -> {
  String s = "value";
  context.assertEquals("value", s);
});
suite.run();

vertx test 命令扩展了 vertx run 命令。它改变了JVM的退出行为, 当测试套件开始执行,并且返回了测试结果(例如success (0) or failure (1))的时候,JVM才会退出。

注意
同一个Verticle中可以执行多个测试套件, Vert.x Unit会等待所有的测试套件执行完成。

测试套件执行完毕

我们没办法假设测试套件将何时执行完毕, 如果有代码需要在测试用例执行完毕之后才执行, 那么这段代码要声明在 after 回调函数中或者让其作为 Completion 的回调。

测试套件 执行回调函数
TestCompletion completion = suite.run(vertx);

// 普通的完成回调函数
completion.handler(ar -> {
  if (ar.succeeded()) {
    System.out.println("Test suite passed!");
  } else {
    System.out.println("Test suite failed:");
    ar.cause().printStackTrace();
  }
});

Completion 对象也提供了 resolve 方法, 该方法接收 Promise 对象参数,这个 Promise 会在测试套件执行时被触发通知。

用测试套件解析初始Promise
TestCompletion completion = suite.run();

// 当测试套件执行完毕,Promise则被解析
completion.resolve(startPromise);

这样可以轻松的创建一个 test Verticle,这个Verticle是由测试套件部署起来的, 而部署Verticle的代码中可以很容易的知悉成功或者失败。

completion 对象也可以像 count-down latch 一样使用,他会阻塞直至测试套件执行完。 这应该用于当前线程与测试套件线程不一样时的场景。

阻塞至测试套件执行完毕
Completion completion = suite.run();

//  阻塞至测试套件执行完毕
completion.await();

await 方法在线程被interrupt或者超时的时候抛出异常。

awaitSuccess 方法是一个变种, 它在测试套件运行失败时抛出异常

阻塞至测试套件执行成功
Completion completion = suite.run();

// 阻塞至测试套件执行成功 否则抛出异常
completion.awaitSuccess();

超时

测试套件中的每一个测试用例都必须在超时时间内执行完毕。默认超时时间是 2分钟 , 超时时间可以用 test options 来设置:

设置测试套件的超时时间
TestOptions options = new TestOptions().setTimeout(10000);

// 10秒超时时间
suite.run(options);

事件循环(Event loop)

Vert.x Unit 是一系列task的执行过程,每个task由前一个task的完成动作来触发。 这些task负责平衡eventloop的调度, 但是这取决于当前执行上下文(例如,测试套件在 main 方法中执行,或者嵌入一个verticle ) 是否配置了 Vertx 对象。

setUseEventLoop 以配置事件循环(event loop)的使用方式:

Table 1. 事件循环(Event loop)的使用
useEventLoop:null useEventLoop:true useEventLoop:false

Vertx instance

使用 vertx event loop

使用 vertx event loop

强制不使用 event loop

在 Verticle 中

使用当前 event loop

使用当前 event loop

强制不使用 event loop

在 main 方法中

不使用 event loop

错误

不使用 event loop

useEventLoop 默认为 null , 这说明, 如果条件允许则使用一个event loop,当没有event loop 可用时,不使用event loop。

测试报告

测试报告是测试套件中的重要部分, Vert.x Unit可以用不同配置来运行不同的 reporter。

默认情况下,不配置reporter,但是当运行测试套件时, 可以用 test options 来配置一个或多个 reporter:

使用终端reporter并将其用作junit xml文件
ReportOptions consoleReport = new ReportOptions().
    setTo("console");

// 将Junit报告信息文件放在当前目录
ReportOptions junitReport = new ReportOptions().
    setTo("file:.").
    setFormat("junit");

suite.run(new TestOptions().
        addReporter(consoleReport).
        addReporter(junitReport)
);

控制台报告

将信息报告至JVM的 System.out 和 System.err :

报告至

终端

格式

常规 或 junit

文件报告

如果想将报告输出至文件,您必须提供一个 Vertx 对象:

to

file : dir name

格式

simple 或 junit

示例

file:.

文件 reporter 会在配置的目录中创建文件,这些文件会以测试套件的名称和格式来命名 (例如, 常规格式 创建 txt 文件,junit格式 创建 xml 文件)。

日志报告

如果要向logger发送报告,您必须提供一个 Vertx 对象:

to

log : logger name

示例

log:mylogger

事件总线报告

如果要向事件总线报告事件,那么您必须提供一个 Vertx 对象:

to

bus : event bus address

示例

bus:the-address

它将测试套件的执行过程和报告解耦合。

通过事件总线发送的消息可以通过 EventBusCollector 来收集起来, 并实现自定义报告

EventBusCollector collector = EventBusCollector.create(
    vertx,
    new ReportingOptions().addReporter(
        new ReportOptions().setTo("file:report.xml").setFormat("junit")));

collector.register("the-address");

Vertx 整合

默认情况下,断言和失败必须在 TestContext 之内完成,另外 断言失败 仅仅在Vert.x Unit的调用下才会有效。

suite.test("my_test_case", ctx -> {

  // '失败'会被Vert.x Unit 报告
  throw new RuntimeException("it failed!");
});

在一个常规的Vert.x回调中,会忽略失败:

suite.test("test-server", testContext -> {
  HttpServer server = vertx.createHttpServer().requestHandler(req -> {
    if (req.path().equals("/somepath")) {
      throw new AssertionError("Wrong path!");
    }
    req.response().end();
  });
});

从 Vert.x 3.3 版本开始,您可以设置一个全局异常处理器来报告 event loop 没有捕捉到的异常:

suite.before(testContext -> {

  // 报告未被捕捉到到异常,以其作为 Vert.x Unit 失败事件
  vertx.exceptionHandler(testContext.exceptionHandler());
});

suite.test("test-server", testContext -> {
  HttpServer server = vertx.createHttpServer().requestHandler(req -> {
    if (req.path().equals("/somepath")) {
      throw new AssertionError("Wrong path!");
    }
    req.response().end();
  });
});

如果异常处理器设置在 before 部分,那么 TestContext 则会在 before, test 和 after 部分之间共享。 所以将异常处理器置于 before 部分是一个正确到做法

Junit 整合

尽管Vertx Unit是多语言的,且不是基于Junit的,但是您依然可以在Junit上运行Vert.x Unit测试套件以及测试用例, 并允许您将测试用例整合到JUnit,允许整合到您的build system,也可以整合到IDE中。

将一个Java类作为JUnit测试套件
@RunWith(VertxUnitRunner.class)
public class JUnitTestSuite {
  @Test
  public void testSomething(TestContext context) {
    context.assertFalse(false);
  }
}

VertxUnitRunner 使用Junit注解来对这个类进行解析, 并创建该类对象之后创建测试套件。 该类中的方法应该在参数中声明一个 TestContext 参数 ,不声明该参数也是可以的。 然而 TestContext 是在异步测试过程中能够获取相关Vertx对象的唯一方式。

在Groovy语言中, 用 io.vertx.groovy.ext.unit.junit.VertxUnitRunner 也可以达到整合Junit的目的。

在 Vert.x 上下文中执行一个测试

默认情况下执行测试方法的是Junit线程。 RunTestOnContext Junit rule可以改变这个行为,使测试方法都运行于 Vert.x event loop 线程。

因此,当某状态在测试方法/Vert.x处理器之间共享时,您就需要小心了,因为他们并不是在同一个线程上运行的, 例如,在Vert.x处理器中累加一个计数器然后在测试方法中断言计数器的值。 使用恰当地同步机制是解决此问题的一种方式, 另外还可以在Vert.x context中执行测试方法,这样共享状态则会在处理器之间传播。

以此目的,RunTestOnContext rule 需要一个 Vertx 对象。 您可以主动提供这个对象,如果不提供,这个 rule 会隐式创建它。 这个对象可以在测试执行过程中获取, 这也使该rule成为管理Vert.x实例的一种方式。

运行一个Java类作为JUnit测试套件
@RunWith(VertxUnitRunner.class)
public class RunOnContextJUnitTestSuite {

  @Rule
  public RunTestOnContext rule = new RunTestOnContext();

  @Test
  public void testSomething(TestContext context) {
    // 使用这个隐式创建的vertx对象
    Vertx vertx = rule.vertx();
  }
}

rule可以用 {@literal @Rule} 或 {@literal @ClassRule} 注解修饰, 前者管理每个测试的Vert.x对象,后者则管理该测试类中所有测试方法相关的那一个Vertx对象。

警告
切记,用这个rule的时候您不可以阻塞 event loop 。使用像 CountDownLatch 类的做法, 则必须要小心。

超时

Vert.x Unit 默认超时时间两分钟,这可以用 @Test 注解中的 timeout 属性来指定:

在测试方法级别配置超时时间
public class JunitTestWithTimeout {

  @Test(timeout = 1000l)
  public void testSomething(TestContext context) {
    //...
  }

}

对于更全局的配置,可以用 Timeout rule:

类级别超时时间配置
@RunWith(VertxUnitRunner.class)
public class TimeoutTestSuite {

  @Rule
  public Timeout rule = Timeout.seconds(1);

  @Test
  public void testSomething(TestContext context) {
    //...
  }
}
注意
@Test 的超时设置会覆盖 Timeout rule 的配置。

参数化测试

JUnit提供了非常有用的 参数化 测试, Vert.x Unit 测试可以利用 VertxUnitRunnerWithParametersFactory 来指定执行器:

运行参数化的Vert.x Unit测试
@RunWith(Parameterized.class)
@Parameterized.UseParametersRunnerFactory(VertxUnitRunnerWithParametersFactory.class)
public class SimpleParameterizedTest {

  @Parameterized.Parameters
  public static Iterable<Integer> data() {
    return Arrays.asList(0, 1, 2);
  }

  public SimpleParameterizedTest(int value) {
    //...
  }

  @Test
  public void testSomething(TestContext context) {
    // 用当前值来执行测试方法
  }
}

Groovy语言中,您可以用 io.vertx.groovy.ext.unit.junit.VertxUnitRunnerWithParametersFactory 来实现参数化测试:

重复测试

当测试随机条件或测试不经常失败时,例如在竞争条件下, 为了增加测试失败的可能性,多次运行同一测试是个方便的方式。

如果用Junit,测试用例必须用 @Repeat 注解修饰,来指定重复执行。 测试用例也必须定义根据自身规则定义 RepeatRule 。

用Junit重复测试
@RunWith(VertxUnitRunner.class)
public class RepeatingTest {

  @Rule
  public RepeatRule rule = new RepeatRule();

  @Repeat(1000)
  @Test
  public void testSomething(TestContext context) {
    // 执行1000次
  }
}

如果声明了RepeatRule,那么 before 和 after 方法会在每次执行测试方法时都执行一次。

注意
重复测试是顺序执行

用其他断言库

Vert.x Unit 的可用性从 Vert.x 3.3 开始有了巨大的提升。您可以用 Hamcrest , AssertJ , Rest Assured ,甚至其他您想用的断言库来写测试用例。这是由 [vertx_integration] 章节里介绍的全局异常处理器的机制实现的。

您可以在 vertx-examples 工程中, 找到Vert.x Unit中用 Hamcrest 和 AssertJ 的示例。

Java 语言整合

测试套件整合

Java语言提供了class, 而且可以用如下映射规则来直接利用Java class创建测试套件:

检查 测试套件对象 的参数和方法,保留带有 TestContext 参数的public且非静态方法并通过方法名映射到Vert.x Unit测试套件:

  • before : before callback

  • after : after callback

  • beforeEach : beforeEach callback

  • afterEach : afterEach callback

  • 以 test 开头的名称: 以方法名命名的测试用例

用Java类写的测试套件
public class MyTestSuite {

  public void testSomething(TestContext context) {
    context.assertFalse(false);
  }
}

这个类可以很容易的转化成Vert.x 测试套件:

从Java对象创建测试套件
TestSuite suite = TestSuite.create(new MyTestSuite());