Now that the IT industry is relatively mature there are very few new products.
In my experience a majority of projects consist of a reimplementation of an existing product making a major change.
These can include:
- Changing the supported operating system.
- Changing the backend database (ISAM to SQL)
- Change of programming language
- Design of the system (Client Server to Distributed
Alongside this the new version of the product must have exactly the same behaviour as the old system.
The old system is likely to be undocumented (or if documented the documentation will be out of date). It is highly likely that people who wrote it are no longer with the company (or have been prompoted to management).
The Reimplementation Technique consists of using the old system to create the unit test suite for the new system. This provides the means of ensuring that the new system functions in exactly the same way as the old system.
The end result is a set of unit tests that provide a good basis for future development. These tests form good documentation for what the code actually does.
Any of the xUnit test suites could be used. I use dunit here since I am familiar with it (and it is less painful to get into html).
The test suite on the new system should take no more than ten mins to run otherwise you will find that it is not used. the test suite may take longer to run against the old system as that is onlu used occasionaly to validate that the tests work on the old system.
In the example that follows I assume that we are only trying to replace the design plus the database. Similar techniques (using say code generation) would work across languages.
A few notes on using DUnit:
A test suite in DUnit looks like the following:
TMyTest = class(TTestCase)
procedure setup; override;
procedure teardown; override;
Dunit treats each published method as a seperate test. Setup is called before each test. Teardown is called afterwards.
The literature surrounding xUnit suggests that you should ensure that any state changes (i.e. database entries) are removed in the teardown. I find that clearing up in the setup is neater. It makes these tests easier to check manually. Also these “side effects” are great for manual user interface tests.
UI testing is difficult in xUnit. However if you can get the system in exactly the state you need (i.e. one item suffering the problem you wish to debug your life becomes much easier).
Here is a simple example.
The application being ported has a telephone list for staff. An update file is read in. It is a CSV file consisting of dept, name and number. The file should replace the contents of the database table with the new list.
The first thing to write is a generator class that will parametrically create our test data.
The tests themselves call helper methods.
Actions do real world things:
- Create the input file
- Read the input file
Enquiries count real world things
Count telephone entries
Count people with name like ()
Typically these classes are contructed by a factory that returns the appropriate class depending upon a compiler directive.
A simple test could look like:
CheckEquals(0, CountAll(), ‘Step 1’);
CheckEquals(0, CountAll(), ‘Step 2’);
CheckEquals(3, CountAll(), ‘Step 3’);
CheckEquals(0, Errors(), ‘Step 4’);
The general principle is to write the tests around business operations, not around the actual implementation.
This makes the tests far more resistant to changes in implementation.
By keeping the actual tests abstract portability becomes much easier.
In one implementation a count could open a file and count the lines, in another a SQL statement does the job.
Jumping languages would use similar techniques. The tests themselves would need to be ported, but since most morden languages are so similar this should not be too much of a job.