Command pattern for regression testing
A few days ago I had to refactor a 20,000 line class that was being used as a regression tester for discrete element and peridynamics simulations. After some thought, I realized that the easiest way to achieve what I wanted was by using the Command design pattern (minus the undo option). My approach was to keep the implementation simple enough that a typical student of mechanics would be able to follow the details, i.e., no template metaprogramming a la Loki.
The original code
The regression tester in the original code looked something like this:
The RegressionTester
class was huge and contained the code for a large number of tests,
each of which was quite involved.
I wanted to separate out each test into its own self-contained class.
The Command pattern approach
There are several variations on the command pattern, but the basic idea is to use object polymorphism to provide a clean interface for function calls.
Step 1: Create a Command interface
I created a directory called TestSimulations
and in that directory created the Command interface
file Command.h
.
Step 2: Create a Command handler
The command handler class returns a pointer to the chosen command that is polymorphic and will return the pointer to the correct test. The header first
and then the implementation (getEnum
translates the string to an enum)
Step 3: Create the test classes
Next we add the actual test classes. The headers have the form
while the code contains the detailed algorithm for each test:
Step 4: Call the regression tester
This is the entry point. Even though we have used the RegressionTester
class, we haven’t explained what it does. You can use it for generic
algorithms that some or all tests use, or for anything else that each
test may need.
Step 5: Run you regression tests
Make sure you save a set of “gold-standard” outputs to compare with the results from your regression tester. Typically a Python script or a shell script is used to do the runs, comparisons, and output to a webdocs.
Conclusion
So, at the expense of an increase in the number of classes and the need for some look-ups of the virtual table, we have a much cleaner implementation of the tests and we can add more tests quite easily.