Executando Testes Serenity BDD com JUnit 5
Este guia cobre tudo o que você precisa saber para escrever e executar testes Serenity BDD usando JUnit 5 (Jupiter), sem Cucumber.
Visão Geral
JUnit 5 é a versão mais recente do popular framework de testes Java. Combinado com Serenity BDD, você obtém:
- Recursos Modernos de Teste: Testes parametrizados, testes aninhados, testes dinâmicos e muito mais
- Asserções Poderosas: Bibliotecas de asserção nativas e de terceiros
- Relatórios Detalhados: Relatórios detalhados do Serenity com capturas de tela e detalhes de execução passo a passo
- Execução Paralela: Execute testes simultaneamente para obter feedback mais rápido
- Organização Flexível: Tags, classes aninhadas e nomes de exibição para melhor organização dos testes
JUnit 4 está obsoleto a partir do Serenity 5.0.0 e será removido no Serenity 6.0.0. Todos os novos projetos devem usar JUnit 5.
Pré-requisitos
- Java 17 ou superior
- Maven ou Gradle
- Uma IDE com suporte a Java (IntelliJ IDEA, Eclipse, VS Code, etc.)
Configurando Dependências
Dependências Maven
Adicione o seguinte ao seu pom.xml:
<properties>
<serenity.version>5.3.1</serenity.version>
<junit.version>6.0.1</junit.version>
</properties>
<dependencies>
<!-- Integração Serenity JUnit 5 -->
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-junit5</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<!-- Serenity Screenplay (opcional, mas recomendado) -->
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<!-- Integração Serenity WebDriver (para testes web) -->
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-screenplay-webdriver</artifactId>
<version>${serenity.version}</version>
<scope>test</scope>
</dependency>
<!-- JUnit 5 (Jupiter) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!-- AssertJ (recomendado para asserções fluentes) -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Dependências Gradle
Para Gradle, adicione ao seu build.gradle:
dependencies {
testImplementation "net.serenity-bdd:serenity-junit5:5.3.1"
testImplementation "net.serenity-bdd:serenity-screenplay:5.3.1"
testImplementation "net.serenity-bdd:serenity-screenplay-webdriver:5.3.1"
testImplementation "org.junit.jupiter:junit-jupiter-api:6.0.1"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:6.0.1"
testImplementation "org.assertj:assertj-core:3.24.2"
}
test {
useJUnitPlatform()
}
Escrevendo Seu Primeiro Teste
Estrutura Básica do Teste
Todo teste Serenity JUnit 5 deve ser anotado com @ExtendWith(SerenityJUnit5Extension.class):
import net.serenitybdd.junit5.SerenityJUnit5Extension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SerenityJUnit5Extension.class)
class WhenSearchingForProducts {
@Test
void shouldFindProductByName() {
// Seu código de teste aqui
}
@Test
void shouldFilterProductsByCategory() {
// Seu código de teste aqui
}
}
Exemplo de Teste Web
Aqui está um exemplo completo de um teste web:
import net.serenitybdd.annotations.Managed;
import net.serenitybdd.junit5.SerenityJUnit5Extension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openqa.selenium.WebDriver;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SerenityJUnit5Extension.class)
class WhenBrowsingProducts {
@Managed(driver = "chrome", options = "headless")
WebDriver driver;
@Test
void shouldDisplayProductDetails() {
driver.get("https://example.com/products/123");
String productTitle = driver.findElement(By.id("product-title")).getText();
assertThat(productTitle).isEqualTo("Product Name");
}
}
Usando Bibliotecas de Passos
Para melhor organização, use as bibliotecas de passos do Serenity:
import net.serenitybdd.annotations.Steps;
import net.serenitybdd.junit5.SerenityJUnit5Extension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(SerenityJUnit5Extension.class)
class WhenManagingShoppingCart {
@Steps
NavigationSteps navigation;
@Steps
ProductSteps products;
@Steps
CartSteps cart;
@Test
void shouldAddProductToCart() {
navigation.openHomePage();
products.searchFor("laptop");
products.selectFirstProduct();
cart.addToCart();
cart.shouldContain(1, "items");
}
}
Ciclo de Vida do Teste
Ciclo de Vida em Nível de Método
Use as anotações de ciclo de vida do JUnit 5:
import org.junit.jupiter.api.*;
@ExtendWith(SerenityJUnit5Extension.class)
class ProductTests {
@BeforeEach
void setUp() {
// Executa antes de cada método de teste
System.out.println("Configurando teste");
}
@AfterEach
void tearDown() {
// Executa após cada método de teste
System.out.println("Limpando teste");
}
@BeforeAll
static void setUpClass() {
// Executa uma vez antes de todos os testes nesta classe
System.out.println("Configurando classe de teste");
}
@AfterAll
static void tearDownClass() {
// Executa uma vez após todos os testes nesta classe
System.out.println("Limpando classe de teste");
}
@Test
void test1() {
// Código do teste
}
@Test
void test2() {
// Código do teste
}
}
Ordem de Execução
setUpClass()
setUp()
test1()
tearDown()
setUp()
test2()
tearDown()
tearDownClass()
Nomes de Exibição e Organização
Nomes de Exibição Personalizados
Torne os nomes dos testes mais legíveis nos relatórios:
import org.junit.jupiter.api.DisplayName;
@ExtendWith(SerenityJUnit5Extension.class)
@DisplayName("Gerenciamento do Carrinho de Compras")
class ShoppingCartTests {
@Test
@DisplayName("Deve adicionar produto ao carrinho vazio")
void addToEmptyCart() {
// Código do teste
}
@Test
@DisplayName("Deve atualizar quantidade do produto existente")
void updateQuantity() {
// Código do teste
}
@Test
@DisplayName("Deve remover produto do carrinho")
void removeProduct() {
// Código do teste
}
}
Organizando testes com @Feature e @Story
Você pode usar as anotações @Feature e @Story para organizar seus testes em uma hierarquia de requisitos nos relatórios do Serenity. Isso oferece controle explícito sobre como os testes aparecem na Documentação Viva, independentemente da estrutura de pacotes:
import net.serenitybdd.annotations.Feature;
import net.serenitybdd.annotations.Story;
@ExtendWith(SerenityJUnit5Extension.class)
@Feature("Shopping Cart")
@Story("Add item to cart")
@DisplayName("When adding items to the shopping cart")
class WhenAddingItemsTest {
@Test
@DisplayName("Should add a single item")
void addSingleItem() { /* ... */ }
@Test
@DisplayName("Should update quantity for duplicate items")
void updateQuantityForDuplicates() { /* ... */ }
}
Quando @Story não está presente, o valor de @DisplayName é usado como nome da story na hierarquia de requisitos. Isso significa que você pode escrever:
@Feature("Shopping Cart")
@DisplayName("Add item to cart") // Usado como nome da story
class WhenAddingItemsTest { /* ... */ }
Consulte Requisitos Baseados em Anotações para um guia completo cobrindo a hierarquia @Epic > @Feature > @Story, herança e migração de anotações baseadas em classes.
Testes Aninhados
Organize testes relacionados com @Nested:
import org.junit.jupiter.api.Nested;
@ExtendWith(SerenityJUnit5Extension.class)
@DisplayName("Autenticação de Usuário")
class AuthenticationTests {
@Nested
@DisplayName("Ao fazer login")
class Login {
@Test
@DisplayName("Deve ter sucesso com credenciais válidas")
void validCredentials() {
// Código do teste
}
@Test
@DisplayName("Deve falhar com senha inválida")
void invalidPassword() {
// Código do teste
}
@Test
@DisplayName("Deve falhar com usuário inexistente")
void nonExistentUser() {
// Código do teste
}
}
@Nested
@DisplayName("Ao fazer logout")
class Logout {
@Test
@DisplayName("Deve limpar a sessão")
void clearSession() {
// Código do teste
}
@Test
@DisplayName("Deve redirecionar para a página de login")
void redirectToLogin() {
// Código do teste
}
}
}
Classes Aninhadas e Anotações de Requisitos
Classes @Nested herdam as anotações @Epic, @Feature e @Story da sua classe envolvente. Isso permite definir a funcionalidade uma vez na classe externa e atribuir histórias individuais a cada classe aninhada:
@ExtendWith(SerenityJUnit5Extension.class)
@Feature("User Authentication")
class AuthenticationTests {
@Nested
@Story("Login")
class WhenLoggingIn {
@Test
void shouldSucceedWithValidCredentials() { /* ... */ }
}
@Nested
@Story("Logout")
class WhenLoggingOut {
@Test
void shouldClearSession() { /* ... */ }
}
}
Isso produz a hierarquia de requisitos:
User Authentication (feature)
├── Login (story)
│ └── Should succeed with valid credentials
└── Logout (story)
└── Should clear session
Consulte Requisitos Baseados em Anotações para os detalhes completos sobre herança de anotações com classes aninhadas.
Testes Parametrizados
Testes Parametrizados Simples
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@ExtendWith(SerenityJUnit5Extension.class)
class SearchTests {
@Steps
SearchSteps search;
@ParameterizedTest
@ValueSource(strings = {"laptop", "phone", "tablet"})
@DisplayName("Deve encontrar produtos para o termo de busca: {0}")
void shouldFindProducts(String searchTerm) {
search.searchFor(searchTerm);
search.shouldSeeResults();
}
}
CSV Source
import org.junit.jupiter.params.provider.CsvSource;
@ParameterizedTest
@CsvSource({
"admin, admin123, Dashboard",
"user, user123, User Home",
"guest, guest123, Guest Portal"
})
@DisplayName("Deve fazer login como {0} e ver {2}")
void shouldLoginSuccessfully(String username, String password, String expectedPage) {
login.as(username, password);
navigation.shouldBeOn(expectedPage);
}
Method Source
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
@ParameterizedTest
@MethodSource("provideTestData")
@DisplayName("Deve processar pedido para {0}")
void shouldProcessOrder(Order order) {
checkout.process(order);
checkout.shouldShowConfirmation();
}
static Stream<Order> provideTestData() {
return Stream.of(
new Order("Product1", 2, 29.99),
new Order("Product2", 1, 49.99),
new Order("Product3", 5, 9.99)
);
}
Tags e Filtros
Marcando Testes com Tags
Use @Tag para categorizar testes:
import org.junit.jupiter.api.Tag;
@ExtendWith(SerenityJUnit5Extension.class)
class ProductTests {
@Test
@Tag("smoke")
@Tag("fast")
void quickSanityCheck() {
// Teste de fumaça rápido
}
@Test
@Tag("regression")
@Tag("slow")
void comprehensiveTest() {
// Teste de regressão completo
}
@Test
@Tag("wip")
void workInProgress() {
// Teste em desenvolvimento
}
}
Executando Testes com Tags
Maven:
# Executar apenas testes de fumaça
mvn test -Dgroups=smoke
# Executar smoke OU regression
mvn test -Dgroups="smoke | regression"
# Executar smoke E fast
mvn test -Dgroups="smoke & fast"
# Excluir testes wip
mvn test -DexcludedGroups=wip
junit-platform.properties:
junit.jupiter.includeTags=smoke
junit.jupiter.excludeTags=wip,slow
Execução Condicional de Testes
Testes Específicos por Sistema Operacional
import org.junit.jupiter.api.condition.*;
@Test
@EnabledOnOs(OS.WINDOWS)
void runOnlyOnWindows() {
// Teste específico para Windows
}
@Test
@DisabledOnOs(OS.MAC)
void dontRunOnMac() {
// Teste desabilitado no macOS
}
Específicos por Versão do Java
@Test
@EnabledOnJre(JRE.JAVA_17)
void runOnlyOnJava17() {
// Teste específico para Java 17
}
@Test
@EnabledForJreRange(min = JRE.JAVA_17, max = JRE.JAVA_21)
void runOnJava17To21() {
// Versões modernas do Java
}
Condições Personalizadas
@Test
@EnabledIf("isProductionEnvironment")
void runOnlyInProduction() {
// Teste apenas em produção
}
boolean isProductionEnvironment() {
return System.getProperty("env", "dev").equals("prod");
}