TestDataCaptureJ: Customization


Table of Contents

Custom Configuration
Code Examples
Setting fields in the constructor
Constructor Configuration
Immutable Classes
Setter Configuration
Collection Adder Methods
Coded Configurations
Next:

Custom Configuration

As has been explained earlier, the TestDataCaptureJ tool expects that the objects that it logs to follow certain JavaBean conventions:

  • Data objects have a no-argument constructor.

  • Fields of data objects have setter methods that follow the standard naming convention.

  • Collection fields that are only accessed by adder methods.

Ideally too the constructor for the data objects and setter methods should not have any side effects that affect the state of the object.

While most classes that are used as data holders that I've come across do tend to follow these conventions, sometimes the classes you want to capture do not, so the TestDataCaptureJ tool allows for some custom configuration to handle some of the exceptions based on some commonly used programming patterns.

  • Setting field values in the constructor instead of using setter methods.

  • Fields in data objects that do not have setter methods defined.

Code Examples

To demonstrate some of the features of the TestDataCaptureJ tool, here are some examples of code and the logging that would be produced from them. The code and logging for the examples for the explanation page are found in:

  • 'test/au/com/dw/testdatacapturej/explanation/CustomizationTest.java'

  • 'test/au/com/dw/testdatacapturej/mock/explanation/*.java'

In order the logging examples in this page, firstly configure 'conf/configuration.properties' to include the unit test configuration files:

# config files for constructor configuration

constructor.config.files=constructor-config.xml, test-constructor-config.xml

# config files for setter method configuration

setter.config.files=setter-config.xml, test-setter-config.xml

# config files for collection adder configuration

collection.config.files=test-collection-config.xml

Setting fields in the constructor

Sometimes the class either has an options to set some or all of the field values in the constructor, and sometimes this is required. In other words, the constructor is used to set field values either instead of, or in addition to using setter methods to set the field values.

Here is an example of a class with an additional utility constructor that can be used to initialized the fields, in addition to having the default no-argument constructor and setter methods for the fields.

'test/au/com/dw/testdatacapturej/mock/explanation/CustomerMultipleConstructors.java':

package au.com.dw.testdatacapturej.mock.explanation;

public class CustomerMultipleConstructors {
	private String firstName;
	private String surName;
	
	public CustomerMultipleConstructors() {

	}
	
	public CustomerMultipleConstructors(String firstName, String surName) {
		super();
		this.firstName = firstName;
		this.surName = surName;
	}

	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getSurName() {
		return surName;
	}
	public void setSurName(String surName) {
		this.surName = surName;
	}
	
}

In this case the TestDataCaptureJ tool can be run without any custom configuration, so that it uses the no-argument constructor and the setter method for the fields. This is what the logging would look like if the configuration file 'conf/test-constructor-config.xml' is not used (i.e. this is the default code generated).

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerMultipleConstructors:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors createParam1CustomerMultipleConstructors_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerMultipleConstructors() {

au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors customerMultipleConstructors0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors();
customerMultipleConstructors0.setFirstName("John");
customerMultipleConstructors0.setSurName("Smith");

return customerMultipleConstructors0;
}

However with the custom configuration in 'conf/test-constructor-config.xml' included, we would generate code that uses the other constructor that initializes the fields instead.

1. // au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerMultipleConstructors:Parameter1
2. public au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors createParam1CustomerMultipleConstructors_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerMultipleConstructors() {

3. au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors customerMultipleConstructors0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors("John", "Smith");

4. return customerMultipleConstructors0;
5. }

  • Notice that the utility constructor is used instead of the no-argument constructor, passing the value of the fields as parameters to the constructor (line 3).

  • Notice that the setter methods for the fields have not been generated, even though they exist in the class.

The next section will explain how this was done.

Constructor Configuration

In the conf directory of the TestDataCaptureJ project there are some configuration files, some of which are used for custom configuration for constructors.

Fragment of 'conf/test-constructor-config.xml':

<?xml version="1.0" encoding="UTF-8"?>
<constructor-config>
.
.
.
	<constructor class="au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors">
		<argument>
			<field-name>firstName</field-name>
		</argument>
		<argument>
			<field-name>surName</field-name>
		</argument>
	</constructor>
			
</constructor-config>

This xml configuration is for the class mock.explanation.CustomerMultipleConstructors and defines the field values that you want to pass as parameters to the constructor. If this configuration is used, then the TestDataCaptureJ tool will generate constructor code that uses a parameterized constructor instead of the no-argument constructor. As well the setter methods for the fields would not be generated.

Of course it is up to you to ensure that a constructor with parameters of the appropriate types actually exist in the class and is configured properly.

To ensure that this constructor configuration is used, make sure that the configuration file is included when the TestDataCaptureJ tool is run. There is a properties file that lists the configuration files that are to be used when the TestDataCaptureJ tool is run.

Fragment of 'conf/configuration.properties':

.
.
.
# config files for constructor configuration

constructor.config.files=constructor-config.xml, test-constructor-config.xml
.
.
.

In the example using mock.explanation.CustomerMultipleConstructors in the previous section, using this configuration produced that logging that generated the parameterized constructor.

au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors customerMultipleConstructors0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors("John", "Smith");

If the constructor configuration was not used, i.e. either the configuration for the mock.explanation.CustomerMultipleConstructors did not exist or the test-constructor-config.xml file was not included in the list in the properties file, then the TestDataCaptureJ tool would just use the no-argument constructor instead and generate the setter methods for the fields - which is the default handling for the tool.

au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors customerMultipleConstructors0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerMultipleConstructors();
customerMultipleConstructors0.setFirstName("John");
customerMultipleConstructors0.setSurName("Smith");

Immutable Classes

Sometimes a developer will want to create an immutable class where the fields cannont be changed once the object has been constructed and initialized. A common idiom to do this is to have only the parameterized constructor that initializes all the fields, and not to have any setter methods for the fields - i.e. the object becomes read-only after construction.

'test/au/com/dw/testdatacapturej/mock/explanation/CustomerImmutable.java':

package au.com.dw.testdatacapturej.mock.explanation;

public class CustomerImmutable {
	private final String firstName;
	private final String surName;
	
	public CustomerImmutable(String firstName, String surName) {
		super();
		this.firstName = firstName;
		this.surName = surName;
	}

	public String getFirstName() {
		return firstName;
	}
	public String getSurName() {
		return surName;
	}
	
}

In cases like this, in order for TestDataCaptureJ to work there needs to be a custom constructor configuration defined and included for this class. Else it will just generate code for a no-argument constructor and setter methods that do not exist in the class.

<?xml version="1.0" encoding="UTF-8"?>
<constructor-config>
.
.
.
	<constructor class="au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable">
		<argument>
			<field-name>firstName</field-name>
		</argument>
		<argument>
			<field-name>surName</field-name>
		</argument>
	</constructor>
			
</constructor-config>

If the custom configuration was not used, then the following code would be generated in the logging. Of course this code would not compile because the 'CustomerImmutable' class doesn't have the setters 'setFirstName(..)' and 'setSurName(..)'.

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerImmutable:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable createParam1CustomerImmutable_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerImmutable() {

au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable customerImmutable0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable();
// Default constructor for au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable does not exist.
customerImmutable0.setFirstName("Mary");
customerImmutable0.setSurName("Jones");

return customerImmutable0;
}

However with the custom configuration included, the correct code is generated setting the value of the fields in the constructor.

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerImmutable:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable createParam1CustomerImmutable_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerImmutable() {

au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable customerImmutable0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerImmutable("Mary", "Jones");

return customerImmutable0;
}

Setter Configuration

Sometimes a class will no have setter methods for some of its fields. The reasons for this can be various:

  • The field is meant to be read-only after it has been initialized, as in the case of immutable objects.

  • The field is meant is not meant to hold state for the object, e.g. it is for a transient field, a calculated value or a temporary value, etc.

'test/au/com/dw/testdatacapturej/mock/explanation/CustomerFullName.java':

public class CustomerFullName {
	private String firstName;
	private String surName;
	private String fullName;
	
	public CustomerFullName() {
	}

	private void createFullName() {
		fullName = surName + ", " + firstName;
	}
	
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
		
		createFullName();
	}
	public String getSurName() {
		return surName;
	}
	public void setSurName(String surName) {
		this.surName = surName;
		
		createFullName();
	}
	public String getFullName() {
		return fullName;
	}
	
}

In this rather contrived example, we do not want TestDataCaptureJ to generate a setter method for the 'fullName' field since the class does not have any setter method.

There is a custom configuration for setters that will configure TestDataCaptureJ not to generate setter methods for certain fields in data objects.

Fragment of 'conf/test-setter-config.xml':

There is a custom configuration for setters that will configure TestDataCaptureJ not to generate setter methods for certain fields in data objects.

<?xml version="1.0" encoding="UTF-8"?>
<setter-config>
    .
    .
    .
    <setter class="au.com.dw.testdatacapturej.mock.explanation.CustomerFullName">
		<field>
			<field-name>fullName</field-name>
			<alternative>ignore</alternative>
		</field>
	</setter>
		
</setter-config>
    

This xml configuration is for the class mock.explanation.CustomerFullName and defines the field values that you do not want setter methods to be generated for. If this configuration is used, then the TestDataCaptureJ tool will generate not generate a setter method for the fullName field.

Of course just like constructor configuration, the setter configuration file needs to be included in the properties file when the TestDataCaptureJ tool is run.

Fragment of 'conf/configuration.properties':

.
.
.
# config files for setter method configuration

setter.config.files=setter-config.xml, test-setter-config.xml
.
.
.

The generated log will not have a 'setFullName(..)' method:

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerFullName:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerFullName createParam1CustomerFullName_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerFullName() {

au.com.dw.testdatacapturej.mock.explanation.CustomerFullName customerFullName0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerFullName();
customerFullName0.setFirstName("Ted");
customerFullName0.setSurName("Jones");

return customerFullName0;

Collection Adder Methods

A common pattern for handling collection fields is to only allow access to it through methods in the enclosing class, rather than accessing the collection directly.

An example is 'test/au/com/dw/testdatacapturej/mock/explanation/CustomerListing.java':

public class CustomerListing {
	private final Collection<Customer> customers = new ArrayList<Customer>();

	public CustomerListing() {
	}
	
	public void addCustomer(Customer customer) {
		this.customers.add(customer);
	}
	
}

In this case there are no getter or setter methods for accessing the collection field 'customer', instead add elements to the collection requires using the adder method 'addCustomer()'.

Running the TestDataCaptureJ tool on this class would generate the following code, which would not work since it is trying to use a setter method named 'setCustomers()' which, of course, doesn't exist in the 'CustomerListing' class.

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerListing:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerListing createParam1CustomerListing_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerListing() {

au.com.dw.testdatacapturej.mock.explanation.CustomerListing customerListing0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerListing();

java.util.ArrayList arrayList0 = new java.util.ArrayList();

au.com.dw.testdatacapturej.mock.explanation.Customer customer1 = new au.com.dw.testdatacapturej.mock.explanation.Customer();
customer1.setFirstName("John");
customer1.setSurName("Smith");
arrayList0.add(customer1);

au.com.dw.testdatacapturej.mock.explanation.Customer customer2 = new au.com.dw.testdatacapturej.mock.explanation.Customer();
customer2.setFirstName("Mary");
customer2.setSurName("Jones");
arrayList0.add(customer2);

customerListing0.setCustomers(arrayList0);

return customerListing0;
}

There is a custom configuration that will handle cases like this, to allow using an adder method instead of a setter method.

Fragment of 'conf/test-collection-config.xml':

.
.
.
	<container class="au.com.dw.testdatacapturej.mock.explanation.CustomerListing">
		<argument>
			<field-name>customers</field-name>
			<adder-method>addCustomer</adder-method>
		</argument>
	</container>
.
.
.

The collection configuration file needs to be included in the properties file when the TestDataCaptureJ tool is run.

Fragment of 'conf/configuration.properties':

.
.
.
# config files for setter method configuration

collection.config.files=test-collection-config.xml
.
.
.

Now when TestDataCaptureJ is run, it should generate the following logging:

// au.com.dw.testdatacapturej.explanation.CustomizationTest.joinPointParamForCustomerListing:Parameter1
public au.com.dw.testdatacapturej.mock.explanation.CustomerListing createParam1CustomerListing_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerListing() {

au.com.dw.testdatacapturej.mock.explanation.CustomerListing customerListing0 = new au.com.dw.testdatacapturej.mock.explanation.CustomerListing();

au.com.dw.testdatacapturej.mock.explanation.Customer customer1 = new au.com.dw.testdatacapturej.mock.explanation.Customer();
customer1.setFirstName("John");
customer1.setSurName("Smith");
customerListing0.addCustomer(customer1);

au.com.dw.testdatacapturej.mock.explanation.Customer customer2 = new au.com.dw.testdatacapturej.mock.explanation.Customer();
customer2.setFirstName("Mary");
customer2.setSurName("Jones");
customerListing0.addCustomer(customer2);

return customerListing0;
}

Now the generated code correctly uses the adder method defined in the XML configuration.

Coded Configurations

Some of the log generation is handled in the code and is not currently configurable. To change the logging generated for these elements would, obviously, require code changes.

  • The generated name of the method that re-creates the test data object is handled in 'au.com.dw.testdatacapturej.builder.MethodBuilder', e.g. 'createParam1CustomerMultipleConstructors_au_com_dw_testdatacapturej_explanation_CustomizationTest_joinPointParamForCustomerMultipleConstructors()'.

  • When using the default 'SIFT' appender in the logging framework configuration, the name of the file to log to for each object in handled in 'au.com.dw.testdatacapturej.builder.FileNameKeyBuilder', e.g. 'org.springframework.samples.jpetstore.domain.Order.initOrder-Parameter1-org.springframework.samples.jpetstore.domain.Account-20110314T174942.java'.

Next:

Tutorial