I would try to explain you the framework JUnit.
I would like to start in a point where everybody agrees on that and gradual answer your question. Let P be a computer program that is designed to solve an algorithmically solvable problem.
P can be trivial(When you see P you can agrees that it´s correct). A trivial program could look like the following:
public static boolean invert(boolean proposition)
{
return !proposition;
}
P can be but also complex. In this case there is a set of propositions that are logically related. You can use this propositions to prove that P is correct(A computer program is correct when for each input it delivers the desired result). That is what do people do from theoretical disciplines like classic Mathematics, Theoretical Computer Science, Computational Geometrie, ...
There is also a practical approach that can be used to demonstrate(not to prove) that P is correct in a specific context(note that a programm that is syntactically and semantically correct in macro computation could be considered as useless or unreliable in micro computation). This approach is known as Testing in Software Engineering(For more information see ISO/IEC/IEEE 29119-1)
The concept of Testing was used before the existence of test frameworks like JUnit, to demonstrate wether a given program is error free. For this purpose a set of new concepts constraints were introduced:
- Production Code: is the code that is used in production and sometimes called Class Under Test(and it should be tested)
- Test Code: is the code that will be used to test a Production Code
- Constraint: A test code must not introduce additional business code
- ...
Example of a Production Code:
public class Number
{
private int n;
public Number(int n_passing)
{
n = n_passing;
}
public boolean isEven()
{
int rest = this.n % 2;
boolean isEven = (rest == 0);
if (isEven) {
return true;
}else {
return false;
}
}
}
Example of a Test Code:
public class NumberTest
{
// Test case 1
public static boolean testIsEven()
{
Number number = new Number(2);
boolean expectedResult = true;
boolean computedResult = number.isEven();
return expectedResult == computedResult;
}
// Test case 2
public static boolean testIsOdd()
{
Number number = new Number(1);
boolean expectedResult = false;
boolean computedResult = number.isEven();
return expectedResult == computedResult;
}
}
Note that we need different Test Cases in order to demonstrate that the class Number is properly implemented.What means that we need Test Data, that leads to different results. The number of test cases that are necessary to achieve thorough Test Coverage can be determined using the Cyclomatic complexity(for further information see Cyclomatic Complexity)
Now, in order to run the test methods implemented in NumberTest
we need an other class, let call it TestRunner
, which could look like following:
public class TestRunner {
public static void main(String[] args)
{
System.out.println(NumberTest.testIsEven());
System.out.println(NumberTest.testIsOdd());
}
}
The problem of this approach is that when ever we change(add, remove, comment, ...) the test code NumberTest
, we must change, save and recompile the TestRunner
class. To test a class with a few number of test cases this problem is not important, but as soon as one deals we a project with a large number of test cases the approach became inefficient, what reduces the productivity.
A better approach would be creating a General Purpose Test Runner that does not need to be change when the test cases changed. This is the main idea behind test framework like JUnit, TestNG, ...
You can create your own Test framework using Java Annotations and Reflections.