Requirements & System Scope
Functional Scope (In-Scope)
- Metadata Annotation / Decorator Discovery: Inspecting, querying, and cataloging target test class behaviors via reflections.
- Structured Test LifeCycle: Executing hooks in fixed ordering:
BeforeAllโBeforeโTestโAfterโAfterAll. - Rigorous Assertion Library: Supporting absolute equality checks, boolean validations, and exception capture testing.
- Test Case Isolation: Guaranteeing zero state pollution by instantiating fresh test classes per method run.
Explicit Boundaries (Out-of-Scope)
- Dynamic File Scanning: Recursively parsing files on disk to find classes or directory watchers (focused purely on runtime class reflection).
- Mocking / Stubbing Frameworks: Dynamic proxy generation or bytecode manipulation to replace target system dependencies (e.g. Mockito features).
Class Diagram & Entity Relationships
Class interactions illustrating how the Runner controls Class Instances and captures Results:
Loading...
- TestResult: Contains test method name, completion status (PASS, FAIL for assertion mismatches, or ERROR for unhandled exceptions), and failure traces.
- AssertionError: Special semantic exception subclass used specifically to classify expected evaluation errors vs runtime environment crashes.
Design Patterns & SOLID Principles
- Command Pattern: Each test method is parsed as a Command object executed uniformly by the central
TestRunnerinvocation engine. - Template Method Pattern: Defines the algorithmic skeleton of a test run (setup, execute, tear down) while subclasses/target methods furnish the actual test actions.
- Single Responsibility Principle (SRP): Isolates assertion mechanisms (
Assert) from scanning execution (TestRunner) and reporting models.
Core Execution Workflows
Reflection Scanning & Hook Execution
- The test runner accepts a test class reference (e.g.,
MyTests.class). - Reflective methods scan the class to resolve annotated elements (matching
@Test,@Before, etc.). - Static class initialization hooks (
BeforeAll) run first. - For every discovered test method:
- A new target object is instantiated via its empty constructor to ensure state isolation.
- Instance setup hooks (
@Before) are invoked. - The actual test method is executed dynamically.
- Errors are checked:
AssertionErrormarks the run as FAIL; other exceptions mark it as ERROR. - Instance teardown hooks (
@After) run inside afinallyblock.
- Static cleanup hooks (
AfterAll) run at the absolute end.
Concurrency & Thread Safety Strategy
Enabling thread-safe parallel test execution:
- Thread Isolation: Instantiating test classes per run guarantees thread isolation, preventing race conditions on object properties during parallel execution.
- Thread-Safe Reporters: If test cases share progress dashboards, the reporter should serialize updates or use concurrent data collections (e.g. concurrent lists) to gather outcomes.
Complete Clean Code Blueprint
Clean reference designs demonstrating dynamic metadata scanning and lifecycle runners in Java and Python:
// โโโ JAVA BLUEPRINT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.lang.annotation.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Before {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface After {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface BeforeAll {}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface AfterAll {}
class Assert {
public static void assertEquals(Object expected, Object actual) {
if (!Objects.equals(expected, actual)) {
throw new AssertionError("Expected " + expected + " but got " + actual);
}
}
public static void assertTrue(boolean condition) {
if (!condition) {
throw new AssertionError("Expected true but got false");
}
}
public static <T extends Throwable> void assertThrows(Class<T> expectedException, Runnable executable) {
try {
executable.run();
} catch (Throwable t) {
if (expectedException.isInstance(t)) {
return; // Success
}
throw new AssertionError("Expected exception of type " + expectedException.getName() + " but got " + t.getClass().getName());
}
throw new AssertionError("Expected exception of type " + expectedException.getName() + " but none was thrown");
}
}
enum TestStatus { PASS, FAIL, ERROR }
class TestResult {
private final String name;
private final TestStatus status;
private final Throwable failureReason;
public TestResult(String name, TestStatus status, Throwable failureReason) {
this.name = name;
this.status = status;
this.failureReason = failureReason;
}
public String getName() { return name; }
public TestStatus getStatus() { return status; }
public Throwable getFailureReason() { return failureReason; }
@Override
public String toString() {
return "TestResult{name='" + name + "', status=" + status + ", reason=" + (failureReason != null ? failureReason.toString() : "None") + "}";
}
}
class TestRunner {
public List<TestResult> run(Class<?> testClass) {
List<TestResult> results = Collections.synchronizedList(new ArrayList<>());
List<Method> testMethods = new ArrayList<>();
List<Method> beforeMethods = new ArrayList<>();
List<Method> afterMethods = new ArrayList<>();
List<Method> beforeAllMethods = new ArrayList<>();
List<Method> afterAllMethods = new ArrayList<>();
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) testMethods.add(method);
if (method.isAnnotationPresent(Before.class)) beforeMethods.add(method);
if (method.isAnnotationPresent(After.class)) afterMethods.add(method);
if (method.isAnnotationPresent(BeforeAll.class)) beforeAllMethods.add(method);
if (method.isAnnotationPresent(AfterAll.class)) afterAllMethods.add(method);
}
try {
// Run BeforeAll
for (Method m : beforeAllMethods) {
m.invoke(null); // Expect static methods
}
if (!testMethods.isEmpty()) {
java.util.concurrent.ExecutorService executor = java.util.concurrent.Executors.newFixedThreadPool(4);
try {
java.util.concurrent.CountDownLatch latch = new java.util.concurrent.CountDownLatch(testMethods.size());
for (Method testMethod : testMethods) {
executor.submit(() -> {
try {
Object instance = testClass.getDeclaredConstructor().newInstance();
Throwable failure = null;
TestStatus status = TestStatus.PASS;
try {
// Run Before
for (Method before : beforeMethods) {
before.invoke(instance);
}
// Run Test
testMethod.invoke(instance);
} catch (InvocationTargetException ite) {
Throwable cause = ite.getCause();
if (cause instanceof AssertionError) {
status = TestStatus.FAIL;
} else {
status = TestStatus.ERROR;
}
failure = cause;
} catch (Exception e) {
status = TestStatus.ERROR;
failure = e;
} finally {
// Run After
try {
for (Method after : afterMethods) {
after.invoke(instance);
}
} catch (Exception e) {
status = TestStatus.ERROR;
failure = e;
}
}
results.add(new TestResult(testMethod.getName(), status, failure));
} catch (Exception e) {
results.add(new TestResult(testMethod.getName(), TestStatus.ERROR, e));
} finally {
latch.countDown();
}
});
}
latch.await();
} finally {
executor.shutdown();
}
}
// Run AfterAll
for (Method m : afterAllMethods) {
m.invoke(null); // Expect static methods
}
} catch (Exception e) {
System.err.println("Lifecycle initialization error: " + e.getMessage());
}
return results;
}
}
// โโโ DEMO TEST SUITE FOR REFLECTION SCANNING โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class MathTestSuite {
private static int globalCounter = 0;
private int localState = 0;
@BeforeAll
public static void initAll() {
globalCounter = 100;
System.out.println("[MathTestSuite] @BeforeAll invoked. globalCounter = " + globalCounter);
}
@Before
public void initEach() {
localState = 5;
System.out.println("[MathTestSuite] @Before invoked. localState = " + localState);
}
@Test
public void testAdditionSuccess() {
System.out.println("[MathTestSuite] Executing testAdditionSuccess");
Assert.assertEquals(10, localState + 5);
}
@Test
public void testAssertionFailure() {
System.out.println("[MathTestSuite] Executing testAssertionFailure");
Assert.assertEquals(12, localState + 5);
}
@Test
public void testRuntimeError() {
System.out.println("[MathTestSuite] Executing testRuntimeError");
String nullString = null;
nullString.length(); // Throws NullPointerException
}
@After
public void cleanupEach() {
System.out.println("[MathTestSuite] @After invoked.");
}
@AfterAll
public static void cleanupAll() {
System.out.println("[MathTestSuite] @AfterAll invoked.");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== RUNNING UNIT TESTING FRAMEWORK DEMO ===");
TestRunner runner = new TestRunner();
List<TestResult> results = runner.run(MathTestSuite.class);
System.out.println("\n=== TEST RESULTS REPORT ===");
for (TestResult result : results) {
System.out.println(result);
}
System.out.println("=== SIMULATION COMPLETE ===");
}
}
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...