A characterization test is a means to describe (characterize) the actual
behavior of an existing piece of software, and therefore protect existing
behavior of legacy code against unintended changes via automated testing.
This term was coined by Michael Feathers
Having a Legacy Business Object; assuming a hard to test class
package com.example;
public class BusinessClass {
public String businessMethod(String param) {
System.out.println("param = " + param);
final String split = param.split(" ")[0];
System.out.println("after split = " + split);
return split;
}
}We can introduce a Chracterization Test - to capture the log of the class and later on re-test; to see if the refcatoring impacted the actual output.
public class BusinessClassTest {
@ClassRule //(1)
public static CharacterizationRule rule = aRuleFor(BusinessClassTest.class)
.build();
private BusinessClass service = new BusinessClass();
@Test
public void just_run_the_method(String parameter) {
service.businessMethod(parameter);
}
}-
Invoking characterization rule
|
Note
|
The junit extension works both with @ClassRule as well as @Rule annotation; at runtime
(when org.junit.rules.TestRule.apply() method is invoked) the behaviour gets configured and
an output file that gets generated (or consumed for verification) - see the appendToExistingFile
configuration flag. Hence running the CharacterizationRule with different annotations changes the
behaviour of the whole test.
The file is named after the classname (passed as the parameter).
|
Initially, to create the master characteristics, the test needs running with
pinchpoint property. This turns the rule into a recording mode, when a master
output is created. All subsequent test runs (without a property) will be checking
the output against the master data.
mvn -Dpinchpoint=true -Dtest=BusinessClassTest test
The default output folder is target/test-classes; the file name is the test
class cannonical name, followed with a txt extension.
JUnit Characterization Rule comes with a handy builder for more extensive configuration. The initial config, as shown in the example is a shorthand and is based on some default values. Whenever this code is used:
CharacterizationRule rule = aRuleFor(BusinessClassTest.class)
.build();what happens under the hood is more or less as follows:
String DEFAULT_FOLDER = System.getProperty("java.io.tmpdir");
String DEFAULT_FILENAME = clazz.getCanonicalName() + ".txt";
CharacterizationRule rule = aRuleFor(BusinessClassTest.class)
.withRules()
.clearOutputBeforeCapture() //(1)
.inFolder(DEFAULT_FOLDER) //(2)
.withFilename(DEFAULT_FILENAME) //(3)
.up()
.build();-
This directive sets the rule truncate a master file (if exists) so that each log of each rule invocation (each method for
@Ruleor each test for@ClassRule). -
Location of the log file.
-
Log file name.
I can imagine a situation when you might want to log all output to a single (existing) file in a custom location. Than the configuration might look as follows:
CharacterizationRule rule = aRuleFor(BusinessClassTest.class)
.withRules()
.appendToExistingFile()
.inFolder("/my/custom/location/")
.withFilename("foo.txt")
.up()
.build();The appending mode comes especially useful when you use multiple parameters for testing
(like the @JUnitParams) described in the next section.
JUnitParams project adds a new runner to JUnit and provides much easier and readable parametrised tests for JUnit >=4.6.
|
Tip
|
Using junitparams is not essential, nonetheless, a reliable characterization tests makes sense when a significant amount of data is pushed through the class in testing |
To use JUnitParams, additional dependency is required
<dependency>
<groupId>pl.pragmatists</groupId>
<artifactId>JUnitParams</artifactId>
<version>1.0.2</version>
<scope>test</scope>
</dependency>A modified example, with additional junit parameters, might looks as follows
@RunWith(JUnitParamsRunner.class) //(1)
public class BusinessClassTest {
@ClassRule
public static CharacterizationRule rule = aRuleFor(BusinessClassTest.class)
.build();
private BusinessClass service = new BusinessClass();
@Test
@FileParameters("classpath:tst.csv") //(2)
public void just_run_the_method(String parameter) {
service.businessMethod(parameter);
}
}-
Runner for parametrized tests
-
File based paramters - please reffer to JunitParams wiki for more details
Sample CSV file with parameters for this particular example looks as follows
first parameter second parameter third parameter
A sample output for this example would be
param = first parameter after split = first param = second parameter after split = second param = third parameter after split = third