Testing & Best Practices — Cheat Sheet
Java A–Z · 6 topics. Download the PDF or the Instagram carousel and share it.
Cheat Sheet · AiCanCode.org
Testing & Best Practices
Java A–Z6 topicsQuick revision reference
1
JUnit 5 Testing
- ✓@BeforeEach / @AfterEach run per test method; @BeforeAll / @AfterAll run once per class.
- ✓@ParameterizedTest with @CsvSource or @MethodSource eliminates repetitive test methods.
- ✓assertAll() runs all assertions even if earlier ones fail — better for compound checks.
- ✓@Nested classes group related tests and can have their own @BeforeEach setup.
- ✓AssertJ provides fluent assertions with far better error messages than raw JUnit assertions.
CalculatorTest.java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator(); // fresh instance per test
}
@AfterEach
void tearDown() {
// cleanup if needed
}
@Test
@DisplayName("Adding two positive numbers returns their sum")
void addPositiveNumbers() {
assertEquals(5, calc.add(2, 3));
}
@Test
void divideByZeroThrows() {
assertThrows(ArithmeticException.class,
() -> calc.divide(10, 0));
}
@Test
void multipleAssertions() {
assertAll("calculator",
() -> assertEquals(4, calc.add(2, 2)),
() -> assertEquals(0, calc.subtract(5, 5)),
() -> assertEquals(6, calc.multiply(2, 3))
); // all run even if one fails
}
@Test
@Disabled("Calculator.sqrt not implemented yet")
void sqrt() { /* ... */ }
}2
Mockito and Test Doubles
- ✓@Mock creates a mock; @InjectMocks creates the class under test and injects mocks.
- ✓when().thenReturn() stubs; verify() asserts interactions; ArgumentCaptor captures arguments.
- ✓Spies wrap real objects — real methods run by default, selective stubbing possible.
- ✓Use doReturn/doThrow for void methods and spies to avoid calling the real method.
- ✓Strict stubbing (MockitoExtension default) fails on unused stubs — keeps tests clean.
MockitoBasics.java
import org.mockito.*;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository repo; // auto-created mock
@Mock
EmailService emailService;
@InjectMocks
UserService service; // repo + emailService injected automatically
@Test
void findByIdReturnsUser() {
// Stub: when repo.findById(42) is called, return this user
User alice = new User(42, "Alice", "alice@example.com");
when(repo.findById(42)).thenReturn(Optional.of(alice));
User result = service.findById(42);
assertThat(result.getName()).isEqualTo("Alice");
}
@Test
void findByIdThrowsWhenNotFound() {
when(repo.findById(anyInt())).thenReturn(Optional.empty());
assertThatThrownBy(() -> service.findById(99))
.isInstanceOf(UserNotFoundException.class);
}
}3
Java Security
- ✓Use PreparedStatement always — SQL injection is the #1 Java vulnerability.
- ✓Validate and normalise file paths before use to prevent path traversal.
- ✓Use PBKDF2, bcrypt, or Argon2 for passwords — never plain SHA or MD5.
- ✓SecureRandom is the only acceptable random source for security tokens and salts.
- ✓Never trust user input, never hard-code secrets, always apply least privilege.
Vulnerabilities.java
// SQL INJECTION — NEVER do this
String user = request.getParameter("user");
String sql = "SELECT * FROM users WHERE name = '" + user + "'";
// user = "'; DROP TABLE users; --" → catastrophic
// SAFE — PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM users WHERE name = ?");
ps.setString(1, user); // parameterised — injection impossible
// PATH TRAVERSAL — validate file paths
String filename = request.getParameter("file");
// BAD: Files.readString(Path.of("/uploads/" + filename))
// filename = "../../../etc/passwd" → reads sensitive file
// SAFE — normalise and validate
Path base = Path.of("/uploads").toRealPath();
Path requested = base.resolve(filename).normalize().toRealPath();
if (!requested.startsWith(base)) {
throw new SecurityException("Path traversal detected");
}
// DESERIALIZATION — whitelist allowed classes
ObjectInputStream ois = new ObjectInputStream(fis);
ois.setObjectInputFilter(
ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.util.*;!*")); // only allow safe classes4
Logging in Java
- ✓Use SLF4J as the logging facade — never import Logback/Log4j2 classes in application code.
- ✓Parameterised logging ({}) avoids string concatenation when the level is disabled.
- ✓Always log exceptions as the last argument to include the full stack trace.
- ✓MDC adds request-scoped context (traceId, userId) to every log line — clear it in finally.
- ✓Use async appenders in production to prevent slow I/O from blocking application threads.
SLF4JBasics.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderService {
// One logger per class — static final
private static final Logger log =
LoggerFactory.getLogger(OrderService.class);
public Order placeOrder(OrderRequest req) {
log.debug("Placing order for user={} item={}", req.getUserId(), req.getItemId());
try {
Order order = processOrder(req);
log.info("Order placed orderId={} userId={} amount={}",
order.getId(), req.getUserId(), order.getAmount());
return order;
} catch (InsufficientStockException e) {
log.warn("Stock insufficient for item={} requested={}",
req.getItemId(), req.getQuantity());
throw e;
} catch (Exception e) {
// Always log exception with message — not just e.getMessage()
log.error("Failed to place order for user={}", req.getUserId(), e);
throw new OrderException("Order processing failed", e);
}
}
}5
Java Best Practices
- ✓Make classes and members as private as possible — expand access only when required.
- ✓Return empty collections (List.of()) instead of null — prevents NullPointerException.
- ✓Prefer composition over inheritance for code reuse — inheritance is for true IS-A relationships.
- ✓Optional is for return types only — not fields, parameters, or collection elements.
- ✓Exceptions must include enough context to diagnose without needing to reproduce the bug.
APIDesign.java
// BAD — public mutable fields, no validation
public class Range {
public int start;
public int end;
}
// GOOD — encapsulated, validated, immutable, fluent factory
public final class Range {
private final int start;
private final int end;
private Range(int start, int end) {
if (start > end) throw new IllegalArgumentException(
"start (%d) must be <= end (%d)".formatted(start, end));
this.start = start;
this.end = end;
}
public static Range of(int start, int end) {
return new Range(start, end);
}
public int start() { return start; }
public int end() { return end; }
public int length() { return end - start; }
public boolean contains(int value) {
return value >= start && value < end;
}
// Return empty collection, not null
public List<Integer> toList() {
if (start >= end) return List.of();
return IntStream.range(start, end).boxed().collect(Collectors.toList());
}
}6
Performance and Profiling
- ✓Measure first — profiler before optimiser. Guessing at bottlenecks wastes time.
- ✓JMH is the only reliable Java micro-benchmark tool — handles warm-up and dead code elimination.
- ✓async-profiler provides accurate CPU/allocation/lock flame graphs with minimal overhead.
- ✓Algorithm and data structure choices have far more impact than micro-optimisations.
- ✓Common traps: autoboxing in loops, regex per call, N+1 queries, excessive logging in hot paths.
JMHBenchmark.java
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Fork(value = 2, warmups = 1)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 10, time = 1)
public class StringBenchmark {
@Param({"10", "100", "1000"})
private int size;
private String[] data;
@Setup
public void setUp() {
data = IntStream.range(0, size)
.mapToObj(Integer::toString)
.toArray(String[]::new);
}
@Benchmark
public void concatenationPlus(Blackhole bh) {
String result = "";
for (String s : data) result += s;
bh.consume(result); // prevents dead code elimination
}
@Benchmark
public void stringBuilder(Blackhole bh) {
StringBuilder sb = new StringBuilder();
for (String s : data) sb.append(s);
bh.consume(sb.toString());
}
}
// Run: java -jar benchmarks.jar StringBenchmarkLearn this free with Aria, your AI tutor → AiCanCode.org/learn/java