(If you haven’t downloaded SimpleTest framework yet, click here.)
You will probably become a Test-infected developer if you try a unit testing framework! But what is unit testing?
"The basic concept of unit testing is write more code which
will test the main code we've written, by "throwing" sample data at it and
examining what it gets back."-Harry Fuecks.
Some developers might not like the idea of writing more code but it will
save you a good time pin pointing the exact piece of code causing the
problem when your script becomes large.
Time to get practical about UnitTesting! We will write a test case for a
simple file manipulation class. This file class will have the basic
operations needed to manipulate files. It will be able to create, read,
write, append and delete files and our test case should test each operation
individually.
First, here is the definition of the class:
[file.class.php]
|
|
I will assume that you are able to understand how the code
works since we are talking about unit testing which is far more advanced
than file manipulation.
If you can't understand how this class works, I suggest you learn first
about OOP then learn about unit testing.
Anyway, it's not the best file class out there because I created it just for
this tutorial so don't get mad about it ;)
Now we want to create a test case for this class to see if it
works as we expect or not!
To create a test case we have to create a new class which inherits from
UnitTestCase class which is found in the file
unit_tester.php which comes in
SimpleTest framework package.
So we create our new PHP file, let's name it
fileTest.php and include
unit_tester.php and our file
file.class.php:
[fileTest.php]
|
|
Of course you have to change the paths to the correct paths in
your environment.
We now start the definition of the test case class and let's name it
fileTest as well:
|
|
As you can see, we have defined 3 methods (including the
constructor) and 2 data members, $file and $filename.
The constructor (fileTest) takes one argument which is the file name and
assign it to the data member $filename and create the file object and assign
it to $file.
The line: $this->UnitTestCase('File Manipulation Test'); is
optional and it just makes a header line in the test reporter (we will talk
about this later).
Also we have defined the methods setUp() and tearDown().
setUp() will be called automatically before each and every test and
tearDown() will be called after each and every test. However, these two
methods are optional.
We have defined the setUp() method to create a new clean file before each
test and tearDown() will delete that file after each test.
Now let's write our first test! To create a
test you simply have to define a method which starts with the word "test".
It doesn't matter if you use an underscore after the word "test" or not! As long
as "test" is the first word, it doesn't matter. Let's see our first test:
|
|
In this test, we check for the file
existence twice (paranoid?) using the method assertTrue() which will insure that
the method exists() will return true!
Then we use the built-in function file_exists() to double check that the
file truely exists!
I'll tell you later why we checked twice.
UnitTesting in SimpleTest depends heavily on assertions! Like in our previous
test we used the method assertTrue() which will require the argument passed to
it to be true in order to pass or it will fail!
It's worth saying that assertTrue() will pass as long as the argument is neither
0 nor false. So assertTrue("String")
will pass.
If you want to be strict about this, use assertIdentical() which will check the
value and the type. e.g. assertIdentical("true", true) will fail!
SimpleTest has many assert*() methods and in our UnitTestCase class we have
these:
|
assertTrue($x) |
Fail if $x is false |
|
assertFalse($x) |
Fail if $x is true |
|
assertNull($x) |
Fail if $x is set |
|
assertNotNull($x) |
Fail if $x not set |
|
assertIsA($x, $t) |
Fail if $x is not the class or type $t |
|
assertNotA($x, $t) |
Fail if $x is of the class or type $t |
|
assertEqual($x, $y) |
Fail if $x == $y is false |
|
assertNotEqual($x, $y) |
Fail if $x == $y is true |
|
assertIdentical($x, $y) |
Fail if $x == $y is false or a type mismatch |
|
assertNotIdentical($x, $y) |
Fail if $x == $y is true and types match |
|
assertReference($x, $y) |
Fail unless $x and $y are the same variable |
|
assertCopy($x, $y) |
Fail if $x and $y are the same variable |
|
assertWantedPattern($p, $x) |
Fail unless the regex $p matches $x |
|
assertNoUnwantedPattern($p, $x) |
Fail if the regex $p matches $x |
|
assertNoErrors() |
Fail if any PHP error occoured |
|
assertError($x) |
Fail if no PHP error or incorrect message |
|
assertErrorPattern($p) |
Fail unless the error matches the regex $p |
We will only need some these assestions to test our current
unit :)
Note that you could pass additional argument to all these methods which will be
the message to be displayed in case the test fails e.g. $this->assertTrue(false,
'Passing false to assertTrue faild!').
Don't worry though, because the error messages generated by SimpleTest are very
informative so no need to bother writing new messages.
Now, let's proceed to the next test:
|
|
This test will test the create() method and makes sure it's creating the file!
We first delete the file created (automatically) by setUp() by
using tearDown() and then check and insure that the file no longer exists by
using assertFalse() which will pass only if file_exists() returned false
(file does not exists).
We then check to see if the method create() successfully created the file
and returned true in the last two lines of the test.
Needless to say that it's more reading friendly to make the
test name similar or related to the method/part you are testing.
We'll now see a new assertion type in the next test:
|
|
This test will check the putContents() and getContents()
methods.
We will write some contents to the file using putContents() and make sure
everything went ok and then use assertEqual() to make sure that what we'd
written in the file is equal to what we get from getContents().
So you think I am paranoid when I check one part twice? It will be useless
to just check for putContents() since it might pass the test but without
actually doing what expected by just returning true or maybe because we had
defined the method incorrectly! So we have to check the contents of this
file and insure they are equal.
This is why we wrote two tests to check file existence and creation as well.
Now, how many operations left to test? Two, right?
Here is the test for the method append():
|
|
Note that we didn't test the method putContents() again since
it was tested before (tests are performed in the order they had been written
in).
We use a new assertion method that is assertWantedPattern() which will try
to match the regular expression pattern in the first argument against the
second argument.
We want to make sure that the word "NeverMind" is found at the end of the
file so the $ sign in regexp will help us.
And finally, our last test:
|
|
There is no new assertion type used here and what I want to test is clear, so I need not say anything here, do I? It's pretty obvious :)
So now all our tests are ready. We then instantiate the test case by:
|
|
Now try running this script….
Nothing appeared, right? Remember when we talked about the line
$this->UnitTestCase('File Manipulation Test') and how it's optional and it will
only create a header in the reporter? Yes, We have to use the reporter to show
how the tests went!
To use the reporter, we have to include the file reporter.php
(at the top for convenience) so our file top should look like this:
|
|
and then create the reporter and run it by the test case object by adding this line at the end:
|
|
Finally, our test case script should be like this:
[fileTest.php]
|
|
Now run the script again and if everything goes fine you should see something similar to this if all tests passed:
File Manipulation Test1/1 test cases complete: 11 passes, 0 fails and 0 exceptions. |
In case we had any failure, you will see something similar to:
File Manipulation TestFail: testAppend -> Pattern [~^nevermind$~i] not detected in [String: SimpleTestNeverMind] at line [61] 1/1 test cases complete: 10 passes, 1 fails and 0 exceptions. |
As you can see, there was a failure in the method testAppend() and the error
message has clearly stated what and where the error is.
Note that this test failed because I edited the pattern and forced the contents
of the file to be "NeverMind" only in order to pass (case-insensitive) which, of
course, made the test fails.
In case you had any failures, the script will continue and will not terminate
until all tests are performed.
If any PHP errors occurred, such as warnings or notices, they will be caught and
reported as exceptions.
Saleh Jamal
http://www.phpsimplicity.com
(Published in DEVpapers.com [link]
in 15-5-2005 [for Hotscripts.com newsletter - May issue])