Capítulo 1: Introdução aos Testes
Introdução
Antes de escrever seu primeiro teste, é crucial entender por que e como testar de forma eficaz. Este capítulo estabelece as bases para uma estratégia de testes profissional.
🎯 Objetivos do Capítulo
Ao final deste capítulo, você saberá:
- ✅ Por que os testes são essenciais
- ✅ Distinguir os diferentes tipos de testes
- ✅ Compreender a pirâmide de testes
- ✅ Configurar seu projeto para testes
- ✅ Conhecer as boas práticas
1. Por que Testar?
O Custo dos Bugs
| Momento da Detecção | Custo Relativo | Impacto |
|---|---|---|
| Durante o desenvolvimento | 1x | Nenhum |
| Em fase de teste | 10x | Atraso |
| Em produção | 100x | Cliente impactado, urgência |
Benefícios dos Testes
✅ Confiança: O código faz o que deveria fazer
✅ Regressão: Detecta bugs introduzidos por mudanças
✅ Documentação: Os testes explicam o comportamento esperado
✅ Design: Escrever testes força um bom design (SOLID)
✅ Refatoração: Modificar o código sem medo
✅ Deploy: CI/CD automatizado
Sem Testes vs Com Testes
// Sem testes 😰
public void deployToProduction() {
// "Espero que funcione..."
// "Vamos ver em produção..."
// "Dedos cruzados..."
}
// Com testes 😎
public void deployToProduction() {
// ✅ 347 testes passaram
// ✅ Cobertura: 85%
// ✅ Todos os quality gates passaram
// → Deploy com confiança!
}
2. Tipos de Testes
Visão Geral
Testes Unitários
O quê: Testar uma unidade de código isoladamente (método, classe)
Características:
- Rápidos (milissegundos)
- Isolados (sem banco de dados, sem rede)
- Numerosos (maioria dos testes)
@Test
void shouldCalculatePriceWithDiscount() {
// Testa SOMENTE a lógica de cálculo
PriceCalculator calculator = new PriceCalculator();
double result = calculator.applyDiscount(100.0, 20);
assertEquals(80.0, result);
}
Testes de Integração
O quê: Testar vários componentes juntos
Características:
- Mais lentos (segundos)
- Usam recursos reais (banco de dados, etc.)
- Menos numerosos
@SpringBootTest
class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@Test
void shouldSaveOrderToDatabase() {
// Testa o service + repository + banco de dados
Order order = orderService.createOrder(/*...*/);
assertNotNull(order.getId());
}
}
Testes End-to-End (E2E)
O quê: Testar a aplicação completa como um usuário
Características:
- Lentos (minutos)
- Ambiente completo
- Poucos testes
// Com Selenium, por exemplo
@Test
void userShouldBeAbleToCompleteCheckout() {
browser.navigate("/products");
browser.click("#add-to-cart");
browser.click("#checkout");
browser.fill("#address", "123 Main St");
browser.click("#confirm");
assertThat(browser.getText("#confirmation"))
.contains("Order confirmed");
}
3. A Pirâmide de Testes
Estrutura Ideal
/\
/ \
/ E2E\ Poucos, lentos, frágeis
/ \
/────────\
/Integração\ Moderado
/ \
/────────────────\
/ Testes Unitários \ Muitos, rápidos, estáveis
/____________________\
Proporções Recomendadas
| Tipo | Proporção | Tempo de Execução |
|---|---|---|
| Unitários | 70% | ~5 segundos |
| Integração | 20% | ~30 segundos |
| E2E | 10% | ~5 minutos |
Anti-Pattern: O Sorvete de Casquinha
____________________
/ Muitos E2E \ ❌ Lentos, frágeis
/────────────────────────\
/ Pouca integração \
/────────────────────────────\
/ Poucos testes \
/ unitários \
/──────────────────────────────────\
❌ RUIM: Testes lentos, manutenção difícil
4. TDD e BDD
TDD (Test-Driven Development)
Escrever os testes ANTES do código:
// Etapa 1: RED - O teste falha (o método não existe)
@Test
void shouldReturnEmptyListWhenNoProducts() {
ProductService service = new ProductService();
List<Product> products = service.findAll();
assertTrue(products.isEmpty());
}
// Etapa 2: GREEN - Implemente o mínimo
public List<Product> findAll() {
return new ArrayList<>();
}
// Etapa 3: REFACTOR - Melhore se necessário
BDD (Behavior-Driven Development)
Testes que descrevem o comportamento em linguagem natural:
// Formato Given-When-Then
@Test
void givenNewUser_whenRegister_thenAccountIsCreated() {
// Given (Contexto)
RegistrationRequest request = new RegistrationRequest(
"john", "john@email.com", "password123"
);
// When (Ação)
User user = userService.register(request);
// Then (Resultado esperado)
assertNotNull(user.getId());
assertEquals("john", user.getUsername());
assertTrue(user.isEnabled());
}
5. Configuração Maven
Dependências de Teste
<!-- pom.xml -->
<dependencies>
<!-- Spring Boot Test (inclui JUnit 5, Mockito, AssertJ) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- H2 para testes -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Security Test (opcional) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
O que está incluído no spring-boot-starter-test
spring-boot-starter-test inclui:
├── JUnit 5 (Jupiter)
├── Mockito
├── AssertJ
├── Hamcrest
├── JSONPath
├── JsonAssert
└── Spring Test
Estrutura dos Arquivos de Teste
src/
├── main/
│ └── java/
│ └── com/example/
│ ├── service/
│ │ └── ProductService.java
│ └── repository/
│ └── ProductRepository.java
└── test/
└── java/
└── com/example/
├── service/
│ └── ProductServiceTest.java ← Testes unitários
├── repository/
│ └── ProductRepositoryTest.java ← Testes de integração
└── controller/
└── ProductControllerTest.java ← Testes de API
6. Configuração de Teste
application-test.properties
# src/test/resources/application-test.properties
# Banco de dados H2 em memória
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop
# Desativar logs verbosos
logging.level.org.hibernate.SQL=WARN
spring.jpa.show-sql=false
# Desativar Flyway para testes (opcional)
spring.flyway.enabled=false
Ativar o Perfil de Teste
@SpringBootTest
@ActiveProfiles("test") // ← Usa application-test.properties
class MyIntegrationTest {
// ...
}
7. Executar os Testes
Via Maven
# Todos os testes
mvn test
# Testes com relatório
mvn test jacoco:report
# Um teste específico
mvn test -Dtest=ProductServiceTest
# Um método específico
mvn test -Dtest=ProductServiceTest#shouldFindProductById
# Skip nos testes (evite!)
mvn install -DskipTests
Via IDE
IntelliJ IDEA:
- Clique direito na classe/método → Run Test
- Ctrl+Shift+F10 (Windows)
- Ctrl+Shift+R (Mac)
VS Code:
- Ícone "Run Test" acima do @Test
- Test Explorer na barra lateral
8. Boas Práticas
Naming Convention
// Formato recomendado: should_ExpectedBehavior_When_StateUnderTest
@Test
void should_ReturnEmpty_When_NoProductsExist() { }
// Ou: given_When_Then
@Test
void given_ValidProduct_When_Save_Then_IdIsGenerated() { }
// Evite:
@Test
void test1() { } // ❌ Não descritivo
@Test
void testProductService() { } // ❌ Muito vago
Padrão AAA (Arrange-Act-Assert)
@Test
void shouldCalculateTotalPrice() {
// Arrange (Preparar)
ShoppingCart cart = new ShoppingCart();
cart.addItem(new Item("Laptop", 1000.0));
cart.addItem(new Item("Mouse", 50.0));
// Act (Executar)
double total = cart.calculateTotal();
// Assert (Verificar)
assertEquals(1050.0, total);
}
Um Teste = Um Conceito
// ❌ RUIM: Testa várias coisas
@Test
void testProduct() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
assertThrows(Exception.class, () -> p.applyDiscount(-5));
}
// ✅ BOM: Um teste por comportamento
@Test
void shouldCreateProductWithNameAndPrice() {
Product p = new Product("Test", 100.0);
assertEquals("Test", p.getName());
assertEquals(100.0, p.getPrice());
}
@Test
void shouldApplyDiscountCorrectly() {
Product p = new Product("Test", 100.0);
p.applyDiscount(10);
assertEquals(90.0, p.getPrice());
}
@Test
void shouldRejectNegativeDiscount() {
Product p = new Product("Test", 100.0);
assertThrows(IllegalArgumentException.class,
() -> p.applyDiscount(-5));
}
9. Resumo
Pontos-Chave
| Conceito | Descrição |
|---|---|
| Teste Unitário | Testa uma unidade isoladamente |
| Teste Integração | Testa vários componentes juntos |
| Teste E2E | Testa a aplicação completa |
| TDD | Testes escritos antes do código |
| AAA | Arrange-Act-Assert |
Pirâmide de Testes
10% E2E (lentos, frágeis)
20% Integração (moderados)
70% Unitários (rápidos, numerosos)
Checklist
✅ spring-boot-starter-test no pom.xml
✅ application-test.properties configurado
✅ Testes em src/test/java
✅ Nomes descritivos
✅ Um teste = um conceito
✅ Padrão AAA
🎯 Próximo Passo
Hora da prática! Vamos explorar o JUnit 5 em detalhes.
➡️ Capítulo 2: Testes Unitários com JUnit 5