7.4. Program Development and Testing


Successive refinement

A commonly used approach to developing a piece of software is successive refinement. Hopefully, you start with a document that describes what the software is supposed to do when it is completed. You create a sequence of milestones, each describing what a partial solution should accomplish. For example, if one job that your program needs to do is to read in some data from a file, then a simple milestone is a program that just reads the data and shows what it read. A refinement plan is a sequence of requirements, each including a little bit more functionality, so that, at each milestone, you can check whether the software written so far does the job that is required by the desired partial set of requirements.

An advantage of successive refinement is that, when there is an error, it is usually in the functions or parts of the program that were just added. So you know where to look for errors.


Unit testing

Unit testing is testing individual functions or other components. If you implement an abstract data type, you probably test that implemenation as one unit. Typically, you need to write some test software whose sole purpose is to test the given unit. Your test software is not part of the finished product, but do not throw it away. Keep it in case you need to make modifications to the unit or in case you discover that there are still mistakes in the unit.

Unit testing is critical to large pieces of software. If unit A uses unit B, and there is a mistake in unit B, then unit A will fail. You can spend as long as you like trying to fix unit A; the mistake is not there. Before working on unit A, you want to make sure that unit B is correct.


Regression tests

As you develop tests, you typically add them to a package of tests that can be run automatically, such as by a script or makefile. After each significant modification, you run this collection of regression tests to ensure that those modifications have not somehow made something fail that used to work.

So tests are not thrown away when they are passed. You keep them around and run them automatically and frequently.


Test-driven development

One style of development is the test-first approach. Before adding any new feature, you develop tests for that feature. You try them out even before you have added the feature. They should fail. Then you know that you have tests that detect errors. The goal is to make those tests work.

One advantage of this is that you know that your collection of tests correctly fails when the feature is not working. Another is that the tests help you to understand just what you are trying to accomplish, so they are an adjunct to the requirements documentation.


Check for repeated mistakes

If you discover a mistake, consider the possibility that you have made a similar mistake in other places. Scan your program to see if you notice it. That will save you a lot of grief.