Single-Class Unit Test with Data-Driven Test Helper
Single-Class Unit Test with Data-Driven Test Helper
This pattern lets you write a self-testing Java class with no external test framework. One class contains four functions. Each function has one job. Test inputs and expected results are listed in one place and driven through a single reusable helper.
The name IcyHot comes from the worked example at the bottom of this page. The pattern applies to any target function - temperatures are just the teaching example.
Starting Point - The Problem and the Target Function
Every instance of this pattern begins with two things: a problem statement and a target function signature. Nothing else is written until these are clear.
Problem statement:
true if one is less than 0
AND the other is greater than 100.
Either temperature can be the icy one or the hot one.
Examples:
icyHot(120, -1) → true
icyHot(-1, 120) → true
icyHot(2, 120) → false
Target function signature:
public boolean icyHot(int temp1, int temp2)
This is the function you have been asked to implement. You do not write its body yet. The signature is enough to build everything else - the test helper mirrors it, and the test cases call it by name.
1. The Four Functions
The class file lists the functions in this order from top to bottom:
| # | Function | Label | Job |
|---|---|---|---|
| 1 | main |
ENTRY | Creates one instance of the class. Calls testCases. Contains no other logic. |
| 2 | testCases |
TEST LIST | Lists all test inputs and expected results. Calls testHelper once per scenario. Contains no assertions. |
| 3 | testHelper |
ASSERTION | Calls the target function. Compares actual result to expected result. Increments testCaseCount and testCaseErrors. This is the data-driven heart of the pattern - one helper handles every test row. |
| 4 | target (e.g. icyHot) |
TARGET | The function the question asks you to write. The only function that contains business logic. |
The class also has two fields: int testCaseCount (total tests run) and int testCaseErrors (total failures).
2. Order of Writing - Given a New Target
The functions are not written in file order. The target is stubbed first so the test helper has a signature to call. The implementation is filled in last so the tests are ready before the logic is written.
return false; or return 0;). Do not write the logic yet.main.
Create an instance. Call testCases. Nothing else.main → testCases → testHelper → target.You write:
target (stub) → testHelper → testCases → main → target (logic).The file is organized by abstraction level (high to low). You write by dependency (what must exist before what can call it).
3. Second Target Example - maxDouble
Requirements
- Given two integers
aandb. - Find the larger of the two integers.
- Return that larger integer multiplied by two.
Step 1 - Target Function Stub
Write the signature first. Return 0 as a placeholder. Do not write the logic yet.
public int maxDouble(int a, int b) {
return 0; // stub - implement in Step 5
}
Step 2 - Test Helper
The test helper follows the same template as testIcyHot.
Only 4 things change: the method name, the input parameters, the return type, and the local variable default.
The error-counting and diagnostic structure stays identical.
| Part | testIcyHot (original) | testMaxDouble (new) |
|---|---|---|
| Method name | testIcyHot | testMaxDouble |
| Input params | int temp1, int temp2 | int a, int b |
| Expected param | boolean expectedReturn | int expectedReturn |
| Local variable | boolean actualReturn = false; | int actualReturn = 0; |
| Target call | icyHot(temp1, temp2) | maxDouble(a, b) |
| Comparison | actualReturn != expectedReturn | actualReturn != expectedReturn |
private void testMaxDouble(int a, int b, int expectedReturn) {
int actualReturn = 0;
testCaseCount++;
try {
actualReturn = maxDouble(a, b);
} catch (Throwable e) {
e.printStackTrace();
testCaseErrors++;
System.err.println("Error " + e + ", expected:" + expectedReturn
+ ", for a:" + a + ", b:" + b
+ ", count:" + testCaseCount + ".");
return;
}
if (actualReturn != expectedReturn) {
System.out.println("Actual:" + actualReturn + ", expected:" + expectedReturn
+ ", for a:" + a + ", b:" + b
+ ", count:" + testCaseCount + ".");
testCaseErrors++;
}
}
The rest of the class - testCases for maxDouble, the implementation body,
and main - follows the same structure shown in the full IcyHot example below.
The test helper is the only function that changes with each new target.
4. IcyHot.java - Full Example
The complete source file. The implementation is intentionally incomplete - it only handles the case where
temp1 is icy and temp2 is hot. The reversed case surfaces as a deliberate test failure
to demonstrate that good test cases expose incomplete logic.
Version 5
- Log in to post comments