在这一部分教程中,将介绍JUnit 5的其他功能,这些功能将通过并行运行测试,配置测试顺序和创建参数化测试来帮助减少测试的执行时间。还将介绍如何利用Selenium Jupiter功能,例如通过系统属性进行测试执行配置,单个浏览器会话测试以加快测试执行速度或捕获测试中的屏幕截图,AssertJ库的基本Demo。
JUnit 5带有内置的并行测试执行支持。下面的命令将并行运行 TodoMvcTests
的测试方法:
./gradlew clean test --tests *TodoMvcTests -Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent
构建成功,在执行过程中,注意到两个Chrome浏览器实例正在运行。在此运行中,所有测试的执行时间减少到原来的几分之一:
> Task :test demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSED demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSED demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSED demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSED demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSED demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSED demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSED BUILD SUCCESSFUL in 10s 4 actionable tasks: 4 executed
一般来讲,自动化测试应该能够独立运行并且没有特定的顺序,并且测试结果不应依赖于先前测试的结果。但是在某些情况下测试执行需要依赖特定顺序。
默认情况下,在JUnit 5中,测试方法的执行在构建之间是无序的,因此非确定性的。但是可以使用内置方法定购器或通过创建自定义定购器来调整执行顺序以满足测试的需求。我们将使用 @Order
批注来提供测试方法的排序,并使用注释类, @TestMethodOrder
以指示 JUnit 5
方法已排序。
@ExtendWith(SeleniumExtension.class) @SingleSession @TestMethodOrder(MethodOrderer.OrderAnnotation.class) @DisplayName("Managing Todos") class TodoMvcTests { @Test @Order(1) @DisplayName("test001") void createsTodo() { } @Test @Order(2) @DisplayName("test002") void createsTodosWithSameName() { } }
对于 TodoMvcTests
类中的每个测试,都会启动一个新的 Chrome
浏览器实例,并在每个测试之后将其关闭。此行为导致整个套件的执行花费了相当多的时间。 Selenium Jupiter
附带了一个简单的类级别注释,可以修改这项功能。 @SingleSession
批注会更改行为,以便在所有测试之前初始化浏览器实例一次,并在所有测试之后关闭浏览器实例。
要应用 @SingleSession
需要稍微修改测试类,然后将驱动程序对象注入构造函数中而不是 @BeforeEach
方法中。我们还需要注意每次测试的正确状态。这可以通过清除 @AfterEach
方法中存储待办事项的本地存储来完成。我还创建了一个字段 driver
,该字段保留所有测试中使用的驱动程序对象实例。
private final ChromeDriver driver; public TodoMvcTests(ChromeDriver driver) { this.driver = driver; this.todoMvc = PageFactory.initElements(driver, TodoMvcPage.class); this.todoMvc.navigateTo(); } @AfterEach void storageCleanup() { driver.getLocalStorage().clear(); }
当执行测试时,我们可以观察到执行所有测试的时间大大减少了:
./gradlew clean test > Task :test demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSED demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSED demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSED demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSED demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSED demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSED demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSED demos.selenium.todomvc.SeleniumTest > projectIsConfigured(ChromeDriver) PASSED BUILD SUCCESSFUL in 9s 3 actionable tasks: 3 executed
提示:如果您希望从选定的类中运行测试,则可以使用Gradle测试任务随附的测试过滤。例如,此命令将仅运行来自 TodoMvcTests
类的测试: ./gradlew clean test --tests *.todomvc.TodoMvcTests
如果你现在尝试使用 JUnit 5
并行执行测试,在并行执行中,每种方法都需要单独的驱动程序实例,并且 @SingleSession
启用后,我们将为所有测试共享一个实例。为了解决这个问题,需要运行测试配置并行执行,为了让顶级类并行运行,但方法在同一线程中。
只需复制 TodoMvcTests
类,然后尝试以下命令:
./gradlew clean test --tests *TodoMvcTests -Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=same_thread -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent
在执行过程中,应该看到正在运行并在终端中输出以下内容:
<===========--> 87% EXECUTING [3s] > :test > 0 tests completed > :test > Executing test demos.selenium.todomvc.MoreTodoMvcTests > :test > Executing test demos.selenium.todomvc.EvenMoreTodoMvcTests > :test > Executing test demos.selenium.todomvc.TodoMvcTests
在当前测试中,我们将 ChromeDriver
直接注入测试类。但是在某些情况下,我们希望对注入的驱动程序有更多的控制,而我们宁愿注入 WebDriver
(接口)并稍后决定应该注入哪个驱动程序实例。我们还需要更改 storageCleanup()
方法,因为通用 WebDriver
不提供直接的 localStorage
访问:
public TodoMvcTests(WebDriver driver) { this.driver = driver; this.todoMvc = PageFactory.initElements(driver, TodoMvcPage.class); this.todoMvc.navigateTo(); } @AfterEach void storageCleanup() { ((JavascriptExecutor) driver).executeScript("window.localStorage.clear()"); }
现在,要在运行时更改浏览器类型,我们需要调整 sel.jup.default.browserconfig
属性。
配置 JUnit 5
和 Selenium Jupiter
的常用方法之一是通过Java系统属性。可以使用属性文件以编程方式完成此操作,也可以使用 -Dswitch
将属性直接传递给JVM 。为了确保在执行 Gradle
时传递给JVM的属性在测试中可用,我们需要进行 build.gradle
如下修改:
test { systemProperties System.getProperties() useJUnitPlatform() testLogging { events "passed", "skipped", "failed" } }
当运行命令时 ./gradlew clean test -Dprop=value
,该属性将在测试中可用。通过上述更改,我们可以选择浏览器类型来运行测试:
./gradlew clean test --tests *TodoMvcTests -Dsel.jup.default.browser=firefox
Selenium Jupiter ./gradlew clean test --tests *TodoMvcTests -Dsel.jup.default.browser=firefox -Dsel.jup.screenshot.at.the.end.of.tests=true -Dsel.jup.screenshot.format=png -Dsel.jup.output.folder=/tmp
参数化单元测试的总体思路是针对不同的测试数据运行相同的测试方法。要在JUnit 5中创建参数化测试,请使用注释测试方法,@ParameterizedTest并提供该测试方法的参数源。有几种可用的参数来源,包括:
为了在测试中使用上述CSV文件,我们需要在测试中加上@ParameterizedTest注释(而不是@Test),然后在@CsvFileSource注释中指向文件:
@ParameterizedTest @CsvFileSource(resources = "/todos.csv", numLinesToSkip = 1, delimiter = ';') @DisplayName("Creates Todo with given name") void createsTodo(String todo) { todoMvc.createTodo(todo); assertSingleTodoShown(todo); }
在下一个示例中,我们将使用以下CSV格式存储在 src/test/resources
目录中:
todo;done Buy the milk;false Clean up the room;true Read the book;false
CSV文件中的每个记录都有两个字段: name
和 done
。在上述测试中,仅使用待办事项的名称。但是我们当然可以使测试复杂一点,并同时使用这两个属性:
@ParameterizedTest(name = "{index} - {0}, done = {1}" ) @CsvFileSource(resources = "/todos.csv", numLinesToSkip = 1, delimiter = ';') @DisplayName("test003") void createsAndRemovesTodo(String todo, boolean done) { todoMvc.createTodo(todo); assertSingleTodoShown(todo); todoMvc.showActive(); assertSingleTodoShown(todo); if (done) { todoMvc.completeTodo(todo); assertNoTodoShown(todo); todoMvc.showCompleted(); assertSingleTodoShown(todo); } todoMvc.removeTodo(todo); assertNoTodoShown(todo); }
JUnit 5
具有许多内置的断言,在实际工作中,可能需要的超出 JUnit 5
所能提供的。在这种情况下,建议使用AssertJ库。 AssertJ
是一个Java库,提供了一组丰富的断言,真正有用的错误消息,提高了测试代码的可读性,并且设计为IDE中容易使用。
AssertJ的一些功能:
要在项目中使用AssertJ,我们需要向中添加单个依赖项 build.gradle
:
testCompile('org.assertj:assertj-core:3.13.2')
首先,我们需要静态导入 org.assertj.core.api.Assertions.*
并使用以下assertThat方法完成代码: assertThat(objectUnderTest)
.
例如将 assertThat(todoMvc.getTodosLeft()).isEqualTo(3);
使用 AssertJ
而不是 assertEquals(3, todoMvc.getTodosLeft());
纯 JUnit 5
或 assertThat(todoMvc.todoExists(readTheBook)).isTrue()
来编写 assertTrue(todoMvc.todoExists(readTheBook))
。
使用复杂类型甚至更好:
todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook); assertThat(todoMvc.getTodos()) .hasSize(3) .containsSequence(buyTheMilk, cleanupTheRoom, readTheBook);