Vai al contenuto

JUnit Comprehensive Cheatsheet

Installazione

Tabella_139_

Core Annotations (JUnit 5)

TABELLA: 140_

Asserzioni di base

Tabella_141_

Asserzioni avanzate

Tabella_142_

Annotazioni di prova parametrizzate

TABELLA

Comandi per anziani

Tabella_144_

Comandi di livello

Tabella_145

Configurazione

# Maven Surefire Plugin Configurazione

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.2</version>
    <configuration>
        <!-- Include/exclude test patterns -->
        <includes>
            <include>**/*Test.java</include>
            <include>**/*Tests.java</include>
        </includes>
        <excludes>
            <exclude>**/*IntegrationTest.java</exclude>
        </excludes>

        <!-- Parallel execution -->
        <parallel>methods</parallel>
        <threadCount>4</threadCount>

        <!-- Tag filtering -->
        <groups>unit, integration</groups>
        <excludedGroups>slow</excludedGroups>

        <!-- System properties -->
        <systemPropertyVariables>
            <env>test</env>
        </systemPropertyVariables>
    </configuration>
</plugin>

Configurazione del test di grado

test {
    useJUnitPlatform {
        // Include/exclude tags
        includeTags 'unit', 'integration'
        excludeTags 'slow'

        // Include/exclude engines
        includeEngines 'junit-jupiter'
        excludeEngines 'junit-vintage'
    }

    // Parallel execution
    maxParallelForks = 4

    // Test filtering
    filter {
        includeTestsMatching '*Test'
        excludeTestsMatching '*IntegrationTest'
    }

    // System properties
    systemProperty 'env', 'test'

    // Test logging
    testLogging {
        events "passed", "skipped", "failed"
        exceptionFormat "full"
        showStandardStreams = true
    }

    // Fail fast
    failFast = true
}

JUnit Platform Properties (junit-platform.properties)

# Parallel execution
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 4

# Test instance lifecycle
junit.jupiter.testinstance.lifecycle.default = per_class

# Display name generation
junit.jupiter.displayname.generator.default = org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

# Automatic extension detection
junit.jupiter.extensions.autodetection.enabled = true

# Deactivate conditions
junit.jupiter.conditions.deactivate = org.junit.*DisabledCondition

Common Use Cases

Use Case: Basic Unit Test with Setup and Teardown

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;

class UserServiceTest {
    private UserService userService;
    private Database mockDb;

    @BeforeEach
    void setUp() {
        mockDb = new MockDatabase();
        userService = new UserService(mockDb);
    }

    @Test
    @DisplayName("Should create user with valid data")
    void testCreateUser() {
        User user = new User("john@example.com", "password123");
        User created = userService.createUser(user);

        assertNotNull(created.getId());
        assertEquals("john@example.com", created.getEmail());
    }

    @AfterEach
    void tearDown() {
        mockDb.close();
    }
}

Use Case: Parameterized Testing for Multiple Inputs

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.*;

class CalculatorTest {

    @ParameterizedTest
    @CsvSource({
        "1, 1, 2",
        "2, 3, 5",
        "10, -5, 5",
        "0, 0, 0"
    })
    void testAddition(int a, int b, int expected) {
        Calculator calc = new Calculator();
        assertEquals(expected, calc.add(a, b));
    }

    @ParameterizedTest
    @ValueSource(strings = {"", "  ", "\t", "\n"})
    void testBlankStrings(String input) {
        assertTrue(StringUtils.isBlank(input));
    }

    @ParameterizedTest
    @MethodSource("provideEmailTestCases")
    void testEmailValidation(String email, boolean expected) {
        assertEquals(expected, EmailValidator.isValid(email));
    }

    static Stream<Arguments> provideEmailTestCases() {
        return Stream.of(
            Arguments.of("test@example.com", true),
            Arguments.of("invalid.email", false),
            Arguments.of("@example.com", false)
        );
    }
}

Use Case: Eccezione Testing and Timeout Verification

import org.junit.jupiter.api.Test;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.*;

class PaymentServiceTest {

    @Test
    void shouldThrowExceptionForInvalidAmount() {
        PaymentService service = new PaymentService();

        Exception exception = assertThrows(
            IllegalArgumentException.class,
            () -> service.processPayment(-100)
        );

        assertEquals("Amount must be positive", exception.getMessage());
    }

    @Test
    void shouldCompleteWithinTimeout() {
        PaymentService service = new PaymentService();

        assertTimeout(Duration.ofSeconds(2), () -> {
            service.processPayment(100);
        });
    }

    @Test
    void shouldNotThrowExceptionForValidPayment() {
        PaymentService service = new PaymentService();

        assertDoesNotThrow(() -> {
            service.processPayment(100);
        });
    }
}

Use Case: Nested Tests for Organized Test Structure

import org.junit.jupiter.api.*;

@DisplayName("Shopping Cart Tests")
class ShoppingCartTest {

    @Nested
    @DisplayName("When cart is empty")
    class EmptyCart {
        private ShoppingCart cart;

        @BeforeEach
        void setUp() {
            cart = new ShoppingCart();
        }

        @Test
        @DisplayName("Should have zero items")
        void testItemCount() {
            assertEquals(0, cart.getItemCount());
        }

        @Test
        @DisplayName("Should have zero total")
        void testTotal() {
            assertEquals(0.0, cart.getTotal());
        }
    }

    @Nested
    @DisplayName("When cart has items")
    class CartWithItems {
        private ShoppingCart cart;

        @BeforeEach
        void setUp() {
            cart = new ShoppingCart();
            cart.addItem(new Item("Book", 29.99));
            cart.addItem(new Item("Pen", 2.50));
        }

        @Test
        @DisplayName("Should calculate correct total")
        void testTotal() {
            assertEquals(32.49, cart.getTotal(), 0.01);
        }

        @Test
        @DisplayName("Should allow item removal")
        void testRemoveItem() {
            cart.removeItem(0);
            assertEquals(1, cart.getItemCount());
        }
    }
}

Use Case: Integration Testing with Test Lifecycle Management

import org.junit.jupiter.api.*;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DatabaseIntegrationTest {
    private static DatabaseConnection connection;
    private UserRepository userRepository;

    @BeforeAll
    void setUpDatabase() {
        // Expensive one-time setup
        connection = DatabaseConnection.connect("jdbc:h2:mem:test");
        connection.runMigrations();
    }

    @BeforeEach
    void setUp() {
        userRepository = new UserRepository(connection);
        connection.beginTransaction();
    }

    @Test
    @Tag("integration")
    void testUserPersistence() {
        User user = new User("test@example.com");
        userRepository.save(user);

        User retrieved = userRepository.findById(user.getId());
        assertEquals(user.getEmail(), retrieved.getEmail());
    }

    @AfterEach
    void tearDown() {
        connection.rollbackTransaction();
    }

    @AfterAll
    void tearDownDatabase() {
        connection.close();
    }
}

Migliori Pratiche

  • Utilizzare i nomi dei test descrittivi: Leverage @DisplayName per rendere chiari gli obiettivi di prova. I nomi dei test dovrebbero descrivere ciò che viene testato e il risultato atteso (ad esempio, "Dovreste gettare l'eccezione quando la quantità è negativa").

  • Follow AAA pattern: Test struttura con Arrange (setup), Act (execute), e Assert (verificare) sezioni per chiarezza e manutenbilità.

Una cosa per prova? Ogni metodo di prova deve verificare un singolo comportamento o uno scenario. Questo rende i guasti più facili da diagnosticare e i test più facili da mantenere.

  • Usi @BeforeEach e @AfterEach saggiamente**: Inizializzare i dispositivi di prova comuni in @BeforeEach ma evitare l'installazione eccessiva che rende i test difficili da capire. Pulire le risorse in @AfterEach_.

  • Preferire @Parameterized Test per più casi simili: Invece di scrivere più metodi di test simili, utilizzare test parametrizzati per testare la stessa logica con diversi input.

  • Utilizzare tutte le affermazioni relative: Asserzioni relative al gruppo con assertAll() per vedere tutti i guasti in una sola volta piuttosto che fermarsi al primo fallimento.

  • Tag test appropriatamente: Usa @Tag per classificare i test (unità, integrazione, lenta) in modo da poter eseguire selettivamente suite di test in diversi ambienti o fasi CI/CD.

  • ** Interdipendenze di test avoidi ** I test dovrebbero essere indipendenti e in grado di eseguire in qualsiasi ordine. Mai contare su ordine di esecuzione o stato da altri test.

  • Utilizzare messaggi di affermazione significativi: Fornire messaggi di errore personalizzati alle affermazioni per rendere più facile il debug: - Ok.

  • I test sono veloci. I test unitari dovrebbero essere eseguiti rapidamente. Spostare i test lenti (database, rete) per separare le suite di test di integrazione utilizzando tag o set sorgente separati.

Risoluzione dei problemi

Traduzione: I test di culla sempre UP-TO-DATE | Usa gradle cleanTest test o gradle test --rerun-tasks_ per forzare l'esecuzione del test. Controllare se i file sorgente di prova sono nella directory corretta (src/test/java). #