This document describes the testing strategy and practices for the FlossWare Platform.
Tests are categorized using JUnit 5 @Tag annotations for selective execution.
| Tag | Description | Examples |
|---|---|---|
unit |
Fast, isolated unit tests | Method logic, validation, builders |
integration |
Tests with external dependencies | Database, network, file I/O |
security |
Security-focused tests | Input validation, auth, encryption |
performance |
Performance and load tests | Benchmarks, stress tests |
slow |
Long-running tests (>1 second) | Large data processing, retries |
mvn testmvn test -Dtest.groups=unitmvn test -Dtest.groups=integrationmvn test -Dtest.groups=securitymvn test -Dtest.excludedGroups=slow# Run unit OR security tests
mvn test -Dtest.groups="unit | security"# Run tests that are BOTH unit AND security
mvn test -Dtest.groups="unit & security"# Run integration tests but exclude slow ones
mvn test -Dtest.groups=integration -Dtest.excludedGroups=slowimport org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@Tag("unit")
class MyServiceTest {
@Test
void shouldCalculateCorrectly() {
// Arrange
MyService service = new MyService();
// Act
int result = service.calculate(5, 3);
// Assert
assertEquals(8, result);
}
}@Tag("integration")
@Tag("slow")
class DatabaseIntegrationTest {
@Test
void shouldConnectToDatabase() {
// Test implementation
}
}@Tag("unit")
@Tag("security")
class InputValidationTest {
@Test
void shouldRejectPathTraversal() {
assertThrows(SecurityException.class, () -> {
validator.validate("../../../etc/passwd");
});
}
}Use descriptive names following this pattern:
// Pattern: should[ExpectedBehavior]When[StateUnderTest]
@Test
void shouldThrowExceptionWhenInputIsNull() { }
@Test
void shouldReturnEmptyListWhenDatabaseIsEmpty() { }
@Test
void shouldCalculateDiscountWhenUserIsPremium() { }// Pattern: [ClassName]Test
class UserServiceTest { }
class ApplicationManagerTest { }
class SecurityValidatorTest { }- Minimum: 60% overall coverage (enforced by JaCoCo)
- Target: 80%+ for critical modules
- Security Code: 90%+ coverage required
# Generate coverage report
mvn clean test jacoco:report
# View report
open target/site/jacoco/index.html# Run coverage for specific module
cd platform-core
mvn clean test jacoco:reportUse @TempDir for temporary file/directory tests:
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
class FileProcessorTest {
@TempDir
Path tempDir;
@Test
void shouldProcessFile() {
Path testFile = tempDir.resolve("test.txt");
Files.writeString(testFile, "content");
// Test with testFile
}
}@BeforeEach
void setUp() {
// Initialize test data
testUser = new User("user123", "Test User");
}
@AfterEach
void tearDown() {
// Clean up resources
}// Equality
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);
// Boolean
assertTrue(condition);
assertFalse(condition);
// Null checks
assertNull(value);
assertNotNull(value);
// Exceptions
assertThrows(IllegalArgumentException.class, () -> {
service.methodThatThrows();
});
// Timeout
assertTimeout(Duration.ofSeconds(1), () -> {
// Fast operation
});
// Collections
assertIterableEquals(expectedList, actualList);assertEquals(expected, actual,
"User ID should match the created user");Test multiple inputs efficiently:
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
@Tag("unit")
class ParameterizedExampleTest {
@ParameterizedTest
@ValueSource(strings = {"user1", "user2", "user3"})
void shouldValidateUsername(String username) {
assertTrue(validator.isValid(username));
}
@ParameterizedTest
@CsvSource({
"1, 2, 3",
"5, 5, 10",
"10, -5, 5"
})
void shouldAddNumbers(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
}Use Mockito for mocking dependencies:
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class ServiceWithDependenciesTest {
@Mock
private DatabaseRepository repository;
@Test
void shouldCallRepository() {
when(repository.findById("123"))
.thenReturn(Optional.of(testUser));
User result = service.getUser("123");
verify(repository).findById("123");
assertEquals(testUser, result);
}
}All tests run by default:
- name: Run tests
run: mvn clean verify- name: Quick test
run: mvn test -Dtest.groups=unit- name: Unit tests
run: mvn test -Dtest.groups=unit
- name: Integration tests
run: mvn test -Dtest.groups=integration
- name: Security tests
run: mvn test -Dtest.groups=security- name: Full test suite
run: mvn clean verifyEach test should be independent and isolated:
// Good - self-contained
@Test
void shouldCalculate() {
Calculator calc = new Calculator();
assertEquals(4, calc.add(2, 2));
}
// Bad - depends on execution order
static int counter = 0;
@Test
void firstTest() {
counter = 5;
}
@Test
void secondTest() {
assertEquals(5, counter); // Fails if run alone
}// Good - tests one thing
@Test
void shouldValidateEmail() {
assertTrue(validator.isValidEmail("user@example.com"));
}
@Test
void shouldRejectInvalidEmail() {
assertFalse(validator.isValidEmail("invalid"));
}
// Avoid - tests multiple things
@Test
void emailValidation() {
assertTrue(validator.isValidEmail("user@example.com"));
assertFalse(validator.isValidEmail("invalid"));
assertThrows(Exception.class, () -> validator.isValidEmail(null));
}// Good - describes behavior
@Test
void shouldReturnNullWhenUserNotFound() { }
// Bad - unclear
@Test
void test1() { }@Test
void shouldCalculateDiscount() {
// Arrange
Order order = new Order(100.00);
DiscountCalculator calculator = new DiscountCalculator();
// Act
double discount = calculator.calculate(order);
// Assert
assertEquals(10.00, discount);
}@Test
void shouldHandleEmptyInput() { }
@Test
void shouldHandleNullInput() { }
@Test
void shouldHandleMaximumValue() { }
@Test
void shouldHandleNegativeValue() { }- Check for timezone dependencies
- Check for file path separators (Windows vs Linux)
- Verify test isolation (no shared state)
- Use
@Timeoutto catch slow tests - Avoid
Thread.sleep()- use proper synchronization - Mock time-dependent operations
# Identify uncovered code
mvn clean test jacoco:report
open target/site/jacoco/index.html
# Add tests for red/yellow highlighted code