468 lines
17 KiB
HTML
468 lines
17 KiB
HTML
<html>
|
|
<head>
|
|
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
<title>
|
|
Download the Simple Test testing framework -
|
|
Unit tests and mock objects for PHP
|
|
</title>
|
|
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
|
</head>
|
|
<body>
|
|
<div class="menu_back">
|
|
<div class="menu">
|
|
<h2>
|
|
<span class="chosen">SimpleTest</span>
|
|
</h2>
|
|
<ul>
|
|
<li>
|
|
<a href="overview.html">Overview</a>
|
|
</li>
|
|
<li>
|
|
<a href="unit_test_documentation.html">Unit tester</a>
|
|
</li>
|
|
<li>
|
|
<a href="group_test_documentation.html">Group tests</a>
|
|
</li>
|
|
<li>
|
|
<a href="server_stubs_documentation.html">Server stubs</a>
|
|
</li>
|
|
<li>
|
|
<a href="mock_objects_documentation.html">Mock objects</a>
|
|
</li>
|
|
<li>
|
|
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
|
</li>
|
|
<li>
|
|
<a href="reporter_documentation.html">Reporting</a>
|
|
</li>
|
|
<li>
|
|
<a href="expectation_documentation.html">Expectations</a>
|
|
</li>
|
|
<li>
|
|
<a href="web_tester_documentation.html">Web tester</a>
|
|
</li>
|
|
<li>
|
|
<a href="form_testing_documentation.html">Testing forms</a>
|
|
</li>
|
|
<li>
|
|
<a href="authentication_documentation.html">Authentication</a>
|
|
</li>
|
|
<li>
|
|
<a href="browser_documentation.html">Scriptable browser</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<h1>Simple Test for PHP</h1>
|
|
<div class="content">
|
|
|
|
|
|
<p>
|
|
The following assumes that you are familiar with the concept
|
|
of unit testing as well as the PHP web development language.
|
|
It is a guide for the impatient new user of
|
|
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>.
|
|
For fuller documentation, especially if you are new
|
|
to unit testing see the ongoing
|
|
<a href="unit_test_documentation.html">documentation</a>, and for
|
|
example test cases see the
|
|
<a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>.
|
|
</p>
|
|
|
|
<p>
|
|
<a class="target" name="unit">
|
|
<h2>Using the tester quickly</h2>
|
|
</a>
|
|
</p>
|
|
<p>
|
|
Amongst software testing tools, a unit tester is the one
|
|
closest to the developer.
|
|
In the context of agile development the test code sits right
|
|
next to the source code as both are written simultaneously.
|
|
In this context SimpleTest aims to be a complete PHP developer
|
|
test solution and is called "Simple" because it
|
|
should be easy to use and extend.
|
|
It wasn't a good choice of name really.
|
|
It includes all of the typical functions you would expect from
|
|
<a href="http://www.junit.org/">JUnit</a> and the
|
|
<a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a>
|
|
ports, but also adds
|
|
<a href="http://www.mockobjects.com">mock objects</a>.
|
|
It has some <a href="http://sourceforge.net/projects/jwebunit/">JWebUnit</a>
|
|
functionality as well.
|
|
This includes web page navigation, cookie testing and form submission.
|
|
</p>
|
|
<p>
|
|
The quickest way to demonstrate is with an example.
|
|
</p>
|
|
<p>
|
|
Let us suppose we are testing a simple file logging class called
|
|
<span class="new_code">Log</span> in <em>classes/log.php</em>.
|
|
We start by creating a test script which we will call
|
|
<em>tests/log_test.php</em> and populate it as follows...
|
|
<pre>
|
|
<strong><?php
|
|
require_once('simpletest/unit_tester.php');
|
|
require_once('simpletest/reporter.php');
|
|
require_once('../classes/log.php');
|
|
?></strong>
|
|
</pre>
|
|
Here the <em>simpletest</em> folder is either local or in the path.
|
|
You would have to edit these locations depending on where you
|
|
placed the toolset.
|
|
Next we create a test case...
|
|
<pre>
|
|
<?php
|
|
require_once('simpletest/unit_tester.php');
|
|
require_once('simpletest/reporter.php');
|
|
require_once('../classes/log.php');
|
|
<strong>
|
|
class TestOfLogging extends UnitTestCase {
|
|
}</strong>
|
|
?>
|
|
</pre>
|
|
Now we have five lines of scaffolding code and still no tests.
|
|
However from this part on we get return on our investment very quickly.
|
|
We'll assume that the <span class="new_code">Log</span> class
|
|
takes the file name to write to in the constructor and we have
|
|
a temporary folder in which to place this file...
|
|
<pre>
|
|
<?php
|
|
require_once('simpletest/unit_tester.php');
|
|
require_once('simpletest/reporter.php');
|
|
require_once('../classes/log.php');
|
|
|
|
class TestOfLogging extends UnitTestCase {
|
|
<strong>
|
|
function testCreatingNewFile() {
|
|
@unlink('/temp/test.log');
|
|
$log = new Log('/temp/test.log');
|
|
$this->assertFalse(file_exists('/temp/test.log'));
|
|
$log->message('Should write this to a file');
|
|
$this->assertTrue(file_exists('/temp/test.log'));
|
|
}</strong>
|
|
}
|
|
?>
|
|
</pre>
|
|
When a test case runs it will search for any method that
|
|
starts with the string <span class="new_code">test</span>
|
|
and execute that method.
|
|
We would normally have more than one test method of course.
|
|
Assertions within the test methods trigger messages to the
|
|
test framework which displays the result immediately.
|
|
This immediate response is important, not just in the event
|
|
of the code causing a crash, but also so that
|
|
<span class="new_code">print</span> statements can display
|
|
their content right next to the test case concerned.
|
|
</p>
|
|
<p>
|
|
To see these results we have to actually run the tests.
|
|
If this is the only test case we wish to run we can achieve
|
|
it with...
|
|
<pre>
|
|
<?php
|
|
require_once('simpletest/unit_tester.php');
|
|
require_once('simpletest/reporter.php');
|
|
require_once('../classes/log.php');
|
|
|
|
class TestOfLogging extends UnitTestCase {
|
|
|
|
function testCreatingNewFile() {
|
|
@unlink('/temp/test.log');
|
|
$log = new Log('/temp/test.log');
|
|
$this->assertFalse(file_exists('/temp/test.log'));
|
|
$log->message('Should write this to a file');
|
|
$this->assertTrue(file_exists('/temp/test.log'));
|
|
}
|
|
}
|
|
<strong>
|
|
$test = &new TestOfLogging();
|
|
$test->run(new HtmlReporter());</strong>
|
|
?>
|
|
</pre>
|
|
</p>
|
|
<p>
|
|
On failure the display looks like this...
|
|
<div class="demo">
|
|
<h1>testoflogging</h1>
|
|
<span class="fail">Fail</span>: testcreatingnewfile->True assertion failed.<br>
|
|
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
|
|
<strong>1</strong> passes and <strong>1</strong> fails.</div>
|
|
</div>
|
|
...and if it passes like this...
|
|
<div class="demo">
|
|
<h1>testoflogging</h1>
|
|
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
|
<strong>2</strong> passes and <strong>0</strong> fails.</div>
|
|
</div>
|
|
And if you get this...
|
|
<div class="demo">
|
|
<b>Fatal error</b>: Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b>
|
|
</div>
|
|
it means you're missing the <em>classes/Log.php</em> file that could look like...
|
|
<pre>
|
|
<?php
|
|
class Log {
|
|
|
|
function Log($file_path) {
|
|
}
|
|
|
|
function message() {
|
|
}
|
|
}
|
|
?>;
|
|
</pre>
|
|
</p>
|
|
|
|
<p>
|
|
<a class="target" name="group">
|
|
<h2>Building group tests</h2>
|
|
</a>
|
|
</p>
|
|
<p>
|
|
It is unlikely in a real application that we will only ever run
|
|
one test case.
|
|
This means that we need a way of grouping cases into a test
|
|
script that can, if need be, run every test in the application.
|
|
</p>
|
|
<p>
|
|
Our first step is to strip the includes and to undo our
|
|
previous hack...
|
|
<pre>
|
|
<?php<strong>
|
|
require_once('../classes/log.php');</strong>
|
|
|
|
class TestOfLogging extends UnitTestCase {
|
|
|
|
function testCreatingNewFile() {
|
|
@unlink('/temp/test.log');
|
|
$log = new Log('/temp/test.log');
|
|
$this->assertFalse(file_exists('/temp/test.log'));
|
|
$log->message('Should write this to a file');
|
|
$this->assertTrue(file_exists('/temp/test.log'));<strong>
|
|
}
|
|
}
|
|
?></strong>
|
|
</pre>
|
|
Next we create a new file called <em>tests/all_tests.php</em>
|
|
and insert the following code...
|
|
<pre>
|
|
<strong><?php
|
|
require_once('simpletest/unit_tester.php');
|
|
require_once('simpletest/reporter.php');
|
|
|
|
$test = &new GroupTest('All tests');
|
|
$test->addTestFile('log_test.php');
|
|
$test->run(new HtmlReporter());
|
|
?></strong>
|
|
</pre>
|
|
The method <span class="new_code">GroupTest::addTestFile()</span>
|
|
will include the test case file and read any new classes created
|
|
that are descended from <span class="new_code">SimpleTestCase</span>, of which
|
|
<span class="new_code">UnitTestCase</span> is one example.
|
|
Just the class names are stored for now, so that the test runner
|
|
can instantiate the class when it works its way
|
|
through your test suite.
|
|
</p>
|
|
<p>
|
|
For this to work properly the test case file should not blindly include
|
|
any other test case extensions that do not actually run tests.
|
|
This could result in extra test cases being counted during the test
|
|
run.
|
|
Hardly a major problem, but to avoid this inconvenience simply add
|
|
a <span class="new_code">SimpleTestOptions::ignore()</span> directive
|
|
somewhere in the test case file.
|
|
Also the test case file should not have been included
|
|
elsewhere or no cases will be added to this group test.
|
|
This would be a more serious error as if the test case classes are
|
|
already loaded by PHP the <span class="new_code">GroupTest::addTestFile()</span>
|
|
method will not detect them.
|
|
</p>
|
|
<p>
|
|
To display the results it is necessary only to invoke
|
|
<em>tests/all_tests.php</em> from the web server.
|
|
</p>
|
|
|
|
<p>
|
|
<a class="target" name="mock">
|
|
<h2>Using mock objects</h2>
|
|
</a>
|
|
</p>
|
|
<p>
|
|
Let's move further into the future.
|
|
</p>
|
|
<p>
|
|
Assume that our logging class is tested and completed.
|
|
Assume also that we are testing another class that is
|
|
required to write log messages, say a
|
|
<span class="new_code">SessionPool</span>.
|
|
We want to test a method that will probably end up looking
|
|
like this...
|
|
<pre>
|
|
<strong>
|
|
class SessionPool {
|
|
...
|
|
function logIn($username) {
|
|
...
|
|
$this->_log->message("User $username logged in.");
|
|
...
|
|
}
|
|
...
|
|
}
|
|
</strong>
|
|
</pre>
|
|
In the spirit of reuse we are using our
|
|
<span class="new_code">Log</span> class.
|
|
A conventional test case might look like this...
|
|
<pre>
|
|
<strong>
|
|
<?php
|
|
require_once('../classes/log.php');
|
|
require_once('../classes/session_pool.php');
|
|
|
|
class TestOfSessionLogging extends UnitTestCase {
|
|
|
|
function setUp() {
|
|
@unlink('/temp/test.log');
|
|
}
|
|
|
|
function tearDown() {
|
|
@unlink('/temp/test.log');
|
|
}
|
|
|
|
function testLogInIsLogged() {
|
|
$log = new Log('/temp/test.log');
|
|
$session_pool = &new SessionPool($log);
|
|
$session_pool->logIn('fred');
|
|
$messages = file('/temp/test.log');
|
|
$this->assertEqual($messages[0], "User fred logged in.\n");
|
|
}
|
|
}
|
|
?></strong>
|
|
</pre>
|
|
This test case design is not all bad, but it could be improved.
|
|
We are spending time fiddling with log files which are
|
|
not part of our test. Worse, we have created close ties
|
|
with the <span class="new_code">Log</span> class and
|
|
this test.
|
|
What if we don't use files any more, but use ths
|
|
<em>syslog</em> library instead?
|
|
Did you notice the extra carriage return in the message?
|
|
Was that added by the logger?
|
|
What if it also added a time stamp or other data?
|
|
</p>
|
|
<p>
|
|
The only part that we really want to test is that a particular
|
|
message was sent to the logger.
|
|
We reduce coupling if we can pass in a fake logging class
|
|
that simply records the message calls for testing, but
|
|
takes no action.
|
|
It would have to look exactly like our original though.
|
|
</p>
|
|
<p>
|
|
If the fake object doesn't write to a file then we save on deleting
|
|
the file before and after each test. We could save even more
|
|
test code if the fake object would kindly run the assertion for us.
|
|
<p>
|
|
</p>
|
|
Too good to be true?
|
|
Luckily we can create such an object easily...
|
|
<pre>
|
|
<?php
|
|
require_once('../classes/log.php');
|
|
require_once('../classes/session_pool.php');<strong>
|
|
Mock::generate('Log');</strong>
|
|
|
|
class TestOfSessionLogging extends UnitTestCase {
|
|
|
|
function testLogInIsLogged() {<strong>
|
|
$log = &new MockLog($this);
|
|
$log->expectOnce('message', array('User fred logged in.'));</strong>
|
|
$session_pool = &new SessionPool($log);
|
|
$session_pool->logIn('fred');<strong>
|
|
$log->tally();</strong>
|
|
}
|
|
}
|
|
?>
|
|
</pre>
|
|
The <span class="new_code">tally()</span> call is needed to
|
|
tell the mock object that time is up for the expected call
|
|
count.
|
|
Without it the mock would wait forever for the method
|
|
call to come in without ever actually notifying the test case.
|
|
The other test will be triggered when the call to
|
|
<span class="new_code">message()</span> is invoked on the
|
|
<span class="new_code">MockLog</span> object.
|
|
The mock call will trigger a parameter comparison and then send the
|
|
resulting pass or fail event to the test display.
|
|
Wildcards can be included here too so as to prevent tests
|
|
becoming too specific.
|
|
</p>
|
|
<p>
|
|
The mock objects in the SimpleTest suite can have arbitrary
|
|
return values set, sequences of returns, return values
|
|
selected according to the incoming arguments, sequences of
|
|
parameter expectations and limits on the number of times
|
|
a method is to be invoked.
|
|
</p>
|
|
<p>
|
|
For this test to run the mock objects library must have been
|
|
included in the test suite, say in <em>all_tests.php</em>.
|
|
</p>
|
|
|
|
<p>
|
|
<a class="target" name="web">
|
|
<h2>Web page testing</h2>
|
|
</a>
|
|
</p>
|
|
<p>
|
|
One of the requirements of web sites is that they produce web
|
|
pages.
|
|
If you are building a project top-down and you want to fully
|
|
integrate testing along the way then you will want a way of
|
|
automatically navigating a site and examining output for
|
|
correctness.
|
|
This is the job of a web tester.
|
|
</p>
|
|
<p>
|
|
The web testing in SimpleTest is fairly primitive, there is
|
|
no JavaScript for example.
|
|
To give an idea here is a trivial example where a home
|
|
page is fetched, from which we navigate to an "about"
|
|
page and then test some client determined content.
|
|
<pre>
|
|
<?php<strong>
|
|
require_once('simpletest/web_tester.php');</strong>
|
|
require_once('simpletest/reporter.php');
|
|
<strong>
|
|
class TestOfAbout extends WebTestCase {
|
|
|
|
function setUp() {
|
|
$this->get('http://test-server/index.php');
|
|
$this->clickLink('About');
|
|
}
|
|
|
|
function testSearchEngineOptimisations() {
|
|
$this->assertTitle('A long title about us for search engines');
|
|
$this->assertWantedPattern('/a popular keyphrase/i');
|
|
}
|
|
}</strong>
|
|
$test = &new TestOfAbout();
|
|
$test->run(new HtmlReporter());
|
|
?>
|
|
</pre>
|
|
With this code as an acceptance test you can ensure that
|
|
the content always meets the specifications of both the
|
|
developers and the other project stakeholders.
|
|
</p>
|
|
<p>
|
|
<a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a>
|
|
</p>
|
|
|
|
</div>
|
|
<div class="copyright">
|
|
Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004
|
|
</div>
|
|
</body>
|
|
</html>
|