It is often tempting to skip unit testing for ad-hoc testing. However when done in the appropriate amount unit testing will increase productivity and reliability. It is also a great diagnostic tool. Unit testing is a skill that can get you to the next level as a programmer as well as an organization. It takes discipline and when done in the right amount you should be able to reap the benefits in weeks.
In this article, I will show simple examples on how to move your ad-hoc tests to test classes. The examples use the RawDev framework which makes unit testing as easy as it can possibly be. I will cover what a unit test is, how to create tests and a tool to execute the tests. I will also talk about some of the pitfalls and how to get around them.
What is a unit test?
Since virtually all code resides in functions, RawDev uses the following definition:
RawDev defines a unit test as a single test of the output of a function call, given specific input. Part of a test is also the verification of object and global state changes.
The above diagram shows all the relationships to a function that is unit tested. The terms in the diagram are described below:
|Function||Usually an object function or a static function. Functions can interact with their external environment.|
|Input||A function has zero or more input parameters. In rare cases these parameters defined as references in which case they can be altered by the function.|
|Output||A function has one output parameter which could be an array.|
|Exception||Usually based on incorrect input or failing of a resource, an exception can be thrown, which will interrup the regular flow of the code.|
|Object & global state||Object functions can modify the state of object variables. In addition, any function can modify class variables and other global state such as databases, files etc.|
Example of a single unit test in RawDev:
How do I write tests?
RawDev uses a modular approach by grouping functions into classes (of course…) and all classes into modules (or sub-modules). In RawDev the test objects that you write are either for one of three components: functions, classes, or modules. Each component test class has an optional setup and tear down function. In addition to a set of test functions that each incorporate one unit test and follow a naming convention. Creating component tests for modules and classes are not always necessary. The naming convention is: “test[Title of test]”. This title is useful in diagnosing a failed test. In RawDev both that path of classes and the path of component test classes is fixed.
When the class and test components are stored in the paths below then the runit testing utility can automatically detect it as a test as well as the parents and children.
Example of the component test for the function drive in class Car:
How do I execute tests?
[RAWDEV_HOME]/bin/runit.php is the binary that runs all unit tests it accepts zero or one argument which is the path of the component to be tested.
run all unit tests:
run all unit tests for module Util:
run all unit tests for class Car:
run all unit tests for function drive:
run one unit test named drive10
When a component is tested, all it’s child components are tested as well. However, the parent components are not tested, except that the setup and cleanup routines are called for all parents. The output of any component test (or all tests) is divided into 3 parts: tree view, overall view, and failed test messages.
Example of the output:
php runit.php Util
X: Util/Util/max:Array : Value  does not match expected value  for item . in Match.php (115)
As you can see all tests succeeded except for the “testArray” unit test function of function max in the Util class. Extra information is displayed (including a simple trace) why the test failed.
. : OK
X : FAIL (output does not match expected)
E : EXCEPTION (unexpected exception occured)
? : NO_EXCEPTION (expected exception did not occur)
Pitfalls and how to get around them
Unit testing efforts should use a balanced and iterative approach. If you don’t create (enough) unit tests early on then you’ll be doing a lot of ad-hoc testing. But if you create too many tests too soon, you run the risk that some of the requirements change and you spend a lot of time re-writing tests. I offer you the following guidelines even though I am sure you will find the best way that works with your programming style.
- When creating a static function is possible then do it. Static methods are easier to test and they can be reused by other components.
- Create functions for each specific task. Straightforward testing and increased re-use.
- Be sparse with code, re-use. Reducing the number of tests.
- Unit test only code that is not going to significantly change. Keeping up tests with code that changes is a slippery slope.
- Prioritize and use your time effectively. Keep the flow. Sometimes very specific tests may need to wait because of a deadline.
Certain projects require more unit testing than others but all projects can benefit from unit testing. Even though it’s tempting to do ad-hoc testing, you will be able to diagnose issues better and spend less total time in the long run when unit testing is done at the right amount.
The RawDev Framework
RawDev is a PHP framework that seeks balance between the following principles: simplicity, modularity, flexibility, power, usability, speediness (execution), security, and reliability (well tested). It is based on the author’s experience of 14 years of framework development in PHP for academic institutions and companies. Several production applications currently use RawDev (e.g. www.healthpanda.com), however you can follow the progress on the open source version that will be ready this summer at http://rawdev.bokenkamp.com. Individual libraries that are posted (such as the unit testing library) are downloadable and fully usable.
http://blog.bokenkamp.com (RawDev blog)
http://rawdev.bokenkamp.com (RawDev Home)
http://rawdev.bokenkamp.com/documentation/unittesting (Unit Testing Doc)
http://rawdev.bokenkamp.com/api/test/unit/test (RUnitTest API)
http://rawdev.bokenkamp.com/api/test/unit/component (RComponentUnitTest API)
Author : Raymond Bokenkamp