|
In order to be able to perform reliable DB-driven tests (be it performance related or not) we need to be able to reconstruct a database to the same state each time a test is run. This is the precise goal of the dbunit project. If the life was ideal we could use it to export the data from a database and import it to another in a (more or less) db agnostic manner. It can use the foreign key relationships between tables in the database and create referentially complete subsets of data, which is exactly what we'd need for database testing. But life is not ideal and this approach doesn't yield the optimal results. Because of various performance tweaks or maybe even oversights in the database design, the foreign key relationships aren't the ideal instrument to track the relationships of your applications business entities if for nothing else then for a complete lack of business logic awareness. In case of RHQ for example, we'd almost always want to include the full configuration objects with the resources, subjects, alert senders, etc. But if we only instructed the dbUnit to export the tables necessary for a resource to be successfully created in the database, we wouldn't get the full config objects - just the entries in the RHQ_CONFIG table that is referenced from RHQ_RESOURCE table. The individual properties are tied to the RHQ_CONFIG also by a foreign key, but the RHQ_RESOURCE only references RHQ_CONFIG and thus dbUnit would not consider it essential to include the properties with the config (correctly). We could instruct dbUnit to follow both directions of the foreign key relationships but because of the above performance and other reasons, such export would almost always include the whole database, including RHQ_PLUGIN and the binary data within there. So where to go else for a datamodel that we could work with, that could be translated to table terms and that has at least a hint of business logic awareness? Well, there's nothing else than the JPA entity model. Although this model obviously still doesn't contain much of the business logic (and therefore we'd still need some configuration to tell it what we want to include and what not), it contains much more information about what relationships are considered "important". In the above example, we can see that the Configuration entity directly references Property entities in a @OneToMany relationship and we could argue that if such explicit mapping exist then the properties are probably important for the configuration object. Export/ImportIn the perftest-support helper module (for now in perftest branch) there is code that implements the diagnostics of the JPA datamodel and that then translates the information from that analysis and from some additional configuration into terms understood by dbUnit to provide a meaningful export. The main class is org.rhq.helpers.perftest.support.Main (I haven't gone round to wrapping the invocation of it in a nice script) and it supports following commandline arguments:
The format of the configuration file is following: <graph packagePrefix="org.rhq.core.domain" includeExplicitDependentsImplicitly="true\|false"> <entity name="resource.Resource" includeAllFields="false\|true" root="false\|true"> <filter>SELECT ID FROM RHQ_RESOURCE WHERE ...</filter> <rel field="resourceConfiguration" exclude="false\|true"/> ... </entity> ... </graph>
The tool analyzes the dependency graph of the entities and based on it and the configuration and outputs all the data from the database that was a) configured to be included by the configuration file and b) and other data that is needed for the referential integrity. TestingIn the code of the above tool, there is a couple of classes to integrate the funtionality with TestNG. Here's what you need to do in your tests to add support for database setup:
Replication
To be able to test multi-agent setups, we need to have data ready for each of the agents in the database. That is of course possible without any kind of replication of the data by simply having the exported data set contain all the data for all the agents. This approach, though sufficient, has two main drawbacks:
The chosen approach is therefore to define the dataset in a single agent environment and then have a tool the is going to be able to replicate parts of the dataset as needed.
/**
* Specifies how to replicate the test data.
* This annotation is only processed as a part of the {@link DatabaseState}
*
* @author Lukas Krejci
*/
public @interface DataReplication {
/**
* The path to the replication configuration.
*/
String url();
/**
* Where does the {@link #url()} point to.
*/
FileStorage storage() default FileStorage.CLASSLOADER;
/**
* How many replicas should be prepared and how they should be distributed
* among the test invocations. The default is a replica per invocation.
*/
ReplicaCreationStrategy replicaCreationStrategy() default ReplicaCreationStrategy.PER_INVOCATION;
/**
* This callback can be used to modify the replica before it is persisted to the database.
* This can be used to modify the "names", "descriptions" and other data that is not significant
* to the referential integrity but that can help identifying the entities.
* <p>
* The method must have the following signature:<br/>
* <code>
* void <method-name>(int replicaNumber, Object original, Object replica, Class<?> entityType)
* </code>
*/
String replicaModifier() default "";
}
The @DataReplication annotation can be specified as part of the @DatabaseState annotation on the test.
But how do the tests obtain the replicas, you ask? It would be ideal if the tests could have a method parameter that would contain the replica data. Unfortunately, I found no way of achieving that in TestNG (I found no way of persuading TestNG that this method is a test method even if it has parameter and doesn't have a data provider). The alternative approach is therefore to use a kind of singleton that would be able to identify the test being executed and supply it with the correct replica. |