JUnit 5和Selenium基础(三)

2020-01-17 09:58:06 浏览数 (1)

在这一部分教程中,将介绍JUnit 5的其他功能,这些功能将通过并行运行测试,配置测试顺序和创建参数化测试来帮助减少测试的执行时间。还将介绍如何利用Selenium Jupiter功能,例如通过系统属性进行测试执行配置,单个浏览器会话测试以加快测试执行速度或捕获测试中的屏幕截图,AssertJ库的基本Demo。

使用JUnit 5并行测试执行

JUnit 5带有内置的并行测试执行支持。下面的命令将并行运行TodoMvcTests的测试方法:

./gradlew clean test --tests *TodoMvcTests -Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent

构建成功,在执行过程中,注意到两个Chrome浏览器实例正在运行。在此运行中,所有测试的执行时间减少到原来的几分之一:

代码语言:javascript复制
> 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测试执行顺序

一般来讲,自动化测试应该能够独立运行并且没有特定的顺序,并且测试结果不应依赖于先前测试的结果。但是在某些情况下测试执行需要依赖特定顺序。

默认情况下,在JUnit 5中,测试方法的执行在构建之间是无序的,因此非确定性的。但是可以使用内置方法定购器或通过创建自定义定购器来调整执行顺序以满足测试的需求。我们将使用@Order批注来提供测试方法的排序,并使用注释类,@TestMethodOrder以指示JUnit 5方法已排序。

代码语言:javascript复制
@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() {
 
    }
  
}

使用Selenium Jupiter的单个浏览器会话

对于TodoMvcTests类中的每个测试,都会启动一个新的Chrome浏览器实例,并在每个测试之后将其关闭。此行为导致整个套件的执行花费了相当多的时间。Selenium Jupiter附带了一个简单的类级别注释,可以修改这项功能。@SingleSession批注会更改行为,以便在所有测试之前初始化浏览器实例一次,并在所有测试之后关闭浏览器实例。

要应用@SingleSession需要稍微修改测试类,然后将驱动程序对象注入构造函数中而不是@BeforeEach方法中。我们还需要注意每次测试的正确状态。这可以通过清除@AfterEach方法中存储待办事项的本地存储来完成。我还创建了一个字段driver,该字段保留所有测试中使用的驱动程序对象实例。

代码语言:javascript复制
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();
}

当执行测试时,我们可以观察到执行所有测试的时间大大减少了:

代码语言:javascript复制
./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

在执行过程中,应该看到正在运行并在终端中输出以下内容:

代码语言:javascript复制
<===========--> 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

Selenium Jupiter的驱动程序配置

在当前测试中,我们将ChromeDriver直接注入测试类。但是在某些情况下,我们希望对注入的驱动程序有更多的控制,而我们宁愿注入WebDriver(接口)并稍后决定应该注入哪个驱动程序实例。我们还需要更改storageCleanup()方法,因为通用WebDriver不提供直接的localStorage访问:

代码语言:javascript复制
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 5Selenium Jupiter的常用方法之一是通过Java系统属性。可以使用属性文件以编程方式完成此操作,也可以使用-Dswitch 将属性直接传递给JVM 。为了确保在执行Gradle时传递给JVM的属性在测试中可用,我们需要进行build.gradle如下修改:

代码语言:javascript复制
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进行参数化测试

参数化单元测试的总体思路是针对不同的测试数据运行相同的测试方法。要在JUnit 5中创建参数化测试,请使用注释测试方法,@ParameterizedTest并提供该测试方法的参数源。有几种可用的参数来源,包括:

  • @ValueSource –提供对文字值数组(例如,int,字符串等)的访问。
  • @MethodSource –提供对从工厂方法返回的值的访问
  • @CsvSource –从一个或多个提供的CSV行中读取逗号分隔值(CSV)
  • @CsvFileSource –用于加载逗号分隔值(CSV)文件

为了在测试中使用上述CSV文件,我们需要在测试中加上@ParameterizedTest注释(而不是@Test),然后在@CsvFileSource注释中指向文件:

代码语言:javascript复制
@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目录中:

代码语言:javascript复制
todo;done
Buy the milk;false
Clean up the room;true
Read the book;false

CSV文件中的每个记录都有两个字段:namedone。在上述测试中,仅使用待办事项的名称。但是我们当然可以使测试复杂一点,并同时使用这两个属性:

代码语言:javascript复制
@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);
}

使用AssertJ更好的断言

JUnit 5具有许多内置的断言,在实际工作中,可能需要的超出JUnit 5所能提供的。在这种情况下,建议使用AssertJ库。AssertJ是一个Java库,提供了一组丰富的断言,真正有用的错误消息,提高了测试代码的可读性,并且设计为IDE中容易使用。

AssertJ的一些功能:

  • 对许多Java类型的流利断言,包括日期,集合,文件等。
  • SoftAssertions(类似于JUnit 5的assertAll)
  • 复杂领域比较
  • 可以轻松扩展–自定义条件和自定义断言

要在项目中使用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 5assertThat(todoMvc.todoExists(readTheBook)).isTrue()来编写assertTrue(todoMvc.todoExists(readTheBook))

使用复杂类型甚至更好:

代码语言:javascript复制
todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook);
 
assertThat(todoMvc.getTodos())
        .hasSize(3)
        .containsSequence(buyTheMilk, cleanupTheRoom, readTheBook);

  • 郑重声明:文章首发于公众号“FunTester”,禁止第三方(腾讯云除外)转载、发表。

0 人点赞