测试与质量保障
你当前正在浏览历史版本 Vert.x 5.0.12 的文档。 访问本页面的 最新版本。

Vert.x Unit

Deprecated

异步多语言单元测试。

简介

Vert.x Unit 旨在通过多语言 API 编写异步单元测试并在 JVM 中运行这些测试。Vert.x Unit API 借鉴了 JUnit 或 QUnit 等现有测试框架,并遵循 Vert.x 的惯例实践。

因此,Vert.x Unit 是测试 Vert.x 应用程序的自然之选。

若要使用 Vert.x Unit,请将以下依赖项添加到构建描述文件的 dependencies 部分:

  • Maven(在你的 pom.xml 中):

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

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

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 方法将执行该套件并遍历套件中的所有测试。套件无论是失败还是通过,如果外界没有感知到测试结果,这都无关紧要。

执行时,测试套件现在会将测试套件的各个步骤报告到控制台:

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 选项用于配置套件运行器在报告测试执行情况时所使用的报告器,详情请参阅 报告 章节。

编写测试套件

测试套件是一组具有名称的测试用例集合,测试用例是一个直接执行的回调。测试套件可以拥有在测试用例或测试套件_之前(before)_和/或_之后(after)_执行的生命周期回调,用于初始化或销毁测试套件所使用的服务。

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

该 API 是链式调用的(fluent),因此测试用例可以连缀声明:

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

测试用例的声明顺序不保证执行顺序,因此测试用例不应依赖另一个测试用例的执行来运行。这种做法被视为不良实践。

Vert.x Unit 提供了 before 和 after 回调来进行全局设置(setup)或清理(cleanup):

TestSuite suite = TestSuite.create("the_test_suite");
suite.before(context -> {
  // 测试套件设置
}).test("my_test_case_1", context -> {
  // 测试 1
}).test("my_test_case_2", context -> {
  // 测试 2
}).test("my_test_case_3", context -> {
  // 测试 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 -> {
  // 测试 1
}).test("my_test_case_2", context -> {
  // 测试 2
}).test("my_test_case_3", context -> {
  // 测试 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.2 +/- 0.5

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

assertTrue 与 assertFalse

断言布尔表达式的值。

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");
  // 后续语句将不会被执行
});

失败原因既可以是如前所述的_字符串_,也可以是一个_错误(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 或任何其他断言库
  // 甚至可以手动抛出 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 来自 Event Bus 的事件回调终止了测试

使用 async 方法创建 Async 对象会将执行的测试用例标记为未终止。当调用 complete 方法时,测试用例才会终止。

当未调用 complete 回调时,测试用例将在超时后失败。

可以在同一个测试用例中创建多个 Async 对象,必须全部_完成(completed)_才能终止测试。

Vert.x 5 使用 JVM 的 Cleaner 机制作为安全兜底,在某些资源(客户端、执行器等)变为*不可达*时进行清理(旧版本使用的是 Java 的终结器 finalizer)。

由于 Cleaner 的执行依赖垃圾回收,清理时机是*非确定性*的,并且可能比终结器的清理更为激进。在异步测试中这非常关键,因为当异步操作仍在进行时,测试回调可能就已经返回了。

如果将客户端(例如 HttpClient / WebClient)创建为局部变量,并且在异步回调完成前没有保留强引用,JVM 可能会将其回收,Vert.x 进而可能会关闭底层连接池,从而导致间歇性失败(例如 VertxException: Pool closed),在 CI 环境中尤为常见。

为了避免这种情况,请在异步工作期间保持客户端强引用可达(例如将其存储在字段中),并在适当时首选显式调用 close()。

多个 Async 对象提供协调机制
suite.test("my_test_case", context -> {

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

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

Async 对象也可以在 before 或 after 回调中使用,在 before 回调中实现依赖于一个或多个异步结果的初始化设置时,这会非常方便:

Async 在测试用例前启动 HTTP 服务器
suite.before(context -> {
  Async async = context.async();
  HttpServer server = vertx.createHttpServer();
  server.requestHandler(requestHandler);
  server.listen(8080).onComplete(ar -> {
    context.assertTrue(ar.succeeded());
    async.complete();
  });
});

可以等待特定 Async 完成,类似于 Java 的 CountDownLatch:

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

// 等待完成
async.awaitSuccess();
这不应在 Event Loop 中执行!

Async 也可以使用初始计数值创建,使用 countDown 使倒计时归零时完成:

等待倒计时归零以完成
Async async = context.async(2);
HttpServer server = vertx.createHttpServer();
server.requestHandler(requestHandler);
server.listen(8080).onComplete(ar -> {
  context.assertTrue(ar.succeeded());
  async.countDown();
});

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

// 等待定时器和 HTTP 请求完成
async.awaitSuccess();

在 async 上调用 complete() 会像往常一样完成该 async,实际上它将计数值直接设为 0。

异步断言

TestContext 提供了有用的方法,为异步测试提供了强大的结构机制:

asyncAssertSuccess 方法返回一个类似于 Async 的 Handler<AsyncResult<T>> 实例,在成功时解析(resolve)该 Async,并在失败时使测试失败并附带失败原因。

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

// 可以替换为

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

asyncAssertSuccess 方法返回一个类似于 Async 的 Handler<AsyncResult<T>> 实例,在成功时调用委托的 Handler<T>,并在失败时使测试失败并附带失败原因。

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

// 可以替换为

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

当 Handler 退出时 async 即告完成,除非在调用期间创建了新的 async,这对于_链式连缀_异步行为非常方便:

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

// 可以替换为

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

asyncAssertFailure 方法返回一个类似于 Async 的 Handler<AsyncResult<T>> 实例,在失败时解析该 Async,并在成功时使测试失败。

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

// 可以替换为

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

asyncAssertFailure 方法返回一个类似于 Async 的 Handler<AsyncResult<T>> 实例,在失败时调用委托的 Handler<Throwable>,并在成功时使测试失败。

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

// 可以替换为

vertx.deployVerticle("my.verticle").onComplete(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 -> {

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

}).beforeEach(context -> {

  // 为每个测试生成一个随机端口
  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).onComplete(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").onComplete(context.asyncAssertSuccess(req -> {
    req.send().onComplete(context.asyncAssertSuccess(resp -> {
      context.assertEquals(200, resp.statusCode());
    }));
  }));
});
共享任意对象仅在 Java 中受支持,其他语言只能共享基本类型或 JSON 类型。其他对象应使用该语言自身的特性来共享。

运行

创建测试套件后,在调用 run 方法之前它不会被执行。

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

也可以使用指定的 Vertx 实例来运行测试套件:

提供 Vertx 实例以运行测试套件
suite.run(vertx);

当使用 Vertx 实例运行时,测试套件将使用 Vert.x Event Loop 执行,详情请参阅 Event Loop 章节。

可以在同一个 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 对象还提供了一个接收 Promise 对象的 resolve 方法,该 Promise 将接收测试套件执行结果的通知:

使用测试套件 resolve 启动 Promise
TestCompletion completion = suite.run();

// 当套件完成时,promise 将被 resolve
completion.resolve(startPromise);

这样可以轻松创建一个_测试_ Verticle,其部署操作就是测试套件的执行,从而使部署它的代码能够轻松感知成功或失败。

Completion 对象还可以像闭锁一样使用,以阻塞直至测试套件完成。当运行测试套件的线程与当前线程不同时,应使用此方式:

阻塞直至测试套件完成
Completion completion = suite.run();

// 等待测试套件完成
completion.await();

当线程被中断或触发超时时,await 会抛出异常。

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 的执行是一个待执行的任务列表,每个任务的执行都由前一个任务的完成驱动。这些任务应尽可能利用 Vert.x Event Loop,但这取决于当前的执行上下文(即测试套件是在 main 中执行还是嵌入在 Verticle 中)以及是否配置了 Vertx 实例。

setUseEventLoop 用于配置 Event Loop 的使用方式:

Table 1. Event Loop 的使用情况
useEventLoop:null useEventLoop:true useEventLoop:false

Vertx 实例

使用 Vert.x Event Loop

使用 Vert.x 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 可以配置为与不同种类的报告器配合运行。

默认情况下未配置任何报告器,运行测试套件时,可以提供_测试选项(test options)_来配置一个或多个报告器:

使用控制台报告器以及 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:

to

console

format

simple 或 junit

文件报告

报告输出至文件,必须提供 Vertx 实例:

to

file : 目录名

format

simple 或 junit

example

file:.

文件报告器将在配置的目录中创建文件,文件将根据所执行的测试套件名称和格式命名(即 simple 创建 txt 文件,junit 创建 xml 文件)。

日志报告

报告输出至日志器(logger),必须提供 Vertx 实例:

to

log : 日志器名称

example

log:mylogger

Event Bus 报告

将事件报告到 Event Bus,必须提供 Vertx 实例:

to

bus : Event Bus 地址

example

bus:the-address

这可以将测试套件的执行与报告解耦。

通过 Event Bus 发送的消息可以由 EventBusCollector 收集并实现自定义报告:

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

collector.register("the-address");

Vert.x 集成

默认情况下,断言和失败处理必须在 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 集成

尽管 Vert.x Unit 是多语言的且并不基于 JUnit,但仍可以从 JUnit 中运行 Vert.x Unit 测试套件或测试用例,从而使你能够将测试与 JUnit 以及构建系统或 IDE 集成。

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

VertxUnitRunner 使用 JUnit 注解来自省类,并在该类之后创建测试套件。测试方法应声明一个 TestContext 参数,如果不声明也可以。然而,TestContext 是检索关联的 Vertx 实例或执行异步测试的唯一途径。

JUnit 集成也可用于 Groovy 语言,使用 io.vertx.groovy.ext.unit.junit.VertxUnitRunner 运行器。

在 Vert.x 上下文中运行测试

默认情况下,调用测试方法的线程是 JUnit 线程。RunTestOnContext JUnit 规则可用于更改此行为,以便使用 Vert.x Event Loop 线程运行这些测试方法。

因此,当在测试方法与 Vert.x 处理器(handler)之间共享状态时必须谨慎,因为它们不在同一个线程上,例如在 Vert.x 处理器中递增计数器并在测试方法中断言该计数器。解决此问题的一种方法是使用适当的同步,另一种方法是在 Vert.x 上下文上执行测试方法,该上下文将传播到所创建的处理器中。

为此,RunTestOnContext 规则需要一个 Vertx 实例。可以显式提供该实例,否则规则将在底层自行管理一个实例。该实例可以在测试运行时进行检索,这也使得该规则成为管理 Vertx 实例的一种方式。

将 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 或 @ClassRule 进行注解,前者为每个测试管理一个 Vert.x 实例,后者为该类的测试方法管理单个 Vert.x 实例。

请牢记,使用此规则时切勿阻塞 Event Loop。使用 CountDownLatch 等类似类时必须十分谨慎。

超时

Vert.x Unit 的 2 分钟超时时间可以通过 @Test 注解的 timeout 属性进行覆盖:

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

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

}

对于更全局的配置,可以使用 Timeout 规则:

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

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

  @Test
  public void testSomething(TestContext context) {
    //...
  }
}
请使用 io.vertx.ext.unit.junit.Timeout,而非 org.junit.rules.Timeout。

方法级别的超时会覆盖类级别的超时:

将类级别规则与方法级别超时时间结合使用
@RunWith(VertxUnitRunner.class)
public class TimeoutOverwriteTestSuite {

  @Rule
  public Timeout rule = Timeout.millis(5_000L);

  @Test
  public void testQuick(TestContext context) {
    //...
  }

  @Test(timeout = 30_000L)
  public void testSlow(TestContext context) {
    //...
  }
}
@Test 的超时时间会覆盖 io.vertx.ext.unit.junit.Timeout 规则,但不会覆盖 org.junit.rules.Timeout 规则。

参数化测试

JUnit 提供了实用的 Parameterized 测试,得益于 VertxUnitRunnerWithParametersFactory,Vert.x Unit 测试可以使用该专用运行器来运行:

运行 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 次
  }
}

声明后,before 和 after 生命周期将随测试执行次数相应地执行多次。

测试重复执行是串行的

与其他断言库结合使用

Vert.x Unit 的易用性在 Vert.x 3.3 中得到了极大改进。你现在可以使用 Hamcrest、http://joel-costigliola.github.io/assertj/[AssertJ]、https://github.com/rest-assured/rest-assured/[Rest Assured] 或任何你想要的断言库来编写测试。这得益于 Vert.x 集成 中描述的全局异常处理器。

你可以在 vertx-examples 项目中找到将 Vert.x Unit 与 Hamcrest 和 AssertJ 结合使用的 Java 示例。

Java 语言集成

测试套件集成

Java 语言提供了类,并且可以根据以下映射规则直接从 Java 类创建测试套件:

对 testSuiteObject 参数的方法进行自省,保留带有 TestContext 参数的 public、非 static 方法,并通过方法名映射到 Vert.x Unit 测试套件:

  • before:before 回调

  • after:after 回调

  • beforeEach:beforeEach 回调

  • afterEach:afterEach 回调

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

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

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

这个类可以轻松转换为 Vert.x 测试套件:

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