More SQL command (drop table, drop database)
delete, select & insert upgraded More utility function (node to array, arrayToNode) XMLDB special move command PHP Unit Test
This commit is contained in:
355
simpletest/docs/en/authentication_documentation.html
Normal file
355
simpletest/docs/en/authentication_documentation.html
Normal file
@@ -0,0 +1,355 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest documentation for testing log-in and authentication</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<span class="chosen">Authentication</span>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Authentication documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Getting through <a href="#basic">Basic HTTP authentication</a>
|
||||
</li>
|
||||
<li>
|
||||
Testing <a href="#cookies">cookie based authentication</a>
|
||||
</li>
|
||||
<li>
|
||||
Managing <a href="#session">browser sessions</a> and timeouts
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
One of the trickiest, and yet most important, areas
|
||||
of testing web sites is the security.
|
||||
Testing these schemes is one of the core goals of
|
||||
the SimpleTest web tester.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="basic"><h2>Basic HTTP authentication</h2></a></p>
|
||||
<p>
|
||||
If you fetch a page protected by basic authentication then
|
||||
rather than receiving content, you will instead get a 401
|
||||
header.
|
||||
We can illustrate this with this test...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {<strong>
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');
|
||||
$this->showHeaders();
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
This allows us to see the challenge header...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<pre style="background-color: lightgray; color: black">
|
||||
HTTP/1.1 401 Authorization Required
|
||||
Date: Sat, 18 Sep 2004 19:25:18 GMT
|
||||
Server: Apache/1.3.29 (Unix) PHP/4.3.4
|
||||
WWW-Authenticate: Basic realm="SimpleTest basic authentication"
|
||||
Connection: close
|
||||
Content-Type: text/html; charset=iso-8859-1
|
||||
</pre>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
||||
<strong>0</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
We are trying to get away from visual inspection though, and so SimpleTest
|
||||
allows to make automated assertions against the challenge.
|
||||
Here is a thorough test of our header...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');<strong>
|
||||
$this->assertAuthentication('Basic');
|
||||
$this->assertResponse(401);
|
||||
$this->assertRealm('SimpleTest basic authentication');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Any one of these tests would normally do on it's own depending
|
||||
on the amount of detail you want to see.
|
||||
</p>
|
||||
<p>
|
||||
One theme that runs through SimpleTest is the ability to use
|
||||
<span class="new_code">SimpleExpectation</span> objects wherever a simple
|
||||
match is not enough.
|
||||
If you want only an approximate match to the realm for
|
||||
example, you can do this...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function test401Header() {
|
||||
$this->get('http://www.lastcraft.com/protected/');
|
||||
$this->assertRealm(<strong>new PatternExpectation('/simpletest/i')</strong>);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Most of the time we are not interested in testing the
|
||||
authentication itself, but want to get past it to test
|
||||
the pages underneath.
|
||||
As soon as the challenge has been issued we can reply with
|
||||
an authentication response...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function testCanAuthenticate() {
|
||||
$this->get('http://www.lastcraft.com/protected/');<strong>
|
||||
$this->authenticate('Me', 'Secret');</strong>
|
||||
$this->assertTitle(...);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The username and password will now be sent with every
|
||||
subsequent request to that directory and subdirectories.
|
||||
You will have to authenticate again if you step outside
|
||||
the authenticated directory, but SimpleTest is smart enough
|
||||
to merge subdirectories into a common realm.
|
||||
</p>
|
||||
<p>
|
||||
You can shortcut this step further by encoding the log in
|
||||
details straight into the URL...
|
||||
<pre>
|
||||
class AuthenticationTest extends WebTestCase {
|
||||
function testCanReadAuthenticatedPages() {
|
||||
$this->get('http://<strong>Me:Secret@</strong>www.lastcraft.com/protected/');
|
||||
$this->assertTitle(...);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
If your username or password has special characters, then you
|
||||
will have to URL encode them or the request will not be parsed
|
||||
correctly.
|
||||
Also this header will not be sent on subsequent requests if
|
||||
you request a page with a fully qualified URL.
|
||||
If you navigate with relative URLs though, the authentication
|
||||
information will be preserved.
|
||||
</p>
|
||||
<p>
|
||||
Only basic authentication is currently supported and this is
|
||||
only really secure in tandem with HTTPS connections.
|
||||
This is usually enough to protect test server from prying eyes,
|
||||
however.
|
||||
Digest authentication and NTLM authentication may be added
|
||||
in the future.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="cookies"><h2>Cookies</h2></a></p>
|
||||
<p>
|
||||
Basic authentication doesn't give enough control over the
|
||||
user interface for web developers.
|
||||
More likely this functionality will be coded directly into
|
||||
the web architecture using cookies and complicated timeouts.
|
||||
</p>
|
||||
<p>
|
||||
Starting with a simple log-in form...
|
||||
<pre>
|
||||
<form>
|
||||
Username:
|
||||
<input type="text" name="u" value="" /><br />
|
||||
Password:
|
||||
<input type="password" name="p" value="" /><br />
|
||||
<input type="submit" value="Log in" />
|
||||
</form>
|
||||
</pre>
|
||||
Which looks like...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
Username:
|
||||
<input type="text" name="u" value=""><br>
|
||||
Password:
|
||||
<input type="password" name="p" value=""><br>
|
||||
<input type="submit" value="Log in">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
Let's suppose that in fetching this page a cookie has been
|
||||
set with a session ID.
|
||||
We are not going to fill the form in yet, just test that
|
||||
we are tracking the user.
|
||||
Here is the test...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
function testSessionCookieSetBeforeForm() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$this->assertCookie('SID');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
All we are doing is confirming that the cookie is set.
|
||||
As the value is likely to be rather cryptic it's not
|
||||
really worth testing this with...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
function testSessionCookieIsCorrectPattern() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->assertCookie('SID', <strong>new PatternExpectation('/[a-f0-9]{32}/i')</strong>);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The rest of the test would be the same as any other form,
|
||||
but we might want to confirm that we still have the same
|
||||
cookie after log-in as before we entered.
|
||||
We wouldn't want to lose track of this after all.
|
||||
Here is a possible test for this...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testSessionCookieSameAfterLogIn() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$session = $this->getCookie('SID');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');
|
||||
$this->assertCookie('SID', $session);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This confirms that the session identifier is maintained
|
||||
afer log-in.
|
||||
</p>
|
||||
<p>
|
||||
We could even attempt to spoof our own system by setting
|
||||
arbitrary cookies to gain access...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testSessionCookieSameAfterLogIn() {
|
||||
$this->get('http://www.my-site.com/login.php');<strong>
|
||||
$this->setCookie('SID', 'Some other session');
|
||||
$this->get('http://www.my-site.com/restricted.php');</strong>
|
||||
$this->assertText('Access denied');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Is your site protected from this attack?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="session"><h2>Browser sessions</h2></a></p>
|
||||
<p>
|
||||
If you are testing an authentication system a critical piece
|
||||
of behaviour is what happens when a user logs back in.
|
||||
We would like to simulate closing and reopening a browser...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testLoseAuthenticationAfterBrowserClose() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');<strong>
|
||||
|
||||
$this->restart();
|
||||
$this->get('http://www.my-site.com/restricted.php');
|
||||
$this->assertText('Access denied');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The <span class="new_code">WebTestCase::restart()</span> method will
|
||||
preserve cookies that have unexpired timeouts, but throw away
|
||||
those that are temporary or expired.
|
||||
You can optionally specify the time and date that the restart
|
||||
happened.
|
||||
</p>
|
||||
<p>
|
||||
Expiring cookies can be a problem.
|
||||
After all, if you have a cookie that expires after an hour,
|
||||
you don't want to stall the test for an hour while the
|
||||
cookie passes it's timeout.
|
||||
</p>
|
||||
<p>
|
||||
To push the cookies over the hour limit you can age them
|
||||
before you restart the session...
|
||||
<pre>
|
||||
class LogInTest extends WebTestCase {
|
||||
...
|
||||
function testLoseAuthenticationAfterOneHour() {
|
||||
$this->get('http://www.my-site.com/login.php');
|
||||
$this->setField('u', 'Me');
|
||||
$this->setField('p', 'Secret');
|
||||
$this->click('Log in');
|
||||
$this->assertText('Welcome Me');
|
||||
<strong>
|
||||
$this->ageCookies(3600);</strong>
|
||||
$this->restart();
|
||||
$this->get('http://www.my-site.com/restricted.php');
|
||||
$this->assertText('Access denied');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
After the restart it will appear that cookies are an
|
||||
hour older and any that pass their expiry will have
|
||||
disappeared.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<span class="chosen">Authentication</span>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
447
simpletest/docs/en/browser_documentation.html
Normal file
447
simpletest/docs/en/browser_documentation.html
Normal file
@@ -0,0 +1,447 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest documentation for the scriptable web browser component</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<span class="chosen">Scriptable browser</span>
|
||||
</div></div>
|
||||
<h1>PHP Scriptable Web Browser</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Using the bundled <a href="#scripting">web browser in scripts</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#debug">Debugging</a> failed pages
|
||||
</li>
|
||||
<li>
|
||||
Complex <a href="#unit">tests with multiple web browsers</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
SimpleTest's web browser component can be used not just
|
||||
outside of the <span class="new_code">WebTestCase</span> class, but also
|
||||
independently of the SimpleTest framework itself.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="scripting"><h2>The Scriptable Browser</h2></a></p>
|
||||
<p>
|
||||
You can use the web browser in PHP scripts to confirm
|
||||
services are up and running, or to extract information
|
||||
from them at a regular basis.
|
||||
For example, here is a small script to extract the current number of
|
||||
open PHP 5 bugs from the <a href="http://www.php.net/">PHP web site</a>...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('simpletest/browser.php');
|
||||
|
||||
$browser = &new SimpleBrowser();
|
||||
$browser->get('http://php.net/');
|
||||
$browser->click('reporting bugs');
|
||||
$browser->click('statistics');
|
||||
$page = $browser->click('PHP 5 bugs only');
|
||||
preg_match('/status=Open.*?by=Any.*?(\d+)<\/a>/', $page, $matches);
|
||||
print $matches[1];
|
||||
?></strong>
|
||||
</pre>
|
||||
There are simpler methods to do this particular example in PHP
|
||||
of course.
|
||||
For example you can just use the PHP <span class="new_code">file()</span>
|
||||
command against what here is a pretty fixed page.
|
||||
However, using the web browser for scripts allows authentication,
|
||||
correct handling of cookies, automatic loading of frames, redirects,
|
||||
form submission and the ability to examine the page headers.
|
||||
Such methods are fragile against a site that is constantly
|
||||
evolving and you would want a more direct way of accessing
|
||||
data in a permanent set up, but for simple tasks this can provide
|
||||
a very rapid solution.
|
||||
</p>
|
||||
<p>
|
||||
All of the navigation methods used in the
|
||||
<a href="web_tester_documentation.html">WebTestCase</a>
|
||||
are present in the <span class="new_code">SimpleBrowser</span> class, but
|
||||
the assertions are replaced with simpler accessors.
|
||||
Here is a full list of the page navigation methods...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">addHeader($header)</span></td>
|
||||
<td>Adds a header to every fetch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useProxy($proxy, $username, $password)</span></td>
|
||||
<td>Use this proxy from now on</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">head($url, $parameters)</span></td>
|
||||
<td>Perform a HEAD request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">get($url, $parameters)</span></td>
|
||||
<td>Fetch a page with GET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">post($url, $parameters)</span></td>
|
||||
<td>Fetch a page with POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLink($label)</span></td>
|
||||
<td>Follows a link by label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLinkById($id)</span></td>
|
||||
<td>Follows a link by attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getUrl()</span></td>
|
||||
<td>Current URL of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getTitle()</span></td>
|
||||
<td>Page title</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getContent()</span></td>
|
||||
<td>Raw page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getContentAsText()</span></td>
|
||||
<td>HTML removed except for alt text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">retry()</span></td>
|
||||
<td>Repeat the last request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">back()</span></td>
|
||||
<td>Use the browser back button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">forward()</span></td>
|
||||
<td>Use the browser forward button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">authenticate($username, $password)</span></td>
|
||||
<td>Retry page or frame after a 401 response</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">restart($date)</span></td>
|
||||
<td>Restarts the browser for a new session</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ageCookies($interval)</span></td>
|
||||
<td>Ages the cookies by the specified time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setCookie($name, $value)</span></td>
|
||||
<td>Sets an additional cookie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getCookieValue($host, $path, $name)</span></td>
|
||||
<td>Reads the most specific cookie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getCurrentCookieValue($name)</span></td>
|
||||
<td>Reads cookie for the current context</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
The methods <span class="new_code">SimpleBrowser::useProxy()</span> and
|
||||
<span class="new_code">SimpleBrowser::addHeader()</span> are special.
|
||||
Once called they continue to apply to all subsequent fetches.
|
||||
</p>
|
||||
<p>
|
||||
Navigating forms is similar to the
|
||||
<a href="form_testing_documentation.html">WebTestCase form navigation</a>...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setField($name, $value)</span></td>
|
||||
<td>Sets all form fields with that name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFieldById($id, $value)</span></td>
|
||||
<td>Sets all form fields with that id</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getField($name)</span></td>
|
||||
<td>Accessor for a form element value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getFieldById($id)</span></td>
|
||||
<td>Accessor for a form element value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmit($label)</span></td>
|
||||
<td>Submits form by button label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitByName($name)</span></td>
|
||||
<td>Submits form by button attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitById($id)</span></td>
|
||||
<td>Submits form by button attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImage($label, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by title or alt text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageByName($name, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageById($id, $x, $y)</span></td>
|
||||
<td>Clicks an input tag of type image by ID attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">submitFormById($id)</span></td>
|
||||
<td>Submits by the form tag attribute</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
At the moment there aren't any methods to list available forms
|
||||
and fields.
|
||||
This will probably be added to later versions of SimpleTest.
|
||||
</p>
|
||||
<p>
|
||||
Within a page, individual frames can be selected.
|
||||
If no selection is made then all the frames are merged together
|
||||
in one large conceptual page.
|
||||
The content of the current page will be a concatenation of all of the
|
||||
frames in the order that they were specified in the "frameset"
|
||||
tags.
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">getFrames()</span></td>
|
||||
<td>A dump of the current frame structure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getFrameFocus()</span></td>
|
||||
<td>Current frame label or index</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocusByIndex($choice)</span></td>
|
||||
<td>Select a frame numbered from 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocus($name)</span></td>
|
||||
<td>Select frame by label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clearFrameFocus()</span></td>
|
||||
<td>Treat all the frames as a single page</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
When focused on a single frame, the content will come from
|
||||
that frame only.
|
||||
This includes links to click and forms to submit.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="debug"><h2>What went wrong?</h2></a></p>
|
||||
<p>
|
||||
All of this functionality is great when we actually manage to fetch pages,
|
||||
but that doesn't always happen.
|
||||
To help figure out what went wrong, the browser has some methods to
|
||||
aid in debugging...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setConnectionTimeout($timeout)</span></td>
|
||||
<td>Close the socket on overrun</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getRequest()</span></td>
|
||||
<td>Raw request header of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getHeaders()</span></td>
|
||||
<td>Raw response header of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getTransportError()</span></td>
|
||||
<td>Any socket level errors in the last fetch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getResponseCode()</span></td>
|
||||
<td>HTTP response of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getMimeType()</span></td>
|
||||
<td>Mime type of page or frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getAuthentication()</span></td>
|
||||
<td>Authentication type in 401 challenge header</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getRealm()</span></td>
|
||||
<td>Authentication realm in 401 challenge header</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setMaximumRedirects($max)</span></td>
|
||||
<td>Number of redirects before page is loaded anyway</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setMaximumNestedFrames($max)</span></td>
|
||||
<td>Protection against recursive framesets</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ignoreFrames()</span></td>
|
||||
<td>Disables frames support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useFrames()</span></td>
|
||||
<td>Enables frames support</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ignoreCookies()</span></td>
|
||||
<td>Disables sending and receiving of cookies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useCookies()</span></td>
|
||||
<td>Enables cookie support</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
The methods <span class="new_code">SimpleBrowser::setConnectionTimeout()</span>
|
||||
<span class="new_code">SimpleBrowser::setMaximumRedirects()</span>,
|
||||
<span class="new_code">SimpleBrowser::setMaximumNestedFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::ignoreFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::useFrames()</span>,
|
||||
<span class="new_code">SimpleBrowser::ignoreCookies()</span> and
|
||||
<span class="new_code">SimpleBrowser::useCokies()</span> continue to apply
|
||||
to every subsequent request.
|
||||
The other methods are frames aware.
|
||||
This means that if you have an individual frame that is not
|
||||
loading, navigate to it using <span class="new_code">SimpleBrowser::setFrameFocus()</span>
|
||||
and you can then use <span class="new_code">SimpleBrowser::getRequest()</span>, etc to
|
||||
see what happened.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="unit"><h2>Complex unit tests with multiple browsers</h2></a></p>
|
||||
<p>
|
||||
Anything that could be done in a
|
||||
<a href="web_tester_documentation.html">WebTestCase</a> can
|
||||
now be done in a <a href="unit_tester_documentation.html">UnitTestCase</a>.
|
||||
This means that we can freely mix domain object testing with the
|
||||
web interface...
|
||||
<pre>
|
||||
<strong>class TestOfRegistration extends UnitTestCase {
|
||||
function testNewUserAddedToAuthenticator() {</strong>
|
||||
$browser = &new SimpleBrowser();
|
||||
$browser->get('http://my-site.com/register.php');
|
||||
$browser->setField('email', 'me@here');
|
||||
$browser->setField('password', 'Secret');
|
||||
$browser->click('Register');
|
||||
<strong>
|
||||
$authenticator = &new Authenticator();
|
||||
$member = &$authenticator->findByEmail('me@here');
|
||||
$this->assertEqual($member->getPassword(), 'Secret');
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
While this may be a useful temporary expediency, I am not a fan
|
||||
of this type of testing.
|
||||
The testing has cut across application layers, make it twice as
|
||||
likely it will need refactoring when the code changes.
|
||||
</p>
|
||||
<p>
|
||||
A more useful case of where using the browser directly can be helpful
|
||||
is where the <span class="new_code">WebTestCase</span> cannot cope.
|
||||
An example is where two browsers are needed at the same time.
|
||||
</p>
|
||||
<p>
|
||||
For example, say we want to disallow multiple simultaneous
|
||||
usage of a site with the same username.
|
||||
This test case will do the job...
|
||||
<pre>
|
||||
class TestOfSecurity extends UnitTestCase {
|
||||
function testNoMultipleLoginsFromSameUser() {<strong>
|
||||
$first = &new SimpleBrowser();
|
||||
$first->get('http://my-site.com/login.php');
|
||||
$first->setField('name', 'Me');
|
||||
$first->setField('password', 'Secret');
|
||||
$first->click('Enter');
|
||||
$this->assertEqual($first->getTitle(), 'Welcome');
|
||||
|
||||
$second = &new SimpleBrowser();
|
||||
$second->get('http://my-site.com/login.php');
|
||||
$second->setField('name', 'Me');
|
||||
$second->setField('password', 'Secret');
|
||||
$second->click('Enter');
|
||||
$this->assertEqual($second->getTitle(), 'Access Denied');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
You can also use the <span class="new_code">SimpleBrowser</span> class
|
||||
directly when you want to write test cases using a different
|
||||
test tool than SimpleTest.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<span class="chosen">Scriptable browser</span>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
121
simpletest/docs/en/docs.css
Normal file
121
simpletest/docs/en/docs.css
Normal file
@@ -0,0 +1,121 @@
|
||||
body {
|
||||
padding-left: 3%;
|
||||
padding-right: 3%;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
pre {
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
font-size: 90%;
|
||||
border: 1px solid;
|
||||
border-color: #999966;
|
||||
background-color: #ffffcc;
|
||||
padding: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.code, .new_code, pre.new_code {
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.copyright {
|
||||
font-size: 80%;
|
||||
color: gray;
|
||||
}
|
||||
div.copyright a {
|
||||
margin-top: 1em;
|
||||
color: gray;
|
||||
}
|
||||
ul.api {
|
||||
border: 2px outset;
|
||||
border-color: gray;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
ul.api li {
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
list-style: none;
|
||||
text-indent: -3em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
div.demo {
|
||||
border: 4px ridge;
|
||||
border-color: gray;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: white;
|
||||
}
|
||||
div.demo span.fail {
|
||||
color: red;
|
||||
}
|
||||
div.demo span.pass {
|
||||
color: green;
|
||||
}
|
||||
div.demo h1 {
|
||||
font-size: 12pt;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
div.menu {
|
||||
text-align: center;
|
||||
}
|
||||
table {
|
||||
border: 2px outset;
|
||||
border-color: gray;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
margin-left: 5%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
td {
|
||||
font-size: 90%;
|
||||
}
|
||||
.shell {
|
||||
color: white;
|
||||
}
|
||||
pre.shell {
|
||||
border: 4px ridge;
|
||||
border-color: gray;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: #000100;
|
||||
color: #99ff99;
|
||||
font-size: 90%;
|
||||
}
|
||||
pre.file {
|
||||
color: black;
|
||||
border: 1px solid;
|
||||
border-color: black;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
margin-left: 20px;
|
||||
margin-right: 40px;
|
||||
background-color: white;
|
||||
font-size: 90%;
|
||||
}
|
||||
form.demo {
|
||||
background-color: lightgray;
|
||||
border: 4px outset;
|
||||
border-color: lightgray;
|
||||
padding: 10px;
|
||||
margin-right: 40%;
|
||||
}
|
||||
dl, dd {
|
||||
margin: 10px;
|
||||
margin-left: 30px;
|
||||
}
|
||||
em {
|
||||
font-weight: bold;
|
||||
font-family: "courier new", courier, typewriter, monospace;
|
||||
}
|
422
simpletest/docs/en/expectation_documentation.html
Normal file
422
simpletest/docs/en/expectation_documentation.html
Normal file
@@ -0,0 +1,422 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Extending the SimpleTest unit tester with additional expectation classes
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<span class="chosen">Expectations</span>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Expectation documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Using expectations for
|
||||
<a href="#mock">more precise testing with mock objects</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#behaviour">Changing mock object behaviour</a> with expectations
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extending">Extending the expectations</a>
|
||||
</li>
|
||||
<li>
|
||||
Underneath SimpleTest <a href="#unit">uses expectation classes</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="mock"><h2>More control over mock objects</h2></a></p>
|
||||
<p>
|
||||
The default behaviour of the
|
||||
<a href="mock_objects_documentation.html">mock objects</a>
|
||||
in
|
||||
<a href="http://sourceforge.net/projects/simpletest/">SimpleTest</a>
|
||||
is either an identical match on the argument or to allow any argument at all.
|
||||
For almost all tests this is sufficient.
|
||||
Sometimes, though, you want to weaken a test case.
|
||||
</p>
|
||||
<p>
|
||||
One place where a test can be too tightly coupled is with
|
||||
text matching.
|
||||
Suppose we have a component that outputs a helpful error
|
||||
message when something goes wrong.
|
||||
You want to test that the correct error was sent, but the actual
|
||||
text may be rather long.
|
||||
If you test for the text exactly, then every time the exact wording
|
||||
of the message changes, you will have to go back and edit the test suite.
|
||||
</p>
|
||||
<p>
|
||||
For example, suppose we have a news service that has failed
|
||||
to connect to its remote source.
|
||||
<pre>
|
||||
<strong>class NewsService {
|
||||
...
|
||||
function publish(&$writer) {
|
||||
if (! $this->isConnected()) {
|
||||
$writer->write('Cannot connect to news service "' .
|
||||
$this->_name . '" at this time. ' .
|
||||
'Please try again later.');
|
||||
}
|
||||
...
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Here it is sending its content to a
|
||||
<span class="new_code">Writer</span> class.
|
||||
We could test this behaviour with a
|
||||
<span class="new_code">MockWriter</span> like so...
|
||||
<pre>
|
||||
class TestOfNewsService extends UnitTestCase {
|
||||
...
|
||||
function testConnectionFailure() {<strong>
|
||||
$writer = &new MockWriter();
|
||||
$writer->expectOnce('write', array(
|
||||
'Cannot connect to news service ' .
|
||||
'"BBC News" at this time. ' .
|
||||
'Please try again later.'));
|
||||
|
||||
$service = &new NewsService('BBC News');
|
||||
$service->publish($writer);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is a good example of a brittle test.
|
||||
If we decide to add additional instructions, such as
|
||||
suggesting an alternative news source, we will break
|
||||
our tests even though no underlying functionality
|
||||
has been altered.
|
||||
</p>
|
||||
<p>
|
||||
To get around this, we would like to do a regular expression
|
||||
test rather than an exact match.
|
||||
We can actually do this with...
|
||||
<pre>
|
||||
class TestOfNewsService extends UnitTestCase {
|
||||
...
|
||||
function testConnectionFailure() {
|
||||
$writer = &new MockWriter();<strong>
|
||||
$writer->expectOnce(
|
||||
'write',
|
||||
array(new PatternExpectation('/cannot connect/i')));</strong>
|
||||
|
||||
$service = &new NewsService('BBC News');
|
||||
$service->publish($writer);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Instead of passing in the expected parameter to the
|
||||
<span class="new_code">MockWriter</span> we pass an
|
||||
expectation class called
|
||||
<span class="new_code">WantedPatternExpectation</span>.
|
||||
The mock object is smart enough to recognise this as special
|
||||
and to treat it differently.
|
||||
Rather than simply comparing the incoming argument to this
|
||||
object, it uses the expectation object itself to
|
||||
perform the test.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">WantedPatternExpectation</span> takes
|
||||
the regular expression to match in its constructor.
|
||||
Whenever a comparison is made by the <span class="new_code">MockWriter</span>
|
||||
against this expectation class, it will do a
|
||||
<span class="new_code">preg_match()</span> with this pattern.
|
||||
With our test case above, as long as "cannot connect"
|
||||
appears in the text of the string, the mock will issue a pass
|
||||
to the unit tester.
|
||||
The rest of the text does not matter.
|
||||
</p>
|
||||
<p>
|
||||
The possible expectation classes are...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">AnythingExpectation</span></td>
|
||||
<td>Will always match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">EqualExpectation</span></td>
|
||||
<td>An equality, rather than the stronger identity comparison</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotEqualExpectation</span></td>
|
||||
<td>An inequality comparison</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">IndenticalExpectation</span></td>
|
||||
<td>The default mock object check which must match exactly</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotIndenticalExpectation</span></td>
|
||||
<td>Inverts the mock object logic</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">WithinMarginExpectation</span></td>
|
||||
<td>Compares a value to within a margin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">OutsideMarginExpectation</span></td>
|
||||
<td>Checks that a value is out side the margin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">PatternExpectation</span></td>
|
||||
<td>Uses a Perl Regex to match a string</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NoPatternExpectation</span></td>
|
||||
<td>Passes only if failing a Perl Regex</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">IsAExpectation</span></td>
|
||||
<td>Checks the type or class name only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">NotAExpectation</span></td>
|
||||
<td>Opposite of the <span class="new_code">IsAExpectation</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">MethodExistsExpectation</span></td>
|
||||
<td>Checks a method is available on an object</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
Most take the expected value in the constructor.
|
||||
The exceptions are the pattern matchers, which take a regular expression,
|
||||
and the <span class="new_code">IsAExpectation</span> and <span class="new_code">NotAExpectation</span> which takes a type
|
||||
or class name as a string.
|
||||
</p>
|
||||
<p>
|
||||
Some examples...
|
||||
</p>
|
||||
<p>
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new IdenticalExpectation(14)));
|
||||
</pre>
|
||||
This is the same as <span class="new_code">$mock->expectOnce('method', array(14))</span>.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new EqualExpectation(14)));
|
||||
</pre>
|
||||
This is different from the previous version in that the string
|
||||
<span class="new_code">"14"</span> as a parameter will also pass.
|
||||
Sometimes the additional type checks of SimpleTest are too restrictive.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new AnythingExpectation(14)));
|
||||
</pre>
|
||||
This is the same as <span class="new_code">$mock->expectOnce('method', array('*'))</span>.
|
||||
<pre>
|
||||
$mock->expectOnce('method', array(new IdenticalExpectation('*')));
|
||||
</pre>
|
||||
This is handy if you want to assert a literal <span class="new_code">"*"</span>.
|
||||
<pre>
|
||||
new NotIdenticalExpectation(14)
|
||||
</pre>
|
||||
This matches on anything other than integer 14.
|
||||
Even the string <span class="new_code">"14"</span> would pass.
|
||||
<pre>
|
||||
new WithinMarginExpectation(14.0, 0.001)
|
||||
</pre>
|
||||
This will accept any value from 13.999 to 14.001 inclusive.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="behaviour"><h2>Using expectations to control stubs</h2></a></p>
|
||||
<p>
|
||||
The expectation classes can be used not just for sending assertions
|
||||
from mock objects, but also for selecting behaviour for the
|
||||
<a href="mock_objects_documentation.html">mock objects</a>.
|
||||
Anywhere a list of arguments is given, a list of expectation objects
|
||||
can be inserted instead.
|
||||
</p>
|
||||
<p>
|
||||
Suppose we want a mock authorisation server to simulate a successful login,
|
||||
but only if it receives a valid session object.
|
||||
We can do this as follows...
|
||||
<pre>
|
||||
Mock::generate('Authorisation');
|
||||
<strong>
|
||||
$authorisation = new MockAuthorisation();
|
||||
$authorisation->setReturnValue(
|
||||
'isAllowed',
|
||||
true,
|
||||
array(new IsAExpectation('Session', 'Must be a session')));
|
||||
$authorisation->setReturnValue('isAllowed', false);</strong>
|
||||
</pre>
|
||||
We have set the default mock behaviour to return false when
|
||||
<span class="new_code">isAllowed</span> is called.
|
||||
When we call the method with a single parameter that
|
||||
is a <span class="new_code">Session</span> object, it will return true.
|
||||
We have also added a second parameter as a message.
|
||||
This will be displayed as part of the mock object
|
||||
failure message if this expectation is the cause of
|
||||
a failure.
|
||||
</p>
|
||||
<p>
|
||||
This kind of sophistication is rarely useful, but is included for
|
||||
completeness.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="extending"><h2>Creating your own expectations</h2></a></p>
|
||||
<p>
|
||||
The expectation classes have a very simple structure.
|
||||
So simple that it is easy to create your own versions for
|
||||
commonly used test logic.
|
||||
</p>
|
||||
<p>
|
||||
As an example here is the creation of a class to test for
|
||||
valid IP addresses.
|
||||
In order to work correctly with the stubs and mocks the new
|
||||
expectation class should extend
|
||||
<span class="new_code">SimpleExpectation</span>...
|
||||
<pre>
|
||||
<strong>class ValidIp extends SimpleExpectation {
|
||||
|
||||
function test($ip) {
|
||||
return (ip2long($ip) != -1);
|
||||
}
|
||||
|
||||
function testMessage($ip) {
|
||||
return "Address [$ip] should be a valid IP address";
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
There are only two methods to implement.
|
||||
The <span class="new_code">test()</span> method should
|
||||
evaluate to true if the expectation is to pass, and
|
||||
false otherwise.
|
||||
The <span class="new_code">testMessage()</span> method
|
||||
should simply return some helpful text explaining the test
|
||||
that was carried out.
|
||||
</p>
|
||||
<p>
|
||||
This class can now be used in place of the earlier expectation
|
||||
classes.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="unit"><h2>Under the bonnet of the unit tester</h2></a></p>
|
||||
<p>
|
||||
The <a href="http://sourceforge.net/projects/simpletest/">SimpleTest unit testing framework</a>
|
||||
also uses the expectation classes internally for the
|
||||
<a href="unit_test_documentation.html">UnitTestCase class</a>.
|
||||
We can also take advantage of these mechanisms to reuse our
|
||||
homebrew expectation classes within the test suites directly.
|
||||
</p>
|
||||
<p>
|
||||
The most crude way of doing this is to use the
|
||||
<span class="new_code">SimpleTest::assert()</span> method to
|
||||
test against it directly...
|
||||
<pre>
|
||||
<strong>class TestOfNetworking extends UnitTestCase {
|
||||
...
|
||||
function testGetValidIp() {
|
||||
$server = &new Server();
|
||||
$this->assert(
|
||||
new ValidIp(),
|
||||
$server->getIp(),
|
||||
'Server IP address->%s');
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
This is a little untidy compared with our usual
|
||||
<span class="new_code">assert...()</span> syntax.
|
||||
</p>
|
||||
<p>
|
||||
For such a simple case we would normally create a
|
||||
separate assertion method on our test case rather
|
||||
than bother using the expectation class.
|
||||
If we pretend that our expectation is a little more
|
||||
complicated for a moment, so that we want to reuse it,
|
||||
we get...
|
||||
<pre>
|
||||
class TestOfNetworking extends UnitTestCase {
|
||||
...<strong>
|
||||
function assertValidIp($ip, $message = '%s') {
|
||||
$this->assert(new ValidIp(), $ip, $message);
|
||||
}</strong>
|
||||
|
||||
function testGetValidIp() {
|
||||
$server = &new Server();<strong>
|
||||
$this->assertValidIp(
|
||||
$server->getIp(),
|
||||
'Server IP address->%s');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
It is unlikely we would ever need this degree of control
|
||||
over the testing machinery.
|
||||
It is rare to need the expectations for more than pattern
|
||||
matching.
|
||||
Also, complex expectation classes could make the tests
|
||||
harder to read and debug.
|
||||
These mechanisms are really of most use to authors of systems
|
||||
that will extend the test framework to create their own tool set.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The expectations mimic the constraints in <a href="http://www.jmock.org/">JMock</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">Full API for SimpleTest</a>
|
||||
from the PHPDoc.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<span class="chosen">Expectations</span>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
342
simpletest/docs/en/form_testing_documentation.html
Normal file
342
simpletest/docs/en/form_testing_documentation.html
Normal file
@@ -0,0 +1,342 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Simple Test documentation for testing HTML forms</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<span class="chosen">Testing forms</span>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Form testing documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Changing form values and successfully
|
||||
<a href="#submit">Submitting a simple form</a>
|
||||
</li>
|
||||
<li>
|
||||
Handling <a href="#multiple">widgets with multiple values</a>
|
||||
by setting lists.
|
||||
</li>
|
||||
<li>
|
||||
Bypassing javascript to <a href="#hidden-field">set a hidden field</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#raw">Raw posting</a> when you don't have a button
|
||||
to click.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="submit"><h2>Submitting a simple form</h2></a></p>
|
||||
<p>
|
||||
When a page is fetched by the <span class="new_code">WebTestCase</span>
|
||||
using <span class="new_code">get()</span> or
|
||||
<span class="new_code">post()</span> the page content is
|
||||
automatically parsed.
|
||||
This results in any form controls that are inside <form> tags
|
||||
being available from within the test case.
|
||||
For example, if we have this snippet of HTML...
|
||||
<pre>
|
||||
<form>
|
||||
<input type="text" name="a" value="A default" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</pre>
|
||||
Which looks like this...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<input type="text" name="a" value="A default">
|
||||
<input type="submit" value="Go">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
We can navigate to this code, via the
|
||||
<a href="http://www.lastcraft.com/form_testing_documentation.php">LastCraft</a>
|
||||
site, with the following test...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
<strong>
|
||||
function testDefaultValue() {
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertField('a', 'A default');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Immediately after loading the page all of the HTML controls are set at
|
||||
their default values just as they would appear in the web browser.
|
||||
The assertion tests that a HTML widget exists in the page with the
|
||||
name "a" and that it is currently set to the value
|
||||
"A default".
|
||||
As usual, we could use a pattern expectation instead if a fixed
|
||||
string.
|
||||
</p>
|
||||
<p>
|
||||
We could submit the form straight away, but first we'll change
|
||||
the value of the text field and only then submit it...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
|
||||
function testDefaultValue() {
|
||||
$this->get('http://www.my-site.com/');
|
||||
$this->assertField('a', 'A default');<strong>
|
||||
$this->setField('a', 'New value');
|
||||
$this->click('Go');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Because we didn't specify a method attribute on the form tag, and
|
||||
didn't specify an action either, the test case will follow
|
||||
the usual browser behaviour of submitting the form data as a <em>GET</em>
|
||||
request back to the same location.
|
||||
SimpleTest tries to emulate typical browser behaviour as much as possible,
|
||||
rather than attempting to catch missing attributes on tags.
|
||||
This is because the target of the testing framework is the PHP application
|
||||
logic, not syntax or other errors in the HTML code.
|
||||
For HTML errors, other tools such as
|
||||
<a href="http://www.w3.org/People/Raggett/tidy/">HTMLTidy</a> should be used.
|
||||
</p>
|
||||
<p>
|
||||
If a field is not present in any form, or if an option is unavailable,
|
||||
then <span class="new_code">WebTestCase::setField()</span> will return
|
||||
<span class="new_code">false</span>.
|
||||
For example, suppose we wish to verify that a "Superuser"
|
||||
option is not present in this form...
|
||||
<pre>
|
||||
<strong>Select type of user to add:</strong>
|
||||
<select name="type">
|
||||
<option>Subscriber</option>
|
||||
<option>Author</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</pre>
|
||||
Which looks like...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<strong>Select type of user to add:</strong>
|
||||
<select name="type">
|
||||
<option>Subscriber</option>
|
||||
<option>Author</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
The following test will confirm it...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...
|
||||
function testNoSuperuserChoiceAvailable() {<strong>
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertFalse($this->setField('type', 'Superuser'));</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The selection will not be changed on a failure to set
|
||||
a widget value.
|
||||
</p>
|
||||
<p>
|
||||
Here is the full list of widgets currently supported...
|
||||
<ul>
|
||||
<li>Text fields, including hidden and password fields.</li>
|
||||
<li>Submit buttons including the button tag, although not yet reset buttons</li>
|
||||
<li>Text area. This includes text wrapping behaviour.</li>
|
||||
<li>Checkboxes, including multiple checkboxes in the same form.</li>
|
||||
<li>Drop down selections, including multiple selects.</li>
|
||||
<li>Radio buttons.</li>
|
||||
<li>Images.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
The browser emulation offered by SimpleTest mimics
|
||||
the actions which can be perform by a user on a
|
||||
standard HTML page. Javascript is not supported, and
|
||||
it's unlikely that support will be added any time
|
||||
soon.
|
||||
</p>
|
||||
<p>
|
||||
Of particular note is that the Javascript idiom of
|
||||
passing form results by setting a hidden field cannot
|
||||
be performed using the normal SimpleTest
|
||||
commands. See below for a way to test such forms.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="multiple"><h2>Fields with multiple values</h2></a></p>
|
||||
<p>
|
||||
SimpleTest can cope with two types of multivalue controls: Multiple
|
||||
selection drop downs, and multiple checkboxes with the same name
|
||||
within a form.
|
||||
The multivalue nature of these means that setting and testing
|
||||
are slightly different.
|
||||
Using checkboxes as an example...
|
||||
<pre>
|
||||
<form class="demo">
|
||||
<strong>Create privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="c" checked><br>
|
||||
<strong>Retrieve privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="r" checked><br>
|
||||
<strong>Update privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="u" checked><br>
|
||||
<strong>Destroy privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="d" checked><br>
|
||||
<input type="submit" value="Enable Privileges">
|
||||
</form>
|
||||
</pre>
|
||||
Which renders as...
|
||||
</p>
|
||||
<p>
|
||||
<form class="demo">
|
||||
<strong>Create privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="c" checked><br>
|
||||
<strong>Retrieve privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="r" checked><br>
|
||||
<strong>Update privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="u" checked><br>
|
||||
<strong>Destroy privileges allowed:</strong>
|
||||
<input type="checkbox" name="crud" value="d" checked><br>
|
||||
<input type="submit" value="Enable Privileges">
|
||||
</form>
|
||||
</p>
|
||||
<p>
|
||||
If we wish to disable all but the retrieval privileges and
|
||||
submit this information we can do it like this...
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...<strong>
|
||||
function testDisableNastyPrivileges() {
|
||||
$this->get('http://www.lastcraft.com/form_testing_documentation.php');
|
||||
$this->assertField('crud', array('c', 'r', 'u', 'd'));
|
||||
$this->setField('crud', array('r'));
|
||||
$this->click('Enable Privileges');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Instead of setting the field to a single value, we give it a list
|
||||
of values.
|
||||
We do the same when testing expected values.
|
||||
We can then write other test code to confirm the effect of this, perhaps
|
||||
by logging in as that user and attempting an update.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="hidden-field"><h2>Forms which use javascript to set a hidden field</h2></a></p>
|
||||
<p>
|
||||
If you want to test a form which relies on javascript to set a hidden
|
||||
field, you can't just call setField().
|
||||
The following code will <em>not</em> work:
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
function testMyJavascriptForm() {
|
||||
<strong>// This does *not* work</strong>
|
||||
$this->setField('a_hidden_field', '123');
|
||||
$this->clickSubmit('OK');
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Instead, you need to pass the additional form parameters to the
|
||||
clickSubmit() method:
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
function testMyJavascriptForm() {
|
||||
// Pass the hidden field value as an additional POST variable
|
||||
<strong>$this->clickSubmit('OK', array('a_hidden_field'=>'123'));</strong>
|
||||
}
|
||||
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
Bear in mind that in doing this you're effectively stubbing out a
|
||||
part of your software (the javascript code in the form), and
|
||||
perhaps you might be better off using something like
|
||||
<a href="http://selenium.openqa.org/">Selenium</a> to ensure a complete
|
||||
acceptance test.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="raw"><h2>Raw posting</h2></a></p>
|
||||
<p>
|
||||
If you want to test a form handler, but have not yet written
|
||||
or do not have access to the form itself, you can create a
|
||||
form submission by hand.
|
||||
<pre>
|
||||
class SimpleFormTests extends WebTestCase {
|
||||
...<strong>
|
||||
function testAttemptedHack() {
|
||||
$this->post(
|
||||
'http://www.my-site.com/add_user.php',
|
||||
array('type' => 'superuser'));
|
||||
$this->assertNoText('user created');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
By adding data to the <span class="new_code">WebTestCase::post()</span>
|
||||
method, we are attempting to fetch the page as a form submission.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<span class="chosen">Testing forms</span>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
386
simpletest/docs/en/group_test_documentation.html
Normal file
386
simpletest/docs/en/group_test_documentation.html
Normal file
@@ -0,0 +1,386 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP test suites</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<span class="chosen">Group tests</span>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Test suite documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Different ways to <a href="#group">group tests</a> together.
|
||||
</li>
|
||||
<li>
|
||||
Combining group tests into <a href="#higher">larger groups</a>.
|
||||
</li>
|
||||
<li>
|
||||
Integrating <a href="#legacy">legacy test cases</a> from other
|
||||
types of PHPUnit.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="group"><h2>Grouping tests into suites</h2></a></p>
|
||||
<p>
|
||||
To run test cases as part of a group, the test cases should really
|
||||
be placed in files without the runner code...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('../classes/io.php');
|
||||
|
||||
class FileTester extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
|
||||
class SocketTester extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
?></strong>
|
||||
</pre>
|
||||
As many cases as needed can appear in a single file.
|
||||
They should include any code they need, such as the library
|
||||
being tested, but none of the simple test libraries.
|
||||
</p>
|
||||
<p>
|
||||
If you have extended any test cases, you can include them
|
||||
as well. In PHP 4...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('../classes/io.php');
|
||||
<strong>
|
||||
class MyFileTestCase extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
SimpleTest::ignore('MyFileTestCase');</strong>
|
||||
|
||||
class FileTester extends MyFileTestCase { ... }
|
||||
|
||||
class SocketTester extends UnitTestCase { ... }
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">FileTester</span> class does
|
||||
not contain any actual tests, but is a base class for other
|
||||
test cases.
|
||||
For this reason we use the
|
||||
<span class="new_code">SimpleTestOptions::ignore()</span> directive
|
||||
to tell the upcoming group test to ignore it.
|
||||
This directive can appear anywhere in the file and works
|
||||
when a whole file of test cases is loaded (see below).
|
||||
</p>
|
||||
<p>
|
||||
If you are using PHP 5, you do not need this special directive at all.
|
||||
Simply mark any test cases that should not be run as abstract...
|
||||
<pre>
|
||||
<strong>abstract</strong> class MyFileTestCase extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
|
||||
class FileTester extends MyFileTestCase { ... }
|
||||
|
||||
class SocketTester extends UnitTestCase { ... }
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
We will call this sample <em>file_test.php</em>.
|
||||
Next we create a group test file, called say <em>my_group_test.php</em>.
|
||||
You will think of a better name I am sure.
|
||||
</p>
|
||||
<p>
|
||||
We will add the test file using a safe method...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('file_test.php');
|
||||
|
||||
$test = &new TestSuite('All file tests');
|
||||
$test->addTestCase(new FileTestCase());
|
||||
$test->run(new HtmlReporter());</strong>
|
||||
?>
|
||||
</pre>
|
||||
This instantiates the test case before the test suite is
|
||||
run.
|
||||
This could get a little expensive with a large number of test
|
||||
cases, and can be surprising behaviour.
|
||||
</p>
|
||||
<p>
|
||||
The main problem is that for every test case
|
||||
that we add we will have
|
||||
to <span class="new_code">require_once()</span> the test code
|
||||
file and manually instantiate each and every test case.
|
||||
</p>
|
||||
<p>
|
||||
We can save a lot of typing with...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('file_test.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?&gt;
|
||||
</pre>
|
||||
What happens here is that the <span class="new_code">TestSuite</span>
|
||||
class has done the <span class="new_code">require_once()</span>
|
||||
for us.
|
||||
It then checks to see if any new test case classes
|
||||
have been created by the new file and automatically adds
|
||||
them to the group test.
|
||||
Now all we have to do is add each new file.
|
||||
</p>
|
||||
<p>
|
||||
No only that, but you can guarantee that the constructor is run
|
||||
just before the first test method and, in PHP 5, the destructor
|
||||
is run just after the last test method.
|
||||
</p>
|
||||
<p>
|
||||
There are two things that could go wrong and which require care...
|
||||
<ol>
|
||||
<li>
|
||||
The file could already have been parsed by PHP, and so no
|
||||
new classes will have been added. You should make
|
||||
sure that the test cases are only included in this file
|
||||
and no others (Note : with the new <cite>autorun</cite>
|
||||
functionnality, this problem has now been solved).
|
||||
</li>
|
||||
<li>
|
||||
New test case extension classes that get included will be
|
||||
placed in the group test and run also.
|
||||
You will need to add a <span class="new_code">SimpleTestOptions::ignore()</span>
|
||||
directive for these classes, or make sure that they are included
|
||||
before the <span class="new_code">TestSuite::addTestFile()</span>
|
||||
line, or make sure that they are abstract classes.
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="higher"><h2>Composite suites</h2></a></p>
|
||||
<p>
|
||||
The above method places all of the test cases into one large group.
|
||||
For larger projects though this may not be flexible enough; you
|
||||
may want to group the tests in all sorts of ways.
|
||||
</p>
|
||||
<p>
|
||||
To get a more flexible group test we can subclass
|
||||
<span class="new_code">TestSuite</span> and then instantiate it as needed...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
<strong>
|
||||
class FileTestSuite extends TestSuite {
|
||||
function FileTestSuite() {
|
||||
$this->TestSuite('All file tests');
|
||||
$this->addTestFile('file_test.php');
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
This effectively names the test in the constructor and then
|
||||
adds our test cases and a single group below.
|
||||
Of course we can add more than one group at this point.
|
||||
We can now invoke the tests from a separate runner file...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('file_test_suite.php');
|
||||
<strong>
|
||||
$test = &new FileTestSuite();
|
||||
$test->run(new HtmlReporter());</strong>
|
||||
?>
|
||||
</pre>
|
||||
...or we can group them into even larger group tests.
|
||||
We can even mix groups and test cases freely as long as
|
||||
we are careful about double includes...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>
|
||||
$test = &new BigTestSuite('Big group');
|
||||
$test->addTestFile('file_test_suite.php');
|
||||
$test->addTestFile('some_test_case.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
In the event of a double include, ony the first instance
|
||||
of the test case will be run.
|
||||
</p>
|
||||
<p>
|
||||
If we still wish to run the original group test, and we
|
||||
don't want all of these little runner files, we can
|
||||
put the test runner code around guard bars when we create
|
||||
each group.
|
||||
<pre>
|
||||
<?php
|
||||
class FileTestSuite extends TestSuite {
|
||||
function FileTestSuite() {
|
||||
$this->TestSuite('All file tests');
|
||||
$test->addTestFile('file_test.php');
|
||||
}
|
||||
}
|
||||
<strong>
|
||||
if (! defined('RUNNER')) {
|
||||
define('RUNNER', true);</strong>
|
||||
$test = &new FileTestSuite();
|
||||
$test->run(new HtmlReporter());
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This approach requires the guard to be set when including
|
||||
the group test file, but this is still less hassle than
|
||||
lots of separate runner files.
|
||||
You include the same guard on the top level tests to make sure
|
||||
that <span class="new_code">run()</span> will run once only
|
||||
from the top level script that has been invoked.
|
||||
<pre>
|
||||
<?php<strong>
|
||||
define('RUNNER', true);</strong>
|
||||
require_once('file_test_suite.php');
|
||||
|
||||
$test = &new BigTestSuite('Big group');
|
||||
$test->addTestCase(new FileTestSuite());
|
||||
$test->addTestCase(...);
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
As with the normal test cases, a <span class="new_code">TestSuite</span> can
|
||||
be loaded with the <span class="new_code">TestSuite::addTestFile()</span> method.
|
||||
<pre>
|
||||
<?php
|
||||
define('RUNNER', true);
|
||||
|
||||
$test = &new BigTestSuite('Big group');<strong>
|
||||
$test->addTestFile('file_test_suite.php');
|
||||
$test->addTestFile(...);</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="legacy"><h2>Integrating legacy test cases</h2></a></p>
|
||||
<p>
|
||||
If you already have unit tests for your code or are extending external
|
||||
classes that have tests, it is unlikely that all of the test cases
|
||||
are in SimpleTest format.
|
||||
Fortunately it is possible to incorporate test cases from other
|
||||
unit testers directly into SimpleTest group tests.
|
||||
</p>
|
||||
<p>
|
||||
Say we have the following
|
||||
<a href="http://sourceforge.net/projects/phpunit">PhpUnit</a>
|
||||
test case in the file <em>config_test.php</em>...
|
||||
<pre>
|
||||
<strong>class ConfigFileTest extends TestCase {
|
||||
function ConfigFileTest() {
|
||||
$this->TestCase('Config file test');
|
||||
}
|
||||
|
||||
function testContents() {
|
||||
$config = new ConfigFile('test.conf');
|
||||
$this->assertRegexp('/me/', $config->getValue('username'));
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
The group test can recognise this as long as we include
|
||||
the appropriate adapter class before we add the test
|
||||
file...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('simpletest/adapters/phpunit_test_case.php');</strong>
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('config_test.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
There are only two adapters, the other is for the
|
||||
<a href="http://pear.php.net/manual/en/package.php.phpunit.php">PEAR</a>
|
||||
1.0 unit tester...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');<strong>
|
||||
require_once('simpletest/adapters/pear_test_case.php');</strong>
|
||||
|
||||
$test = &new TestSuite('All file tests');<strong>
|
||||
$test->addTestFile('some_pear_test_cases.php');</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
The PEAR test cases can be freely mixed with SimpleTest
|
||||
ones even in the same test file,
|
||||
but you cannot use SimpleTest assertions in the legacy
|
||||
test case versions.
|
||||
This is done as a check that you are not accidently making
|
||||
your test cases completely dependent on SimpleTest.
|
||||
You may want to do a PEAR release of your library for example,
|
||||
which would mean shipping it with valid PEAR::PhpUnit test
|
||||
cases.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<span class="chosen">Group tests</span>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
538
simpletest/docs/en/index.html
Normal file
538
simpletest/docs/en/index.html
Normal file
@@ -0,0 +1,538 @@
|
||||
<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">
|
||||
<span class="chosen">SimpleTest</span>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Simple Test for PHP</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#unit">Using unit tester</a>
|
||||
with an example.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#group">Grouping tests</a>
|
||||
for testing with one click.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#mock">Using mock objects</a>
|
||||
to ease testing and gain tighter control.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#web">Testing web pages</a>
|
||||
at the browser level.
|
||||
</li>
|
||||
</ul>
|
||||
<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, and includes
|
||||
<a href="http://www.mockobjects.com">mock objects</a>.
|
||||
</p>
|
||||
<p>
|
||||
What makes this tool immediately useful to the PHP developer is the internal
|
||||
web browser.
|
||||
This allows tests that navigate web sites, fill in forms and test pages.
|
||||
Being able to write these test in PHP means that it is easy to write
|
||||
integrated tests.
|
||||
An example might be confirming that a user was written to a database
|
||||
after a signing up through the web site.
|
||||
</p>
|
||||
<p>
|
||||
The quickest way to demonstrate SimpleTest 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>
|
||||
<?php
|
||||
<strong>require_once('simpletest/autorun.php');</strong>
|
||||
require_once('../classes/log.php');
|
||||
|
||||
class TestOfLogging extends <strong>UnitTestCase</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
|
||||
unpacked the toolset.
|
||||
The "autorun.php" file does more than just include the
|
||||
SimpleTest files, it also runs our test for us.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">TestOfLogging</span> is our first test case and it's
|
||||
currently empty.
|
||||
Each test case is a class that extends one of the SimpleTet base classes
|
||||
and we can have as many of these in the file as we want.
|
||||
</p>
|
||||
<p>
|
||||
With three lines of scaffolding, and our <span class="new_code">Log</span> class
|
||||
include, we have a test suite.
|
||||
No tests though.
|
||||
</p>
|
||||
<p>
|
||||
For our first test, 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/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
|
||||
class TestOfLogging extends UnitTestCase {
|
||||
function <strong>testLogCreatesNewFileOnFirstMessage()</strong> {
|
||||
@unlink('/temp/test.log');
|
||||
$log = new Log('/temp/test.log');
|
||||
<strong>$this->assertFalse(file_exists('/temp/test.log'));</strong>
|
||||
$log->message('Should write this to a file');
|
||||
<strong>$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 "test"
|
||||
and execute that method.
|
||||
If the method starts "test", it's a test.
|
||||
Note the very long name <span class="new_code">testLogCreatesNewFileOnFirstMessage()</span>.
|
||||
This is considered good style and makes the test output more readable.
|
||||
</p>
|
||||
<p>
|
||||
We would normally have more than one test method in a test case,
|
||||
but that's for later.
|
||||
</p>
|
||||
<p>
|
||||
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 debugging content right next to the assertion concerned.
|
||||
</p>
|
||||
<p>
|
||||
To see these results we have to actually run the tests.
|
||||
No other code is necessary - we can just open the page
|
||||
with our browser.
|
||||
</p>
|
||||
<p>
|
||||
On failure the display looks like this...
|
||||
<div class="demo">
|
||||
<h1>TestOfLogging</h1>
|
||||
<span class="fail">Fail</span>: testLogCreatesNewFileOnFirstMessage->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<strong>
|
||||
class Log {
|
||||
function Log($file_path) {
|
||||
}
|
||||
|
||||
function message() {
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
It's fun to write the code after the test.
|
||||
More than fun even -
|
||||
this system is called "Test Driven Development".
|
||||
</p>
|
||||
<p>
|
||||
For more information about <span class="new_code">UnitTestCase</span>, see
|
||||
the <a href="unit_test_documentation.html">unit test documentation</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="group"><h2>Building test suites</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 for the application.
|
||||
</p>
|
||||
<p>
|
||||
Our first step is to create a new file called <em>tests/all_tests.php</em>
|
||||
and insert the following code...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>require_once('simpletest/autorun.php');</strong>
|
||||
|
||||
class AllTests extends <strong>TestSuite</strong> {
|
||||
function AllTests() {
|
||||
$this->TestSuite(<strong>'All tests'</strong>);
|
||||
<strong>$this->addFile('log_test.php');</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
The "autorun" include allows our upcoming test suite
|
||||
to be run just by invoking this script.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">TestSuite</span> subclass must chain it's constructor.
|
||||
This limitation will be removed in future versions.
|
||||
</p>
|
||||
<p>
|
||||
The method <span class="new_code">TestSuite::addFile()</span>
|
||||
will include the test case file and read any new classes
|
||||
that are descended from <span class="new_code">SimpleTestCase</span>.
|
||||
<span class="new_code">UnitTestCase</span> is just one example of a class derived from
|
||||
<span class="new_code">SimpleTestCase</span>, and you can create your own.
|
||||
<span class="new_code">TestSuite::addFile()</span> can include other test suites.
|
||||
</p>
|
||||
<p>
|
||||
The class will not be instantiated yet.
|
||||
When the test suite runs it will construct each instance once
|
||||
it reaches that test, then destroy it straight after.
|
||||
This means that the constructor is run just before each run
|
||||
of that test case, and the destructor is run before the next test case starts.
|
||||
</p>
|
||||
<p>
|
||||
It is common to group test case code into superclasses which are not
|
||||
supposed to run, but become the base classes of other tests.
|
||||
For "autorun" to work properly the test case file should not blindly run
|
||||
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 mark your
|
||||
base class as <span class="new_code">abstract</span>.
|
||||
SimpleTest won't run abstract classes.
|
||||
If you are still using PHP4, then
|
||||
a <span class="new_code">SimpleTestOptions::ignore()</span> directive
|
||||
somewhere in the test case file will have the same effect.
|
||||
</p>
|
||||
<p>
|
||||
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">TestSuite::addFile()</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 or the command line.
|
||||
</p>
|
||||
<p>
|
||||
For more information about building test suites,
|
||||
see the <a href="group_test_documentation.html">test suite documentation</a>.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="mock"><h2>Using mock objects</h2></a></p>
|
||||
<p>
|
||||
Let's move further into the future and do something really complicated.
|
||||
</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>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
<strong>require_once('../classes/session_pool.php');</strong>
|
||||
|
||||
class <strong>TestOfSessionLogging</strong> extends UnitTestCase {
|
||||
|
||||
function setUp() {
|
||||
<strong>@unlink('/temp/test.log');</strong>
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
<strong>@unlink('/temp/test.log');</strong>
|
||||
}
|
||||
|
||||
function testLoggingInIsLogged() {
|
||||
<strong>$log = new Log('/temp/test.log');
|
||||
$session_pool = &new SessionPool($log);
|
||||
$session_pool->logIn('fred');</strong>
|
||||
$messages = file('/temp/test.log');
|
||||
$this->assertEqual($messages[0], "User fred logged in.<strong>\n</strong>");
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
We'll explain the <span class="new_code">setUp()</span> and <span class="new_code">tearDown()</span>
|
||||
methods later.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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?
|
||||
It means that our <span class="new_code">TestOfSessionLogging</span> test will
|
||||
fail, even thouh it's not testing Logging.
|
||||
</p>
|
||||
<p>
|
||||
It's fragile in smaller ways too.
|
||||
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 can reduce coupling if we 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?
|
||||
We can create such an object easily...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/log.php');
|
||||
require_once('../classes/session_pool.php');
|
||||
|
||||
<strong>Mock::generate('Log');</strong>
|
||||
|
||||
class TestOfSessionLogging extends UnitTestCase {
|
||||
|
||||
function testLoggingInIsLogged() {<strong>
|
||||
$log = &new MockLog();
|
||||
$log->expectOnce('message', array('User fred logged in.'));</strong>
|
||||
$session_pool = &new SessionPool(<strong>$log</strong>);
|
||||
$session_pool->logIn('fred');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">Mock::generate()</span> call code generated a new class
|
||||
called <span class="new_code">MockLog</span>.
|
||||
This looks like an identical clone, except that we can wire test code
|
||||
to it.
|
||||
That's what <span class="new_code">expectOnce()</span> does.
|
||||
It says that if <span class="new_code">message()</span> is ever called on me, it had
|
||||
better be with the parameter "User fred logged in.".
|
||||
</p>
|
||||
<p>
|
||||
The 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 by <span class="new_code">SessionPool::logIn()</span> code.
|
||||
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 you don't have to test every parameter of
|
||||
a call when you only want to test one.
|
||||
</p>
|
||||
<p>
|
||||
If the mock reaches the end of the test case without the
|
||||
method being called, the <span class="new_code">expectOnce()</span>
|
||||
expectation will trigger a test failure.
|
||||
In other words the mocks can detect the absence of
|
||||
behaviour as well as the presence.
|
||||
</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 more information about mocking and stubbing, see the
|
||||
<a href="mock_objects_documentation.html">mock objects documentation</a>.
|
||||
</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, as there is
|
||||
no JavaScript.
|
||||
Most other browser operations are simulated.
|
||||
</p>
|
||||
<p>
|
||||
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
|
||||
require_once('simpletest/autorun.php');
|
||||
<strong>require_once('simpletest/web_tester.php');</strong>
|
||||
|
||||
class TestOfAbout extends <strong>WebTestCase</strong> {
|
||||
function testOurAboutPageGivesFreeReignToOurEgo() {
|
||||
<strong>$this->get('http://test-server/index.php');
|
||||
$this->click('About');
|
||||
$this->assertTitle('About why we are so great');
|
||||
$this->assertText('We are really great');</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</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>
|
||||
You can navigate forms too...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('simpletest/web_tester.php');
|
||||
|
||||
class TestOfRankings extends WebTestCase {
|
||||
function testWeAreTopOfGoogle() {
|
||||
$this->get('http://google.com/');
|
||||
$this->setField('q', 'simpletest');
|
||||
$this->click("I'm Feeling Lucky");
|
||||
$this->assertTitle('SimpleTest - Unit Testing for PHP');
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
...although this could violate Google's(tm) terms and conditions.
|
||||
</p>
|
||||
<p>
|
||||
For more information about web testing, see the
|
||||
<a href="browser_documentation.html">scriptable
|
||||
browser documentation</a> and the
|
||||
<a href="web_tester_documentation.html">WebTestCase</a>.
|
||||
</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>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://sourceforge.net/project/showfiles.php?group_id=76550&release_id=153280">Download PHP Simple Test</a>
|
||||
from <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<span class="chosen">SimpleTest</span>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
757
simpletest/docs/en/mock_objects_documentation.html
Normal file
757
simpletest/docs/en/mock_objects_documentation.html
Normal file
@@ -0,0 +1,757 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP mock objects documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<span class="chosen">Mock objects</span>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Mock objects documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#what">What are mock objects?</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#creation">Creating mock objects</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#stub">Mocks as actors</a> or stubs.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#expectations">Mocks as critics</a> with expectations.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#approaches">Other approaches</a> including mock libraries.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="what"><h2>What are mock objects?</h2></a></p>
|
||||
<p>
|
||||
Mock objects have two roles during a test case: actor and critic.
|
||||
</p>
|
||||
<p>
|
||||
The actor behaviour is to simulate objects that are difficult to
|
||||
set up or time consuming to set up for a test.
|
||||
The classic example is a database connection.
|
||||
Setting up a test database at the start of each test would slow
|
||||
testing to a crawl and would require the installation of the
|
||||
database engine and test data on the test machine.
|
||||
If we can simulate the connection and return data of our
|
||||
choosing we not only win on the pragmatics of testing, but can
|
||||
also feed our code spurious data to see how it responds.
|
||||
We can simulate databases being down or other extremes
|
||||
without having to create a broken database for real.
|
||||
In other words, we get greater control of the test environment.
|
||||
</p>
|
||||
<p>
|
||||
If mock objects only behaved as actors they would simply be
|
||||
known as server stubs.
|
||||
This was originally a pattern named by Robert Binder (Testing
|
||||
object-oriented systems: models, patterns, and tools,
|
||||
Addison-Wesley) in 1999.
|
||||
</p>
|
||||
<p>
|
||||
A server stub is a simulation of an object or component.
|
||||
It should exactly replace a component in a system for test
|
||||
or prototyping purposes, but remain lightweight.
|
||||
This allows tests to run more quickly, or if the simulated
|
||||
class has not been written, to run at all.
|
||||
</p>
|
||||
<p>
|
||||
However, the mock objects not only play a part (by supplying chosen
|
||||
return values on demand) they are also sensitive to the
|
||||
messages sent to them (via expectations).
|
||||
By setting expected parameters for a method call they act
|
||||
as a guard that the calls upon them are made correctly.
|
||||
If expectations are not met they save us the effort of
|
||||
writing a failed test assertion by performing that duty on our
|
||||
behalf.
|
||||
</p>
|
||||
<p>
|
||||
In the case of an imaginary database connection they can
|
||||
test that the query, say SQL, was correctly formed by
|
||||
the object that is using the connection.
|
||||
Set them up with fairly tight expectations and you will
|
||||
hardly need manual assertions at all.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="creation"><h2>Creating mock objects</h2></a></p>
|
||||
<p>
|
||||
In the same way that we create server stubs, all we need is an
|
||||
existing class, say a database connection that looks like this...
|
||||
<pre>
|
||||
<strong>class DatabaseConnection {
|
||||
function DatabaseConnection() {
|
||||
}
|
||||
|
||||
function query() {
|
||||
}
|
||||
|
||||
function selectQuery() {
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
The class does not need to have been implemented yet.
|
||||
To create a mock version of the class we need to include the
|
||||
mock object library and run the generator...
|
||||
<pre>
|
||||
<strong>require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('database_connection.php');
|
||||
|
||||
Mock::generate('DatabaseConnection');</strong>
|
||||
</pre>
|
||||
This generates a clone class called
|
||||
<span class="new_code">MockDatabaseConnection</span>.
|
||||
We can now create instances of the new class within
|
||||
our test case...
|
||||
<pre>
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('database_connection.php');
|
||||
|
||||
Mock::generate('DatabaseConnection');
|
||||
<strong>
|
||||
class MyTestCase extends UnitTestCase {
|
||||
|
||||
function testSomething() {
|
||||
$connection = &new MockDatabaseConnection();
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Unlike the generated stubs the mock constructor needs a reference
|
||||
to the test case so that it can dispatch passes and failures while
|
||||
checking its expectations.
|
||||
This means that mock objects can only be used within test cases.
|
||||
Despite this their extra power means that stubs are hardly ever used
|
||||
if mocks are available.
|
||||
</p>
|
||||
<p>
|
||||
<a class="target" name="stub"><h2>Mocks as actors</h2></a>
|
||||
</p>
|
||||
<p>
|
||||
The mock version of a class has all the methods of the original,
|
||||
so that operations like
|
||||
<span class="new_code">$connection->query()</span> are still
|
||||
legal.
|
||||
The return value will be <span class="new_code">null</span>,
|
||||
but we can change that with...
|
||||
<pre>
|
||||
<strong>$connection->setReturnValue('query', 37)</strong>
|
||||
</pre>
|
||||
Now every time we call
|
||||
<span class="new_code">$connection->query()</span> we get
|
||||
the result of 37.
|
||||
We can set the return value to anything, say a hash of
|
||||
imaginary database results or a list of persistent objects.
|
||||
Parameters are irrelevant here, we always get the same
|
||||
values back each time once they have been set up this way.
|
||||
That may not sound like a convincing replica of a
|
||||
database connection, but for the half a dozen lines of
|
||||
a test method it is usually all you need.
|
||||
</p>
|
||||
<p>
|
||||
We can also add extra methods to the mock when generating it
|
||||
and choose our own class name...
|
||||
<pre>
|
||||
<strong>Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));</strong>
|
||||
</pre>
|
||||
Here the mock will behave as if the <span class="new_code">setOptions()</span>
|
||||
existed in the original class.
|
||||
This is handy if a class has used the PHP <span class="new_code">overload()</span>
|
||||
mechanism to add dynamic methods.
|
||||
You can create a special mock to simulate this situation.
|
||||
</p>
|
||||
<p>
|
||||
Things aren't always that simple though.
|
||||
One common problem is iterators, where constantly returning
|
||||
the same value could cause an endless loop in the object
|
||||
being tested.
|
||||
For these we need to set up sequences of values.
|
||||
Let's say we have a simple iterator that looks like this...
|
||||
<pre>
|
||||
class Iterator {
|
||||
function Iterator() {
|
||||
}
|
||||
|
||||
function next() {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is about the simplest iterator you could have.
|
||||
Assuming that this iterator only returns text until it
|
||||
reaches the end, when it returns false, we can simulate it
|
||||
with...
|
||||
<pre>
|
||||
Mock::generate('Iterator');
|
||||
|
||||
class IteratorTest extends UnitTestCase() {
|
||||
|
||||
function testASequence() {<strong>
|
||||
$iterator = &new MockIterator();
|
||||
$iterator->setReturnValue('next', false);
|
||||
$iterator->setReturnValueAt(0, 'next', 'First string');
|
||||
$iterator->setReturnValueAt(1, 'next', 'Second string');</strong>
|
||||
...
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
When <span class="new_code">next()</span> is called on the
|
||||
mock iterator it will first return "First string",
|
||||
on the second call "Second string" will be returned
|
||||
and on any other call <span class="new_code">false</span> will
|
||||
be returned.
|
||||
The sequenced return values take precedence over the constant
|
||||
return value.
|
||||
The constant one is a kind of default if you like.
|
||||
</p>
|
||||
<p>
|
||||
Another tricky situation is an overloaded
|
||||
<span class="new_code">get()</span> operation.
|
||||
An example of this is an information holder with name/value pairs.
|
||||
Say we have a configuration class like...
|
||||
<pre>
|
||||
class Configuration {
|
||||
function Configuration() {
|
||||
}
|
||||
|
||||
function getValue($key) {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This is a classic situation for using mock objects as
|
||||
actual configuration will vary from machine to machine,
|
||||
hardly helping the reliability of our tests if we use it
|
||||
directly.
|
||||
The problem though is that all the data comes through the
|
||||
<span class="new_code">getValue()</span> method and yet
|
||||
we want different results for different keys.
|
||||
Luckily the mocks have a filter system...
|
||||
<pre>
|
||||
<strong>$config = &new MockConfiguration();
|
||||
$config->setReturnValue('getValue', 'primary', array('db_host'));
|
||||
$config->setReturnValue('getValue', 'admin', array('db_user'));
|
||||
$config->setReturnValue('getValue', 'secret', array('db_password'));</strong>
|
||||
</pre>
|
||||
The extra parameter is a list of arguments to attempt
|
||||
to match.
|
||||
In this case we are trying to match only one argument which
|
||||
is the look up key.
|
||||
Now when the mock object has the
|
||||
<span class="new_code">getValue()</span> method invoked
|
||||
like this...
|
||||
<pre>
|
||||
$config->getValue('db_user')
|
||||
</pre>
|
||||
...it will return "admin".
|
||||
It finds this by attempting to match the calling arguments
|
||||
to its list of returns one after another until
|
||||
a complete match is found.
|
||||
</p>
|
||||
<p>
|
||||
You can set a default argument argument like so...
|
||||
<pre><strong>
|
||||
$config->setReturnValue('getValue', false, array('*'));</strong>
|
||||
</pre>
|
||||
This is not the same as setting the return value without
|
||||
any argument requirements like this...
|
||||
<pre><strong>
|
||||
$config->setReturnValue('getValue', false);</strong>
|
||||
</pre>
|
||||
In the first case it will accept any single argument,
|
||||
but exactly one is required.
|
||||
In the second case any number of arguments will do and
|
||||
it acts as a catchall after all other matches.
|
||||
Note that if we add further single parameter options after
|
||||
the wildcard in the first case, they will be ignored as the wildcard
|
||||
will match first.
|
||||
With complex parameter lists the ordering could be important
|
||||
or else desired matches could be masked by earlier wildcard
|
||||
ones.
|
||||
Declare the most specific matches first if you are not sure.
|
||||
</p>
|
||||
<p>
|
||||
There are times when you want a specific object to be
|
||||
dished out by the mock rather than a copy.
|
||||
The PHP4 copy semantics force us to use a different method
|
||||
for this.
|
||||
You might be simulating a container for example...
|
||||
<pre>
|
||||
class Thing {
|
||||
}
|
||||
|
||||
class Vector {
|
||||
function Vector() {
|
||||
}
|
||||
|
||||
function get($index) {
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
In this case you can set a reference into the mock's
|
||||
return list...
|
||||
<pre>
|
||||
$thing = &new Thing();<strong>
|
||||
$vector = &new MockVector();
|
||||
$vector->setReturnReference('get', $thing, array(12));</strong>
|
||||
</pre>
|
||||
With this arrangement you know that every time
|
||||
<span class="new_code">$vector->get(12)</span> is
|
||||
called it will return the same
|
||||
<span class="new_code">$thing</span> each time.
|
||||
This is compatible with PHP5 as well.
|
||||
</p>
|
||||
<p>
|
||||
These three factors, timing, parameters and whether to copy,
|
||||
can be combined orthogonally.
|
||||
For example...
|
||||
<pre>
|
||||
$complex = &new MockComplexThing();
|
||||
$stuff = &new Stuff();<strong>
|
||||
$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1));</strong>
|
||||
</pre>
|
||||
This will return the <span class="new_code">$stuff</span> only on the third
|
||||
call and only if two parameters were set the second of
|
||||
which must be the integer 1.
|
||||
That should cover most simple prototyping situations.
|
||||
</p>
|
||||
<p>
|
||||
A final tricky case is one object creating another, known
|
||||
as a factory pattern.
|
||||
Suppose that on a successful query to our imaginary
|
||||
database, a result set is returned as an iterator with
|
||||
each call to <span class="new_code">next()</span> giving
|
||||
one row until false.
|
||||
This sounds like a simulation nightmare, but in fact it can all
|
||||
be mocked using the mechanics above.
|
||||
</p>
|
||||
<p>
|
||||
Here's how...
|
||||
<pre>
|
||||
Mock::generate('DatabaseConnection');
|
||||
Mock::generate('ResultIterator');
|
||||
|
||||
class DatabaseTest extends UnitTestCase {
|
||||
|
||||
function testUserFinder() {<strong>
|
||||
$result = &new MockResultIterator();
|
||||
$result->setReturnValue('next', false);
|
||||
$result->setReturnValueAt(0, 'next', array(1, 'tom'));
|
||||
$result->setReturnValueAt(1, 'next', array(3, 'dick'));
|
||||
$result->setReturnValueAt(2, 'next', array(6, 'harry'));
|
||||
|
||||
$connection = &new MockDatabaseConnection();
|
||||
$connection->setReturnValue('query', false);
|
||||
$connection->setReturnReference(
|
||||
'query',
|
||||
$result,
|
||||
array('select id, name from users'));</strong>
|
||||
|
||||
$finder = &new UserFinder($connection);
|
||||
$this->assertIdentical(
|
||||
$finder->findNames(),
|
||||
array('tom', 'dick', 'harry'));
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Now only if our
|
||||
<span class="new_code">$connection</span> is called with the correct
|
||||
<span class="new_code">query()</span> will the
|
||||
<span class="new_code">$result</span> be returned that is
|
||||
itself exhausted after the third call to <span class="new_code">next()</span>.
|
||||
This should be enough
|
||||
information for our <span class="new_code">UserFinder</span> class,
|
||||
the class actually
|
||||
being tested here, to come up with goods.
|
||||
A very precise test and not a real database in sight.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="expectations"><h2>Mocks as critics</h2></a></p>
|
||||
<p>
|
||||
Although the server stubs approach insulates your tests from
|
||||
real world disruption, it is only half the benefit.
|
||||
You can have the class under test receiving the required
|
||||
messages, but is your new class sending correct ones?
|
||||
Testing this can get messy without a mock objects library.
|
||||
</p>
|
||||
<p>
|
||||
By way of example, suppose we have a
|
||||
<span class="new_code">SessionPool</span> class that we
|
||||
want to add logging to.
|
||||
Rather than grow the original class into something more
|
||||
complicated, we want to add this behaviour with a decorator (GOF).
|
||||
The <span class="new_code">SessionPool</span> code currently looks
|
||||
like this...
|
||||
<pre>
|
||||
<strong>class SessionPool {
|
||||
function SessionPool() {
|
||||
...
|
||||
}
|
||||
|
||||
function &findSession($cookie) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
class Session {
|
||||
...
|
||||
}</strong>
|
||||
</pre>
|
||||
While our logging code looks like this...
|
||||
<pre>
|
||||
<strong>
|
||||
class Log {
|
||||
function Log() {
|
||||
...
|
||||
}
|
||||
|
||||
function message() {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
class LoggingSessionPool {
|
||||
function LoggingSessionPool(&$session_pool, &$log) {
|
||||
...
|
||||
}
|
||||
|
||||
function &findSession($cookie) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}</strong>
|
||||
</pre>
|
||||
Out of all of this, the only class we want to test here
|
||||
is the <span class="new_code">LoggingSessionPool</span>.
|
||||
In particular we would like to check that the
|
||||
<span class="new_code">findSession()</span> method is
|
||||
called with the correct session ID in the cookie and that
|
||||
it sent the message "Starting session $cookie"
|
||||
to the logger.
|
||||
</p>
|
||||
<p>
|
||||
Despite the fact that we are testing only a few lines of
|
||||
production code, here is what we would have to do in a
|
||||
conventional test case:
|
||||
<ol>
|
||||
<li>Create a log object.</li>
|
||||
<li>Set a directory to place the log file.</li>
|
||||
<li>Set the directory permissions so we can write the log.</li>
|
||||
<li>Create a <span class="new_code">SessionPool</span> object.</li>
|
||||
<li>Hand start a session, which probably does lot's of things.</li>
|
||||
<li>Invoke <span class="new_code">findSession()</span>.</li>
|
||||
<li>Read the new Session ID (hope there is an accessor!).</li>
|
||||
<li>Raise a test assertion to confirm that the ID matches the cookie.</li>
|
||||
<li>Read the last line of the log file.</li>
|
||||
<li>Pattern match out the extra logging timestamps, etc.</li>
|
||||
<li>Assert that the session message is contained in the text.</li>
|
||||
</ol>
|
||||
It is hardly surprising that developers hate writing tests
|
||||
when they are this much drudgery.
|
||||
To make things worse, every time the logging format changes or
|
||||
the method of creating new sessions changes, we have to rewrite
|
||||
parts of this test even though this test does not officially
|
||||
test those parts of the system.
|
||||
We are creating headaches for the writers of these other classes.
|
||||
</p>
|
||||
<p>
|
||||
Instead, here is the complete test method using mock object magic...
|
||||
<pre>
|
||||
Mock::generate('Session');
|
||||
Mock::generate('SessionPool');
|
||||
Mock::generate('Log');
|
||||
|
||||
class LoggingSessionPoolTest extends UnitTestCase {
|
||||
...
|
||||
function testFindSessionLogging() {<strong>
|
||||
$session = &new MockSession();
|
||||
$pool = &new MockSessionPool();
|
||||
$pool->setReturnReference('findSession', $session);
|
||||
$pool->expectOnce('findSession', array('abc'));
|
||||
|
||||
$log = &new MockLog();
|
||||
$log->expectOnce('message', array('Starting session abc'));
|
||||
|
||||
$logging_pool = &new LoggingSessionPool($pool, $log);
|
||||
$this->assertReference($logging_pool->findSession('abc'), $session);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
We start by creating a dummy session.
|
||||
We don't have to be too fussy about this as the check
|
||||
for which session we want is done elsewhere.
|
||||
We only need to check that it was the same one that came
|
||||
from the session pool.
|
||||
</p>
|
||||
<p>
|
||||
<span class="new_code">findSession()</span> is a factory
|
||||
method the simulation of which is described <a href="#stub">above</a>.
|
||||
The point of departure comes with the first
|
||||
<span class="new_code">expectOnce()</span> call.
|
||||
This line states that whenever
|
||||
<span class="new_code">findSession()</span> is invoked on the
|
||||
mock, it will test the incoming arguments.
|
||||
If it receives the single argument of a string "abc"
|
||||
then a test pass is sent to the unit tester, otherwise a fail is
|
||||
generated.
|
||||
This was the part where we checked that the right session was asked for.
|
||||
The argument list follows the same format as the one for setting
|
||||
return values.
|
||||
You can have wildcards and sequences and the order of
|
||||
evaluation is the same.
|
||||
</p>
|
||||
<p>
|
||||
We use the same pattern to set up the mock logger.
|
||||
We tell it that it should have
|
||||
<span class="new_code">message()</span> invoked
|
||||
once only with the argument "Starting session abc".
|
||||
By testing the calling arguments, rather than the logger output,
|
||||
we insulate the test from any display changes in the logger.
|
||||
</p>
|
||||
<p>
|
||||
We start to run our tests when we create the new
|
||||
<span class="new_code">LoggingSessionPool</span> and feed
|
||||
it our preset mock objects.
|
||||
Everything is now under our control.
|
||||
</p>
|
||||
<p>
|
||||
This is still quite a bit of test code, but the code is very
|
||||
strict.
|
||||
If it still seems rather daunting there is a lot less of it
|
||||
than if we tried this without mocks and this particular test,
|
||||
interactions rather than output, is always more work to set
|
||||
up.
|
||||
More often you will be testing more complex situations without
|
||||
needing this level or precision.
|
||||
Also some of this can be refactored into a test case
|
||||
<span class="new_code">setUp()</span> method.
|
||||
</p>
|
||||
<p>
|
||||
Here is the full list of expectations you can set on a mock object
|
||||
in <a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a>...
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Expectation</th>
|
||||
<th>Needs <span class="new_code">tally()</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">expect($method, $args)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectAt($timing, $method, $args)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectMaximumCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectMinimumCallCount($method, $count)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectNever($method)</span></td>
|
||||
<td style="text-align: center">No</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectOnce($method, $args)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectAtLeastOnce($method, $args)</span></td>
|
||||
<td style="text-align: center">Yes</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Where the parameters are...
|
||||
<dl>
|
||||
<dt class="new_code">$method</dt>
|
||||
<dd>The method name, as a string, to apply the condition to.</dd>
|
||||
<dt class="new_code">$args</dt>
|
||||
<dd>
|
||||
The arguments as a list. Wildcards can be included in the same
|
||||
manner as for <span class="new_code">setReturn()</span>.
|
||||
This argument is optional for <span class="new_code">expectOnce()</span>
|
||||
and <span class="new_code">expectAtLeastOnce()</span>.
|
||||
</dd>
|
||||
<dt class="new_code">$timing</dt>
|
||||
<dd>
|
||||
The only point in time to test the condition.
|
||||
The first call starts at zero.
|
||||
</dd>
|
||||
<dt class="new_code">$count</dt>
|
||||
<dd>The number of calls expected.</dd>
|
||||
</dl>
|
||||
The method <span class="new_code">expectMaximumCallCount()</span>
|
||||
is slightly different in that it will only ever generate a failure.
|
||||
It is silent if the limit is never reached.
|
||||
</p>
|
||||
<p>
|
||||
Also if you have juste one call in your test, make sure you're using
|
||||
<span class="new_code">expectOnce</span>.<br>
|
||||
Using <span class="new_code">$mocked->expectAt(0, 'method', 'args);</span>
|
||||
on its own will not be catched :
|
||||
checking the arguments and the overall call count
|
||||
are currently independant.
|
||||
</p>
|
||||
<p>
|
||||
Like the assertions within test cases, all of the expectations
|
||||
can take a message override as an extra parameter.
|
||||
Also the original failure message can be embedded in the output
|
||||
as "%s".
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="approaches"><h2>Other approaches</h2></a></p>
|
||||
<p>
|
||||
There are three approaches to creating mocks including the one
|
||||
that SimpleTest employs.
|
||||
Coding them by hand using a base class, generating them to
|
||||
a file and dynamically generating them on the fly.
|
||||
</p>
|
||||
<p>
|
||||
Mock objects generated with <a href="simple_test.html">SimpleTest</a>
|
||||
are dynamic.
|
||||
They are created at run time in memory, using
|
||||
<span class="new_code">eval()</span>, rather than written
|
||||
out to a file.
|
||||
This makes the mocks easy to create, a one liner,
|
||||
especially compared with hand
|
||||
crafting them in a parallel class hierarchy.
|
||||
The problem is that the behaviour is usually set up in the tests
|
||||
themselves.
|
||||
If the original objects change the mock versions
|
||||
that the tests rely on can get out of sync.
|
||||
This can happen with the parallel hierarchy approach as well,
|
||||
but is far more quickly detected.
|
||||
</p>
|
||||
<p>
|
||||
The solution, of course, is to add some real integration
|
||||
tests.
|
||||
You don't need very many and the convenience gained
|
||||
from the mocks more than outweighs the small amount of
|
||||
extra testing.
|
||||
You cannot trust code that was only tested with mocks.
|
||||
</p>
|
||||
<p>
|
||||
If you are still determined to build static libraries of mocks
|
||||
because you want to simulate very specific behaviour, you can
|
||||
achieve the same effect using the SimpleTest class generator.
|
||||
In your library file, say <em>mocks/connection.php</em> for a
|
||||
database connection, create a mock and inherit to override
|
||||
special methods or add presets...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/mock_objects.php');
|
||||
require_once('../classes/connection.php');
|
||||
<strong>
|
||||
Mock::generate('Connection', 'BasicMockConnection');
|
||||
class MockConnection extends BasicMockConnection {
|
||||
function MockConnection() {
|
||||
$this->BasicMockConnection();
|
||||
$this->setReturn('query', false);
|
||||
}
|
||||
}</strong>
|
||||
?>
|
||||
</pre>
|
||||
The generate call tells the class generator to create
|
||||
a class called <span class="new_code">BasicMockConnection</span>
|
||||
rather than the usual <span class="new_code">MockConnection</span>.
|
||||
We then inherit from this to get our version of
|
||||
<span class="new_code">MockConnection</span>.
|
||||
By intercepting in this way we can add behaviour, here setting
|
||||
the default value of <span class="new_code">query()</span> to be false.
|
||||
By using the default name we make sure that the mock class
|
||||
generator will not recreate a different one when invoked elsewhere in the
|
||||
tests.
|
||||
It never creates a class if it already exists.
|
||||
As long as the above file is included first then all tests
|
||||
that generated <span class="new_code">MockConnection</span> should
|
||||
now be using our one instead.
|
||||
If we don't get the order right and the mock library
|
||||
creates one first then the class creation will simply fail.
|
||||
</p>
|
||||
<p>
|
||||
Use this trick if you find you have a lot of common mock behaviour
|
||||
or you are getting frequent integration problems at later
|
||||
stages of testing.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
The original
|
||||
<a href="http://www.mockobjects.com/">Mock objects</a> paper.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest home page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<span class="chosen">Mock objects</span>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
486
simpletest/docs/en/overview.html
Normal file
486
simpletest/docs/en/overview.html
Normal file
@@ -0,0 +1,486 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Overview and feature list for the SimpleTest PHP unit tester and web tester
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<span class="chosen">Overview</span>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Overview of SimpleTest</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#summary">Quick summary</a>
|
||||
of the SimpleTest tool for PHP.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#features">List of features</a>,
|
||||
both current ones and those planned.
|
||||
</li>
|
||||
<li>
|
||||
There are plenty of <a href="#resources">unit testing resources</a>
|
||||
on the web.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="summary"><h2>What is SimpleTest?</h2></a></p>
|
||||
<p>
|
||||
The heart of SimpleTest is a testing framework built around
|
||||
test case classes.
|
||||
These are written as extensions of base test case classes,
|
||||
each extended with methods that actually contain test code.
|
||||
Top level test scripts then invoke the <span class="new_code">run()</span>
|
||||
methods on every one of these test cases in order.
|
||||
Each test method is written to invoke various assertions that
|
||||
the developer expects to be true such as
|
||||
<span class="new_code">assertEqual()</span>.
|
||||
If the expectation is correct, then a successful result is dispatched to the
|
||||
observing test reporter, but any failure triggers an alert
|
||||
and a description of the mismatch.
|
||||
</p>
|
||||
<p>
|
||||
A <a href="unit_test_documentation.html">test case</a> looks like this...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
|
||||
class <strong>MyTestCase</strong> extends UnitTestCase {
|
||||
<strong>
|
||||
function testCreatedLogFile() {
|
||||
$log = &new Log('my.log');
|
||||
$log->message('Hello');
|
||||
$this->assertTrue(file_exists('my.log'));
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
These tools are designed for the developer.
|
||||
Tests are written in the PHP language itself more or less
|
||||
as the application itself is built.
|
||||
The advantage of using PHP itself as the testing language is that
|
||||
there are no new languages to learn, testing can start straight away,
|
||||
and the developer can test any part of the code.
|
||||
Basically, all parts that can be accessed by the application code can also be
|
||||
accessed by the test code, if they are in the same programming language.
|
||||
</p>
|
||||
<p>
|
||||
The simplest type of test case is the
|
||||
<a href="unit_tester_documentation.html">UnitTestCase</a>.
|
||||
This class of test case includes standard tests for equality,
|
||||
references and pattern matching.
|
||||
All these test the typical expectations of what you would
|
||||
expect the result of a function or method to be.
|
||||
This is by far the most common type of test in the daily
|
||||
routine of development, making up about 95% of test cases.
|
||||
</p>
|
||||
<p>
|
||||
The top level task of a web application though is not to
|
||||
produce correct output from its methods and objects, but
|
||||
to generate web pages.
|
||||
The <a href="web_tester_documentation.html">WebTestCase</a> class tests web
|
||||
pages.
|
||||
It simulates a web browser requesting a page, complete with
|
||||
cookies, proxies, secure connections, authentication, forms, frames and most
|
||||
navigation elements.
|
||||
With this type of test case, the developer can assert that
|
||||
information is present in the page and that forms and
|
||||
sessions are handled correctly.
|
||||
</p>
|
||||
<p>
|
||||
A <a href="web_tester_documentation.html">WebTestCase</a> looks like this...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('simpletest/web_tester.php');
|
||||
|
||||
class <strong>MySiteTest</strong> extends WebTestCase {
|
||||
<strong>
|
||||
function testHomePage() {
|
||||
$this->get('http://www.my-site.com/index.php');
|
||||
$this->assertTitle('My Home Page');
|
||||
$this->clickLink('Contact');
|
||||
$this->assertTitle('Contact me');
|
||||
$this->assertPattern('/Email me at/');
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="features"><h2>Feature list</h2></a></p>
|
||||
<p>
|
||||
The following is a very rough outline of past and future features
|
||||
and their expected point of release.
|
||||
I am afraid it is liable to change without warning, as meeting the
|
||||
milestones rather depends on time available.
|
||||
Green stuff has been coded, but not necessarily released yet.
|
||||
If you have a pressing need for a green but unreleased feature
|
||||
then you should check-out the code from Sourceforge SVN directly.
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Description</th>
|
||||
<th>Release</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Unit test case</td>
|
||||
<td>Core test case class and assertions</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Html display</td>
|
||||
<td>Simplest possible display</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autoloading of test cases</td>
|
||||
<td>
|
||||
Reading a file with test cases and loading them into a
|
||||
group test automatically
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mock objects</td>
|
||||
<td>
|
||||
Objects capable of simulating other objects removing
|
||||
test dependencies
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Web test case</td>
|
||||
<td>Allows link following and title tag matching</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Partial mocks</td>
|
||||
<td>
|
||||
Mocking parts of a class for testing less than a class
|
||||
or for complex simulations
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Web cookie handling</td>
|
||||
<td>Correct handling of cookies when fetching pages</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Following redirects</td>
|
||||
<td>Page fetching automatically follows 300 redirects</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Form parsing</td>
|
||||
<td>Ability to submit simple forms and read default form values</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Command line interface</td>
|
||||
<td>Test display without the need of a web browser</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Exposure of expectation classes</td>
|
||||
<td>Can create precise tests with mocks as well as test cases</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>XML output and parsing</td>
|
||||
<td>
|
||||
Allows multi host testing and the integration of acceptance
|
||||
testing extensions
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Browser component</td>
|
||||
<td>
|
||||
Exposure of lower level web browser interface for more
|
||||
detailed test cases
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTTP authentication</td>
|
||||
<td>
|
||||
Fetching protected web pages with basic authentication
|
||||
only
|
||||
</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SSL support</td>
|
||||
<td>Can connect to https: pages</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Proxy support</td>
|
||||
<td>Can connect via. common proxies</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Frames support</td>
|
||||
<td>Handling of frames in web test cases</td>
|
||||
<td style="color: green;">1.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File upload testing</td>
|
||||
<td>Can simulate the input type file tag</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mocking interfaces</td>
|
||||
<td>
|
||||
Can generate mock objects to interfaces as well as classes
|
||||
and class interfaces are carried for type hints
|
||||
</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Testing exceptions</td>
|
||||
<td>Similar to testing PHP errors</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTML label support</td>
|
||||
<td>Can access all controls using the visual label</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Base tag support</td>
|
||||
<td>Respects page base tag when clicking</td>
|
||||
<td style="color: green;">1.0.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>PHP 5 E_STRICT compliant</td>
|
||||
<td>PHP 5 only version that works with the E_STRICT error level</td>
|
||||
<td style="color: red;">1.1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>BDD style fixtures</td>
|
||||
<td>Can import fixtures using a mixin like given() method</td>
|
||||
<td style="color: red;">1.5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reporting machinery enhancements</td>
|
||||
<td>Improved message passing for better cooperation with IDEs</td>
|
||||
<td style="color: red;">1.5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fluent mock interface</td>
|
||||
<td>More flexible and concise mock objects</td>
|
||||
<td style="color: red;">1.6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Localisation</td>
|
||||
<td>Messages abstracted and code generated</td>
|
||||
<td style="color: red;">1.6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CSS selectors</td>
|
||||
<td>HTML content can be examined using CSS selectors</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTML table assertions</td>
|
||||
<td>Can match HTML or table elements to expectations</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unified acceptance testing model</td>
|
||||
<td>Content searchable through selectors combined with expectations</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DatabaseTestCase</td>
|
||||
<td>SQL selectors and DB drivers</td>
|
||||
<td style="color: red;">1.7</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IFrame support</td>
|
||||
<td>Reads IFrame content that can be refreshed</td>
|
||||
<td style="color: red;">1.8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alternate HTML parsers</td>
|
||||
<td>Can detect compiled parsers for performance improvements</td>
|
||||
<td style="color: red;">1.8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Integrated Selenium support</td>
|
||||
<td>Easy to use built in Selenium driver and tutorial</td>
|
||||
<td style="color: red;">1.9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code coverage</td>
|
||||
<td>Reports using the bundled tool when using XDebug</td>
|
||||
<td style="color: red;">1.9</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Deprecation of old methods</td>
|
||||
<td>Simpler interface for SimpleTest2</td>
|
||||
<td style="color: red;">2.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Javascript suport</td>
|
||||
<td>Use of PECL module to add Javascript to the native browser</td>
|
||||
<td style="color: red;">3.0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
PHP5 migraton will start straight after the version 1.0.1 series,
|
||||
whereupon only PHP 5.1+ will be supported.
|
||||
SimpleTest is currently compatible with PHP 5, but will not
|
||||
make use of all of the new features until version 1.1.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="resources"><h2>Web resources for testing</h2></a></p>
|
||||
<p>
|
||||
Process is at least as important as tools.
|
||||
The type of process that makes the heaviest use of a developer's
|
||||
testing tool is of course
|
||||
<a href="http://www.extremeprogramming.org/">Extreme Programming</a>.
|
||||
This is one of the
|
||||
<a href="http://www.agilealliance.com/articles/index">Agile Methodologies</a>
|
||||
which combine various practices to "flatten the cost curve" of software development.
|
||||
More extreme still is <a href="http://www.testdriven.com/modules/news/">Test Driven Development</a>,
|
||||
where you very strictly adhere to the rule of no coding until you have a test.
|
||||
If you're more of a planner, or believe that experience trumps evolution,
|
||||
you may prefer the
|
||||
<a href="http://www.therationaledge.com/content/dec_01/f_spiritOfTheRUP_pk.html">RUP</a> approach.
|
||||
I haven't tried it, but even I can see that you will need test tools (see figure 9).
|
||||
</p>
|
||||
<p>
|
||||
Most unit testers clone <a href="http://www.junit.org/">JUnit</a> to some degree,
|
||||
as far as the interface at least. There is a wealth of information on the
|
||||
JUnit site including the
|
||||
<a href="http://junit.sourceforge.net/doc/faq/faq.htm">FAQ</a>
|
||||
which contains plenty of general advice on testing.
|
||||
Once you get bitten by the bug you will certainly appreciate the phrase
|
||||
<a href="http://junit.sourceforge.net/doc/testinfected/testing.htm">test infected</a>
|
||||
coined by Eric Gamma.
|
||||
If you are still reviewing which unit tester to use you can find pretty complete
|
||||
lists from
|
||||
<a href="http://en.wikipedia.org/wiki/List_of_unit_testing_frameworks">Wikipedia</a>,
|
||||
<a href="http://www.testingfaqs.org/t-unit.html">Software testing FAQ</a>,
|
||||
and <a href="http://www.opensourcetesting.org/functional.php">Open source testing</a>.
|
||||
</p>
|
||||
<p>
|
||||
There is still very little material on using mock objects, which is a shame
|
||||
as unit testing without them is a lot more work.
|
||||
The <a href="http://www.sidewize.com/company/mockobjects.pdf">original mock objects paper</a>
|
||||
is very Java focused, but still worth a read.
|
||||
The most authoritive sources are probably
|
||||
<a href="http://mockobjects.com">the original mock objects site</a> and
|
||||
<a href="http://jmock.org/">JMock</a>.
|
||||
Java centric, but tucked away in PDFs they contain some deep knowledge on using mocks from the
|
||||
extended experience of the concept inventors.
|
||||
As a new technology there are plenty of discussions and debate on how to use mocks,
|
||||
often on Wikis such as
|
||||
<a href="http://xpdeveloper.com/cgi-bin/oldwiki.cgi?MockObjects">Extreme Tuesday</a>
|
||||
or <a href="http://www.mockobjects.com/MocksObjectsPaper.html">www.mockobjects.com</a>
|
||||
or <a href="http://c2.com/cgi/wiki?MockObject">the original C2 Wiki</a>.
|
||||
Injecting mocks into a class is the main area of debate for which this
|
||||
<a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">paper on IBM</a>
|
||||
makes a good starting point.
|
||||
</p>
|
||||
<p>
|
||||
There are plenty of web testing tools, but the scriptable ones
|
||||
are mostly are written in Java and
|
||||
tutorials and advice are rather thin on the ground.
|
||||
The only hope is to look at the documentation for
|
||||
<a href="http://httpunit.sourceforge.net/">HTTPUnit</a>,
|
||||
<a href="http://htmlunit.sourceforge.net/">HTMLUnit</a>
|
||||
or <a href="http://jwebunit.sourceforge.net/">JWebUnit</a> and hope for clues.
|
||||
There are some XML driven test frameworks, but again most
|
||||
require Java to run.
|
||||
</p>
|
||||
<p>
|
||||
Most significant is a new generation of tools that run directly in the web browser
|
||||
are now available.
|
||||
These include
|
||||
<a href="http://www.openqa.org/selenium/">Selenium</a> and
|
||||
<a href="http://wtr.rubyforge.org/">Watir</a>.
|
||||
They are non-trivial to set up and slow to run, but can essentially test anything.
|
||||
As SimpleTest does not support JavaScript you would probably
|
||||
have to look at these tools anyway if you have highly dynamic
|
||||
pages.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="unit_test_documentation.html">Documentation for SimpleTest</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.lastcraft.com/first_test_tutorial.php">How to write PHP test cases</a>
|
||||
is a fairly advanced tutorial.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">SimpleTest API</a> from phpdoc.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<span class="chosen">Overview</span>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
445
simpletest/docs/en/partial_mocks_documentation.html
Normal file
445
simpletest/docs/en/partial_mocks_documentation.html
Normal file
@@ -0,0 +1,445 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP partial mocks documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<span class="chosen">Partial mocks</span>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Partial mock objects documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#inject">The mock injection problem</a>.
|
||||
</li>
|
||||
<li>
|
||||
Moving creation to a <a href="#creation">protected factory</a> method.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#partial">Partial mocks</a> generate subclasses.
|
||||
</li>
|
||||
<li>
|
||||
Partial mocks <a href="#less">test less than a class</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
A partial mock is simply a pattern to alleviate a specific problem
|
||||
in testing with mock objects,
|
||||
that of getting mock objects into tight corners.
|
||||
It's quite a limited tool and possibly not even a good idea.
|
||||
It is included with SimpleTest because I have found it useful
|
||||
on more than one occasion and has saved a lot of work at that point.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="inject"><h2>The mock injection problem</h2></a></p>
|
||||
<p>
|
||||
When one object uses another it is very simple to just pass a mock
|
||||
version in already set up with its expectations.
|
||||
Things are rather tricker if one object creates another and the
|
||||
creator is the one you want to test.
|
||||
This means that the created object should be mocked, but we can
|
||||
hardly tell our class under test to create a mock instead.
|
||||
The tested class doesn't even know it is running inside a test
|
||||
after all.
|
||||
</p>
|
||||
<p>
|
||||
For example, suppose we are building a telnet client and it
|
||||
needs to create a network socket to pass its messages.
|
||||
The connection method might look something like...
|
||||
<pre>
|
||||
<strong><?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
$socket->read( ... );
|
||||
...
|
||||
}
|
||||
}
|
||||
?></strong>
|
||||
</pre>
|
||||
We would really like to have a mock object version of the socket
|
||||
here, what can we do?
|
||||
</p>
|
||||
<p>
|
||||
The first solution is to pass the socket in as a parameter,
|
||||
forcing the creation up a level.
|
||||
Having the client handle this is actually a very good approach
|
||||
if you can manage it and should lead to factoring the creation from
|
||||
the doing.
|
||||
In fact, this is one way in which testing with mock objects actually
|
||||
forces you to code more tightly focused solutions.
|
||||
They improve your programming.
|
||||
</p>
|
||||
<p>
|
||||
Here this would be...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
<strong>function &connect(&$socket, $username, $password) {
|
||||
$socket->read( ... );
|
||||
...
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This means that the test code is typical for a test involving
|
||||
mock objects.
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect($socket, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
It is pretty obvious though that one level is all you can go.
|
||||
You would hardly want your top level application creating
|
||||
every low level file, socket and database connection ever
|
||||
needed.
|
||||
It wouldn't know the constructor parameters anyway.
|
||||
</p>
|
||||
<p>
|
||||
The next simplest compromise is to have the created object passed
|
||||
in as an optional parameter...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...<strong>
|
||||
function &connect($ip, $port, $username, $password, $socket = false) {
|
||||
if (!$socket) {
|
||||
$socket = &new Socket($ip, $port);
|
||||
}
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
For a quick solution this is usually good enough.
|
||||
The test now looks almost the same as if the parameter
|
||||
was formally passed...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The problem with this approach is its untidiness.
|
||||
There is test code in the main class and parameters passed
|
||||
in the test case that are never used.
|
||||
This is a quick and dirty approach, but nevertheless effective
|
||||
in most situations.
|
||||
</p>
|
||||
<p>
|
||||
The next method is to pass in a factory object to do the creation...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {<strong>
|
||||
function Telnet(&$network) {
|
||||
$this->_network = &$network;
|
||||
}</strong>
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_network->createSocket($ip, $port);
|
||||
$socket->read( ... );</strong>
|
||||
...
|
||||
return $socket;
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This is probably the most highly factored answer as creation
|
||||
is now moved into a small specialist class.
|
||||
The networking factory can now be tested separately, but mocked
|
||||
easily when we are testing the telnet class...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$network = &new MockNetwork($this);
|
||||
$network->setReturnReference('createSocket', $socket);
|
||||
$telnet = &new Telnet($network);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The downside is that we are adding a lot more classes to the
|
||||
library.
|
||||
Also we are passing a lot of factories around which will
|
||||
make the code a little less intuitive.
|
||||
The most flexible solution, but the most complex.
|
||||
</p>
|
||||
<p>
|
||||
Is there a middle ground?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="creation"><h2>Protected factory method</h2></a></p>
|
||||
<p>
|
||||
There is a way we can circumvent the problem without creating
|
||||
any new application classes, but it involves creating a subclass
|
||||
when we do the actual testing.
|
||||
Firstly we move the socket creation into its own method...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('socket.php');
|
||||
|
||||
class Telnet {
|
||||
...
|
||||
function &connect($ip, $port, $username, $password) {<strong>
|
||||
$socket = &$this->_createSocket($ip, $port);</strong>
|
||||
$socket->read( ... );
|
||||
...
|
||||
}<strong>
|
||||
|
||||
function &_createSocket($ip, $port) {
|
||||
return new Socket($ip, $port);
|
||||
}</strong>
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This is the only change we make to the application code.
|
||||
</p>
|
||||
<p>
|
||||
For the test case we have to create a subclass so that
|
||||
we can intercept the socket creation...
|
||||
<pre>
|
||||
<strong>class TelnetTestVersion extends Telnet {
|
||||
var $_mock;
|
||||
|
||||
function TelnetTestVersion(&$mock) {
|
||||
$this->_mock = &$mock;
|
||||
$this->Telnet();
|
||||
}
|
||||
|
||||
function &_createSocket() {
|
||||
return $this->_mock;
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
Here I have passed the mock in the constructor, but a
|
||||
setter would have done just as well.
|
||||
Note that the mock was set into the object variable
|
||||
before the constructor was chained.
|
||||
This is necessary in case the constructor calls
|
||||
<span class="new_code">connect()</span>.
|
||||
Otherwise it could get a null value from
|
||||
<span class="new_code">_createSocket()</span>.
|
||||
</p>
|
||||
<p>
|
||||
After the completion of all of this extra work the
|
||||
actual test case is fairly easy.
|
||||
We just test our new class instead...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($socket);
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The new class is very simple of course.
|
||||
It just sets up a return value, rather like a mock.
|
||||
It would be nice if it also checked the incoming parameters
|
||||
as well.
|
||||
Just like a mock.
|
||||
It seems we are likely to do this often, can
|
||||
we automate the subclass creation?
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="partial"><h2>A partial mock</h2></a></p>
|
||||
<p>
|
||||
Of course the answer is "yes" or I would have stopped writing
|
||||
this by now!
|
||||
The previous test case was a lot of work, but we can
|
||||
generate the subclass using a similar approach to the mock objects.
|
||||
</p>
|
||||
<p>
|
||||
Here is the partial mock version of the test...
|
||||
<pre>
|
||||
<strong>Mock::generatePartial(
|
||||
'Telnet',
|
||||
'TelnetTestVersion',
|
||||
array('_createSocket'));</strong>
|
||||
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {<strong>
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The partial mock is a subclass of the original with
|
||||
selected methods "knocked out" with test
|
||||
versions.
|
||||
The <span class="new_code">generatePartial()</span> call
|
||||
takes three parameters: the class to be subclassed,
|
||||
the new test class name and a list of methods to mock.
|
||||
</p>
|
||||
<p>
|
||||
Instantiating the resulting objects is slightly tricky.
|
||||
The only constructor parameter of a partial mock is
|
||||
the unit tester reference.
|
||||
As with the normal mock objects this is needed for sending
|
||||
test results in response to checked expectations.
|
||||
</p>
|
||||
<p>
|
||||
The original constructor is not run yet.
|
||||
This is necessary in case the constructor is going to
|
||||
make use of the as yet unset mocked methods.
|
||||
We set any return values at this point and then run the
|
||||
constructor with its normal parameters.
|
||||
This three step construction of "new", followed
|
||||
by setting up the methods, followed by running the constructor
|
||||
proper is what distinguishes the partial mock code.
|
||||
</p>
|
||||
<p>
|
||||
Apart from construction, all of the mocked methods have
|
||||
the same features as mock objects and all of the unmocked
|
||||
methods behave as before.
|
||||
We can set expectations very easily...
|
||||
<pre>
|
||||
class TelnetTest extends UnitTestCase {
|
||||
...
|
||||
function testConnection() {
|
||||
$socket = &new MockSocket($this);
|
||||
...
|
||||
$telnet = &new TelnetTestVersion($this);
|
||||
$telnet->setReturnReference('_createSocket', $socket);<strong>
|
||||
$telnet->expectOnce('_createSocket', array('127.0.0.1', 21));</strong>
|
||||
$telnet->Telnet();
|
||||
$telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
|
||||
...<strong>
|
||||
$telnet->tally();</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="less"><h2>Testing less than a class</h2></a></p>
|
||||
<p>
|
||||
The mocked out methods don't have to be factory methods,
|
||||
they could be any sort of method.
|
||||
In this way partial mocks allow us to take control of any part of
|
||||
a class except the constructor.
|
||||
We could even go as far as to mock every method
|
||||
except one we actually want to test.
|
||||
</p>
|
||||
<p>
|
||||
This last situation is all rather hypothetical, as I haven't
|
||||
tried it.
|
||||
I am open to the possibility, but a little worried that
|
||||
forcing object granularity may be better for the code quality.
|
||||
I personally use partial mocks as a way of overriding creation
|
||||
or for occasional testing of the TemplateMethod pattern.
|
||||
</p>
|
||||
<p>
|
||||
It's all going to come down to the coding standards of your
|
||||
project to decide which mechanism you use.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">Full API for SimpleTest</a>
|
||||
from the PHPDoc.
|
||||
</li>
|
||||
<li>
|
||||
The protected factory is described in
|
||||
<a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">this paper from IBM</a>.
|
||||
This is the only formal comment I have seen on this problem.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<span class="chosen">Partial mocks</span>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
519
simpletest/docs/en/reporter_documentation.html
Normal file
519
simpletest/docs/en/reporter_documentation.html
Normal file
@@ -0,0 +1,519 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP test runner and display documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<span class="chosen">Reporting</span>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Test reporter documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Displaying <a href="#html">results in HTML</a>
|
||||
</li>
|
||||
<li>
|
||||
Displaying and <a href="#other">reporting results</a>
|
||||
in other formats
|
||||
</li>
|
||||
<li>
|
||||
Using <a href="#cli">SimpleTest from the command line</a>
|
||||
</li>
|
||||
<li>
|
||||
Using <a href="#xml">Using XML</a> for remote testing
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
|
||||
<p>
|
||||
SimpleTest pretty much follows the MVC pattern
|
||||
(Model-View-Controller).
|
||||
The reporter classes are the view and the model is your
|
||||
test cases and their hiearchy.
|
||||
The controller is mostly hidden from the user of
|
||||
SimpleTest unless you want to change how the test cases
|
||||
are actually run, in which case it is possible to
|
||||
override the runner objects from within the test case.
|
||||
As usual with MVC, the controller is mostly undefined
|
||||
and there are other places to control the test run.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="html"><h2>Reporting results in HTML</h2></a></p>
|
||||
<p>
|
||||
The default test display is minimal in the extreme.
|
||||
It reports success and failure with the conventional red and
|
||||
green bars and shows a breadcrumb trail of test groups
|
||||
for every failed assertion.
|
||||
Here's a fail...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<span class="fail">Fail</span>: createnewfile->True assertion failed.<br>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete.
|
||||
<strong>0</strong> passes, <strong>1</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
And here all tests passed...
|
||||
<div class="demo">
|
||||
<h1>File test</h1>
|
||||
<div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete.
|
||||
<strong>1</strong> passes, <strong>0</strong> fails and <strong>0</strong> exceptions.</div>
|
||||
</div>
|
||||
The good news is that there are several points in the display
|
||||
hiearchy for subclassing.
|
||||
</p>
|
||||
<p>
|
||||
For web page based displays there is the
|
||||
<span class="new_code">HtmlReporter</span> class with the following
|
||||
signature...
|
||||
<pre>
|
||||
class HtmlReporter extends SimpleReporter {
|
||||
public HtmlReporter($encoding) { ... }
|
||||
public makeDry(boolean $is_dry) { ... }
|
||||
public void paintHeader(string $test_name) { ... }
|
||||
public void sendNoCacheHeaders() { ... }
|
||||
public void paintFooter(string $test_name) { ... }
|
||||
public void paintGroupStart(string $test_name, integer $size) { ... }
|
||||
public void paintGroupEnd(string $test_name) { ... }
|
||||
public void paintCaseStart(string $test_name) { ... }
|
||||
public void paintCaseEnd(string $test_name) { ... }
|
||||
public void paintMethodStart(string $test_name) { ... }
|
||||
public void paintMethodEnd(string $test_name) { ... }
|
||||
public void paintFail(string $message) { ... }
|
||||
public void paintPass(string $message) { ... }
|
||||
public void paintError(string $message) { ... }
|
||||
public void paintException(string $message) { ... }
|
||||
public void paintMessage(string $message) { ... }
|
||||
public void paintFormattedMessage(string $message) { ... }
|
||||
protected string _getCss() { ... }
|
||||
public array getTestList() { ... }
|
||||
public integer getPassCount() { ... }
|
||||
public integer getFailCount() { ... }
|
||||
public integer getExceptionCount() { ... }
|
||||
public integer getTestCaseCount() { ... }
|
||||
public integer getTestCaseProgress() { ... }
|
||||
}
|
||||
</pre>
|
||||
Here is what some of these methods mean. First the display methods
|
||||
that you will probably want to override...
|
||||
<ul class="api">
|
||||
<li>
|
||||
<span class="new_code">HtmlReporter(string $encoding)</span><br>
|
||||
is the constructor.
|
||||
Note that the unit test sets up the link to the display
|
||||
rather than the other way around.
|
||||
The display is a mostly passive receiver of test events.
|
||||
This allows easy adaption of the display for other test
|
||||
systems beside unit tests, such as monitoring servers.
|
||||
The encoding is the character encoding you wish to
|
||||
display the test output in.
|
||||
In order to correctly render debug output when
|
||||
using the web tester, this should match the encoding
|
||||
of the site you are trying to test.
|
||||
The available character set strings are described in
|
||||
the PHP <a href="http://www.php.net/manual/en/function.htmlentities.php">html_entities()</a>
|
||||
function.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintHeader(string $test_name)</span><br>
|
||||
is called once at the very start of the test when the first
|
||||
start event arrives.
|
||||
The first start event is usually delivered by the top level group
|
||||
test and so this is where <span class="new_code">$test_name</span>
|
||||
comes from.
|
||||
It paints the page titles, CSS, body tag, etc.
|
||||
It returns nothing (<span class="new_code">void</span>).
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintFooter(string $test_name)</span><br>
|
||||
Called at the very end of the test to close any tags opened
|
||||
by the page header.
|
||||
By default it also displays the red/green bar and the final
|
||||
count of results.
|
||||
Actually the end of the test happens when a test end event
|
||||
comes in with the same name as the one that started it all
|
||||
at the same level.
|
||||
The tests nest you see.
|
||||
Closing the last test finishes the display.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintMethodStart(string $test_name)</span><br>
|
||||
is called at the start of each test method.
|
||||
The name normally comes from method name.
|
||||
The other test start events behave the same way except
|
||||
that the group test one tells the reporter how large
|
||||
it is in number of held test cases.
|
||||
This is so that the reporter can display a progress bar
|
||||
as the runner churns through the test cases.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintMethodEnd(string $test_name)</span><br>
|
||||
backs out of the test started with the same name.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintFail(string $message)</span><br>
|
||||
paints a failure.
|
||||
By default it just displays the word fail, a breadcrumbs trail
|
||||
showing the current test nesting and the message issued by
|
||||
the assertion.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">void paintPass(string $message)</span><br>
|
||||
by default does nothing.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">string _getCss()</span><br>
|
||||
Returns the CSS styles as a string for the page header
|
||||
method.
|
||||
Additional styles have to be appended here if you are
|
||||
not overriding the page header.
|
||||
You will want to use this method in an overriden page header
|
||||
if you want to include the original CSS.
|
||||
</li>
|
||||
</ul>
|
||||
There are also some accessors to get information on the current
|
||||
state of the test suite.
|
||||
Use these to enrich the display...
|
||||
<ul class="api">
|
||||
<li>
|
||||
<span class="new_code">array getTestList()</span><br>
|
||||
is the first convenience method for subclasses.
|
||||
Lists the current nesting of the tests as a list
|
||||
of test names.
|
||||
The first, most deeply nested test, is first in the
|
||||
list and the current test method will be last.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getPassCount()</span><br>
|
||||
returns the number of passes chalked up so far.
|
||||
Needed for the display at the end.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getFailCount()</span><br>
|
||||
is likewise the number of fails so far.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getExceptionCount()</span><br>
|
||||
is likewise the number of errors so far.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getTestCaseCount()</span><br>
|
||||
is the total number of test cases in the test run.
|
||||
This includes the grouping tests themselves.
|
||||
</li>
|
||||
<li>
|
||||
<span class="new_code">integer getTestCaseProgress()</span><br>
|
||||
is the number of test cases completed so far.
|
||||
</li>
|
||||
</ul>
|
||||
One simple modification is to get the HtmlReporter to display
|
||||
the passes as well as the failures and errors...
|
||||
<pre>
|
||||
<strong>class ShowPasses extends HtmlReporter {
|
||||
|
||||
function paintPass($message) {
|
||||
parent::paintPass($message);
|
||||
print "&<span class=\"pass\">Pass</span>: ";
|
||||
$breadcrumb = $this->getTestList();
|
||||
array_shift($breadcrumb);
|
||||
print implode("-&gt;", $breadcrumb);
|
||||
print "-&gt;$message<br />\n";
|
||||
}
|
||||
|
||||
function _getCss() {
|
||||
return parent::_getCss() . ' .pass { color: green; }';
|
||||
}
|
||||
}</strong>
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
One method that was glossed over was the <span class="new_code">makeDry()</span>
|
||||
method.
|
||||
If you run this method, with no parameters, on the reporter
|
||||
before the test suite is run no actual test methods
|
||||
will be called.
|
||||
You will still get the events of entering and leaving the
|
||||
test methods and test cases, but no passes or failures etc,
|
||||
because the test code will not actually be executed.
|
||||
</p>
|
||||
<p>
|
||||
The reason for this is to allow for more sophistcated
|
||||
GUI displays that allow the selection of individual test
|
||||
cases.
|
||||
In order to build a list of possible tests they need a
|
||||
report on the test structure for drawing, say a tree view
|
||||
of the test suite.
|
||||
With a reporter set to dry run that just sends drawing events
|
||||
this is easily accomplished.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="other"><h2>Extending the reporter</h2></a></p>
|
||||
<p>
|
||||
Rather than simply modifying the existing display, you might want to
|
||||
produce a whole new HTML look, or even generate text or XML.
|
||||
Rather than override every method in
|
||||
<span class="new_code">HtmlReporter</span> we can take one
|
||||
step up the class hiearchy to <span class="new_code">SimpleReporter</span>
|
||||
in the <em>simple_test.php</em> source file.
|
||||
</p>
|
||||
<p>
|
||||
A do nothing display, a blank canvas for your own creation, would
|
||||
be...
|
||||
<pre>
|
||||
<strong>require_once('simpletest/simple_test.php');</strong>
|
||||
|
||||
class MyDisplay extends SimpleReporter {<strong>
|
||||
</strong>
|
||||
function paintHeader($test_name) {
|
||||
}
|
||||
|
||||
function paintFooter($test_name) {
|
||||
}
|
||||
|
||||
function paintStart($test_name, $size) {<strong>
|
||||
parent::paintStart($test_name, $size);</strong>
|
||||
}
|
||||
|
||||
function paintEnd($test_name, $size) {<strong>
|
||||
parent::paintEnd($test_name, $size);</strong>
|
||||
}
|
||||
|
||||
function paintPass($message) {<strong>
|
||||
parent::paintPass($message);</strong>
|
||||
}
|
||||
|
||||
function paintFail($message) {<strong>
|
||||
parent::paintFail($message);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
No output would come from this class until you add it.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="cli"><h2>The command line reporter</h2></a></p>
|
||||
<p>
|
||||
SimpleTest also ships with a minimal command line reporter.
|
||||
The interface mimics JUnit to some extent, but paints the
|
||||
failure messages as they arrive.
|
||||
To use the command line reporter simply substitute it
|
||||
for the HTML version...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
$test->run(<strong>new TextReporter()</strong>);
|
||||
?>
|
||||
</pre>
|
||||
Then invoke the test suite from the command line...
|
||||
<pre class="shell">
|
||||
php file_test.php
|
||||
</pre>
|
||||
You will need the command line version of PHP installed
|
||||
of course.
|
||||
A passing test suite looks like this...
|
||||
<pre class="shell">
|
||||
File test
|
||||
OK
|
||||
Test cases run: 1/1, Failures: 0, Exceptions: 0
|
||||
</pre>
|
||||
A failure triggers a display like this...
|
||||
<pre class="shell">
|
||||
File test
|
||||
1) True assertion failed.
|
||||
in createnewfile
|
||||
FAILURES!!!
|
||||
Test cases run: 1/1, Failures: 1, Exceptions: 0
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
One of the main reasons for using a command line driven
|
||||
test suite is of using the tester as part of some automated
|
||||
process.
|
||||
To function properly in shell scripts the test script should
|
||||
return a non-zero exit code on failure.
|
||||
If a test suite fails the value <span class="new_code">false</span>
|
||||
is returned from the <span class="new_code">SimpleTest::run()</span>
|
||||
method.
|
||||
We can use that result to exit the script with the desired return
|
||||
code...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
<strong>exit ($test->run(new TextReporter()) ? 0 : 1);</strong>
|
||||
?>
|
||||
</pre>
|
||||
Of course we don't really want to create two test scripts,
|
||||
a command line one and a web browser one, for each test suite.
|
||||
The command line reporter includes a method to sniff out the
|
||||
run time environment...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/unit_tester.php');
|
||||
require_once('simpletest/reporter.php');
|
||||
|
||||
$test = &new TestSuite('File test');
|
||||
$test->addTestFile('tests/file_test.php');
|
||||
<strong>if (TextReporter::inCli()) {</strong>
|
||||
exit ($test->run(new TextReporter()) ? 0 : 1);
|
||||
<strong>}</strong>
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
This is the form used within SimpleTest itself.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="xml"><h2>Remote testing</h2></a></p>
|
||||
<p>
|
||||
SimpleTest ships with an <span class="new_code">XmlReporter</span> class
|
||||
used for internal communication.
|
||||
When run the output looks like...
|
||||
<pre class="shell">
|
||||
<?xml version="1.0"?>
|
||||
<run>
|
||||
<group size="4">
|
||||
<name>Remote tests</name>
|
||||
<group size="4">
|
||||
<name>Visual test with 48 passes, 48 fails and 4 exceptions</name>
|
||||
<case>
|
||||
<name>testofunittestcaseoutput</name>
|
||||
<test>
|
||||
<name>testofresults</name>
|
||||
<pass>This assertion passed</pass>
|
||||
<fail>This assertion failed</fail>
|
||||
</test>
|
||||
<test>
|
||||
...
|
||||
</test>
|
||||
</case>
|
||||
</group>
|
||||
</group>
|
||||
</run>
|
||||
</pre>
|
||||
You can make use of this format with the parser
|
||||
supplied as part of SimpleTest itself.
|
||||
This is called <span class="new_code">SimpleTestXmlParser</span> and
|
||||
resides in <em>xml.php</em> within the SimpleTest package...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/xml.php');
|
||||
|
||||
...
|
||||
$parser = &new SimpleTestXmlParser(new HtmlReporter());
|
||||
$parser->parse($test_output);
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">$test_output</span> should be the XML format
|
||||
from the XML reporter, and could come from say a command
|
||||
line run of a test case.
|
||||
The parser sends events to the reporter just like any
|
||||
other test run.
|
||||
There are some odd occasions where this is actually useful.
|
||||
</p>
|
||||
<p>
|
||||
A problem with large test suites is thet they can exhaust
|
||||
the default 8Mb memory limit on a PHP process.
|
||||
By having the test groups output in XML and run in
|
||||
separate processes, the output can be reparsed to
|
||||
aggregate the results into a much smaller footprint top level
|
||||
test.
|
||||
</p>
|
||||
<p>
|
||||
Because the XML output can come from anywhere, this opens
|
||||
up the possibility of aggregating test runs from remote
|
||||
servers.
|
||||
A test case already exists to do this within the SimpleTest
|
||||
framework, but it is currently experimental...
|
||||
<pre>
|
||||
<?php
|
||||
<strong>require_once('../remote.php');</strong>
|
||||
require_once('../reporter.php');
|
||||
|
||||
$test_url = ...;
|
||||
$dry_url = ...;
|
||||
|
||||
$test = &new TestSuite('Remote tests');
|
||||
$test->addTestCase(<strong>new RemoteTestCase($test_url, $dry_url)</strong>);
|
||||
$test->run(new HtmlReporter());
|
||||
?>
|
||||
</pre>
|
||||
The <span class="new_code">RemoteTestCase</span> takes the actual location
|
||||
of the test runner, basically a web page in XML format.
|
||||
It also takes the URL of a reporter set to do a dry run.
|
||||
This is so that progress can be reported upward correctly.
|
||||
The <span class="new_code">RemoteTestCase</span> can be added to test suites
|
||||
just like any other group test.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<span class="chosen">Reporting</span>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
431
simpletest/docs/en/unit_test_documentation.html
Normal file
431
simpletest/docs/en/unit_test_documentation.html
Normal file
@@ -0,0 +1,431 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>SimpleTest for PHP regression test documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<span class="chosen">Unit tester</span>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>PHP Unit Test documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#unit">Unit test cases</a> and basic assertions.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#extending_unit">Extending test cases</a> to
|
||||
customise them for your own project.
|
||||
</li>
|
||||
<li>
|
||||
<a href="#running_unit">Running a single case</a> as
|
||||
a single script.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="unit"><h2>Unit test cases</h2></a></p>
|
||||
<p>
|
||||
The core system is a regression testing framework built around
|
||||
test cases.
|
||||
A sample test case looks like this...
|
||||
<pre>
|
||||
<strong>class FileTestCase extends UnitTestCase {
|
||||
}</strong>
|
||||
</pre>
|
||||
Actual tests are added as methods in the test case whose names
|
||||
by default start with the string "test" and
|
||||
when the test case is invoked all such methods are run in
|
||||
the order that PHP introspection finds them.
|
||||
As many test methods can be added as needed.
|
||||
</p>
|
||||
<p>
|
||||
For example...
|
||||
<pre>
|
||||
require_once('simpletest/autorun.php');
|
||||
require_once('../classes/writer.php');
|
||||
|
||||
class FileTestCase extends UnitTestCase {
|
||||
function FileTestCase() {
|
||||
$this->UnitTestCase('File test');
|
||||
}<strong>
|
||||
|
||||
function setUp() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function testCreation() {
|
||||
$writer = &new FileWriter('../temp/test.txt');
|
||||
$writer->write('Hello');
|
||||
$this->assertTrue(file_exists('../temp/test.txt'), 'File created');
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
The constructor is optional and usually omitted.
|
||||
Without a name, the class name is taken as the name of the test case.
|
||||
</p>
|
||||
<p>
|
||||
Our only test method at the moment is <span class="new_code">testCreation()</span>
|
||||
where we check that a file has been created by our
|
||||
<span class="new_code">Writer</span> object.
|
||||
We could have put the <span class="new_code">unlink()</span>
|
||||
code into this method as well, but by placing it in
|
||||
<span class="new_code">setUp()</span> and
|
||||
<span class="new_code">tearDown()</span> we can use it with
|
||||
other test methods that we add.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">setUp()</span> method is run
|
||||
just before each and every test method.
|
||||
<span class="new_code">tearDown()</span> is run just after
|
||||
each and every test method.
|
||||
</p>
|
||||
<p>
|
||||
You can place some test case set up into the constructor to
|
||||
be run once for all the methods in the test case, but
|
||||
you risk test inteference that way.
|
||||
This way is slightly slower, but it is safer.
|
||||
Note that if you come from a JUnit background this will not
|
||||
be the behaviour you are used to.
|
||||
JUnit surprisingly reinstantiates the test case for each test
|
||||
method to prevent such interference.
|
||||
SimpleTest requires the end user to use <span class="new_code">setUp()</span>, but
|
||||
supplies additional hooks for library writers.
|
||||
</p>
|
||||
<p>
|
||||
The means of reporting test results (see below) are by a
|
||||
visiting display class
|
||||
that is notified by various <span class="new_code">assert...()</span>
|
||||
methods.
|
||||
Here is the full list for the <span class="new_code">UnitTestCase</span>
|
||||
class, the default for SimpleTest...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">assertTrue($x)</span></td>
|
||||
<td>Fail if $x is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertFalse($x)</span></td>
|
||||
<td>Fail if $x is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNull($x)</span></td>
|
||||
<td>Fail if $x is set</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotNull($x)</span></td>
|
||||
<td>Fail if $x not set</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertIsA($x, $t)</span></td>
|
||||
<td>Fail if $x is not the class or type $t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotA($x, $t)</span></td>
|
||||
<td>Fail if $x is of the class or type $t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertEqual($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotEqual($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertWithinMargin($x, $y, $m)</span></td>
|
||||
<td>Fail if abs($x - $y) < $m is false</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertOutsideMargin($x, $y, $m)</span></td>
|
||||
<td>Fail if abs($x - $y) < $m is true</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertIdentical($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is false or a type mismatch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNotIdentical($x, $y)</span></td>
|
||||
<td>Fail if $x == $y is true and types match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertReference($x, $y)</span></td>
|
||||
<td>Fail unless $x and $y are the same variable</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertClone($x, $y)</span></td>
|
||||
<td>Fail unless $x and $y are identical copies</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertPattern($p, $x)</span></td>
|
||||
<td>Fail unless the regex $p matches $x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoPattern($p, $x)</span></td>
|
||||
<td>Fail if the regex $p matches $x</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">expectError($x)</span></td>
|
||||
<td>Swallows any upcoming matching error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assert($e)</span></td>
|
||||
<td>Fail on failed <a href="expectation_documentation.html">expectation</a> object $e</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
All assertion methods can take an optional description as a
|
||||
last parameter.
|
||||
This is to label the displayed result with.
|
||||
If omitted a default message is sent instead, which is usually
|
||||
sufficient.
|
||||
This default message can still be embedded in your own message
|
||||
if you include "%s" within the string.
|
||||
All the assertions return true on a pass or false on failure.
|
||||
</p>
|
||||
<p>
|
||||
Some examples...
|
||||
<pre>
|
||||
$variable = null;
|
||||
<strong>$this->assertNull($variable, 'Should be cleared');</strong>
|
||||
</pre>
|
||||
...will pass and normally show no message.
|
||||
If you have
|
||||
<a href="http://www.lastcraft.com/display_subclass_tutorial.php">set up the tester to display passes</a>
|
||||
as well then the message will be displayed as is.
|
||||
<pre>
|
||||
<strong>$this->assertIdentical(0, false, 'Zero is not false [%s]');</strong>
|
||||
</pre>
|
||||
This will fail as it performs a type
|
||||
check, as well as a comparison, between the two values.
|
||||
The "%s" part is replaced by the default
|
||||
error message that would have been shown if we had not
|
||||
supplied our own.
|
||||
<pre>
|
||||
$a = 1;
|
||||
$b = $a;
|
||||
<strong>$this->assertReference($a, $b);</strong>
|
||||
</pre>
|
||||
Will fail as the variable <span class="new_code">$a</span> is a copy of <span class="new_code">$b</span>.
|
||||
<pre>
|
||||
<strong>$this->assertPattern('/hello/i', 'Hello world');</strong>
|
||||
</pre>
|
||||
This will pass as using a case insensitive match the string
|
||||
<span class="new_code">hello</span> is contained in <span class="new_code">Hello world</span>.
|
||||
<pre>
|
||||
<strong>$this->expectError();</strong>
|
||||
trigger_error('Catastrophe');
|
||||
</pre>
|
||||
Here the check catches the "Catastrophe"
|
||||
message without checking the text and passes.
|
||||
This removes the error from the queue.
|
||||
<pre>
|
||||
<strong>$this->expectError('Catastrophe');</strong>
|
||||
trigger_error('Catastrophe');
|
||||
</pre>
|
||||
The next error check tests not only the existence of the error,
|
||||
but also the text which, here matches so another pass.
|
||||
If any unchecked errors are left at the end of a test method then
|
||||
an exception will be reported in the test.
|
||||
</p>
|
||||
<p>
|
||||
Note that SimpleTest cannot catch compile time PHP errors.
|
||||
</p>
|
||||
<p>
|
||||
The test cases also have some convenience methods for debugging
|
||||
code or extending the suite...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">setUp()</span></td>
|
||||
<td>Runs this before each test method</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">tearDown()</span></td>
|
||||
<td>Runs this after each test method</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">pass()</span></td>
|
||||
<td>Sends a test pass</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">fail()</span></td>
|
||||
<td>Sends a test failure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">error()</span></td>
|
||||
<td>Sends an exception event</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">signal($type, $payload)</span></td>
|
||||
<td>Sends a user defined message to the test reporter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">dump($var)</span></td>
|
||||
<td>Does a formatted <span class="new_code">print_r()</span> for quick and dirty debugging</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="extending_unit"><h2>Extending test cases</h2></a></p>
|
||||
<p>
|
||||
Of course additional test methods can be added to create
|
||||
specific types of test case, so as to extend framework...
|
||||
<pre>
|
||||
require_once('simpletest/autorun.php');
|
||||
<strong>
|
||||
class FileTester extends UnitTestCase {
|
||||
function FileTester($name = false) {
|
||||
$this->UnitTestCase($name);
|
||||
}
|
||||
|
||||
function assertFileExists($filename, $message = '%s') {
|
||||
$this->assertTrue(
|
||||
file_exists($filename),
|
||||
sprintf($message, 'File [$filename] existence check'));
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
Here the SimpleTest library is held in a folder called
|
||||
<em>simpletest</em> that is local.
|
||||
Substitute your own path for this.
|
||||
</p>
|
||||
<p>
|
||||
To prevent this test case being run accidently, it is
|
||||
advisable to mark it as <span class="new_code">abstract</span>.
|
||||
</p>
|
||||
<p>
|
||||
Alternatively you could add a
|
||||
<span class="new_code">SimpleTestOptions::ignore('FileTester');</span>
|
||||
directive in your code.
|
||||
</p>
|
||||
<p>
|
||||
This new case can be now be inherited just like
|
||||
a normal test case...
|
||||
<pre>
|
||||
class FileTestCase extends <strong>FileTester</strong> {
|
||||
|
||||
function setUp() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
@unlink('../temp/test.txt');
|
||||
}
|
||||
|
||||
function testCreation() {
|
||||
$writer = &new FileWriter('../temp/test.txt');
|
||||
$writer->write('Hello');<strong>
|
||||
$this->assertFileExists('../temp/test.txt');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
If you want a test case that does not have all of the
|
||||
<span class="new_code">UnitTestCase</span> assertions,
|
||||
only your own and a few basics,
|
||||
you need to extend the <span class="new_code">SimpleTestCase</span>
|
||||
class instead.
|
||||
It is found in <em>simple_test.php</em> rather than
|
||||
<em>unit_tester.php</em>.
|
||||
See <a href="group_test_documentation.html">later</a> if you
|
||||
want to incorporate other unit tester's
|
||||
test cases in your test suites.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="running_unit"><h2>Running a single test case</h2></a></p>
|
||||
<p>
|
||||
You won't often run single test cases except when bashing
|
||||
away at a module that is having difficulty, and you don't
|
||||
want to upset the main test suite.
|
||||
With <em>autorun</em> no particular scaffolding is needed,
|
||||
just launch your particular test file and you're ready to go.
|
||||
</p>
|
||||
<p>
|
||||
You can even decide which reporter (for example,
|
||||
<span class="new_code">TextReporter</span> or <span class="new_code">HtmlReporter</span>)
|
||||
you prefer for a specific file when launched on its own...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');<strong>
|
||||
SimpleTest :: prefer(new TextReporter());</strong>
|
||||
require_once('../classes/writer.php');
|
||||
|
||||
class FileTestCase extends UnitTestCase {
|
||||
...
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
This script will run as is, but of course will output zero passes
|
||||
and zero failures until test methods are added.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://simpletest.org/api/">Full API for SimpleTest</a>
|
||||
from the PHPDoc.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<span class="chosen">Unit tester</span>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<a href="web_tester_documentation.html">Web tester</a>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
584
simpletest/docs/en/web_tester_documentation.html
Normal file
584
simpletest/docs/en/web_tester_documentation.html
Normal file
@@ -0,0 +1,584 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Simple Test for PHP web script testing documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="docs.css" title="Styles">
|
||||
</head>
|
||||
<body>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<span class="chosen">Web tester</span>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<h1>Web tester documentation</h1>
|
||||
This page...
|
||||
<ul>
|
||||
<li>
|
||||
Successfully <a href="#fetch">fetching a web page</a>
|
||||
</li>
|
||||
<li>
|
||||
Testing the <a href="#content">page content</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#navigation">Navigating a web site</a>
|
||||
while testing
|
||||
</li>
|
||||
<li>
|
||||
<a href="#request">Raw request modifications</a> and debugging methods
|
||||
</li>
|
||||
</ul>
|
||||
<div class="content">
|
||||
<p><a class="target" name="fetch"><h2>Fetching a page</h2></a></p>
|
||||
<p>
|
||||
Testing classes is all very well, but PHP is predominately
|
||||
a language for creating functionality within web pages.
|
||||
How do we test the front end presentation role of our PHP
|
||||
applications?
|
||||
Well the web pages are just text, so we should be able to
|
||||
examine them just like any other test data.
|
||||
</p>
|
||||
<p>
|
||||
This leads to a tricky issue.
|
||||
If we test at too low a level, testing for matching tags
|
||||
in the page with pattern matching for example, our tests will
|
||||
be brittle.
|
||||
The slightest change in layout could break a large number of
|
||||
tests.
|
||||
If we test at too high a level, say using mock versions of a
|
||||
template engine, then we lose the ability to automate some classes
|
||||
of test.
|
||||
For example, the interaction of forms and navigation will
|
||||
have to be tested manually.
|
||||
These types of test are extremely repetitive and error prone.
|
||||
</p>
|
||||
<p>
|
||||
SimpleTest includes a special form of test case for the testing
|
||||
of web page actions.
|
||||
The <span class="new_code">WebTestCase</span> includes facilities
|
||||
for navigation, content and cookie checks and form handling.
|
||||
Usage of these test cases is similar to the
|
||||
<a href="unit_tester_documentation.html">UnitTestCase</a>...
|
||||
<pre>
|
||||
<strong>class TestOfLastcraft extends WebTestCase {
|
||||
}</strong>
|
||||
</pre>
|
||||
Here we are about to test the
|
||||
<a href="http://www.lastcraft.com/">Last Craft</a> site itself.
|
||||
If this test case is in a file called <em>lastcraft_test.php</em>
|
||||
then it can be loaded in a runner script just like unit tests...
|
||||
<pre>
|
||||
<?php
|
||||
require_once('simpletest/autorun.php');<strong>
|
||||
require_once('simpletest/web_tester.php');</strong>
|
||||
SimpleTest::prefer(new TextReporter());
|
||||
|
||||
class WebTests extends TestSuite {
|
||||
function WebTests() {
|
||||
$this->TestSuite('Web site tests');<strong>
|
||||
$this->addFile('lastcraft_test.php');</strong>
|
||||
}
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
I am using the text reporter here to more clearly
|
||||
distinguish the web content from the test output.
|
||||
</p>
|
||||
<p>
|
||||
Nothing is being tested yet.
|
||||
We can fetch the home page by using the
|
||||
<span class="new_code">get()</span> method...
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
<strong>
|
||||
function testHomepage() {
|
||||
$this->assertTrue($this->get('http://www.lastcraft.com/'));
|
||||
}</strong>
|
||||
}
|
||||
</pre>
|
||||
The <span class="new_code">get()</span> method will
|
||||
return true only if page content was successfully
|
||||
loaded.
|
||||
It is a simple, but crude way to check that a web page
|
||||
was actually delivered by the web server.
|
||||
However that content may be a 404 response and yet
|
||||
our <span class="new_code">get()</span> method will still return true.
|
||||
</p>
|
||||
<p>
|
||||
Assuming that the web server for the Last Craft site is up
|
||||
(sadly not always the case), we should see...
|
||||
<pre class="shell">
|
||||
Web site tests
|
||||
OK
|
||||
Test cases run: 1/1, Failures: 0, Exceptions: 0
|
||||
</pre>
|
||||
All we have really checked is that any kind of page was
|
||||
returned.
|
||||
We don't yet know if it was the right one.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="content"><h2>Testing page content</h2></a></p>
|
||||
<p>
|
||||
To confirm that the page we think we are on is actually the
|
||||
page we are on, we need to verify the page content.
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
|
||||
function testHomepage() {<strong>
|
||||
$this->get('http://www.lastcraft.com/');
|
||||
$this->assertText('Why the last craft');</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The page from the last fetch is held in a buffer in
|
||||
the test case, so there is no need to refer to it directly.
|
||||
The pattern match is always made against the buffer.
|
||||
</p>
|
||||
<p>
|
||||
Here is the list of possible content assertions...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">assertTitle($title)</span></td>
|
||||
<td>Pass if title is an exact match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertText($text)</span></td>
|
||||
<td>Pass if matches visible and "alt" text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoText($text)</span></td>
|
||||
<td>Pass if doesn't match visible and "alt" text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertPattern($pattern)</span></td>
|
||||
<td>A Perl pattern match against the page content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoPattern($pattern)</span></td>
|
||||
<td>A Perl pattern match to not find content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertLink($label)</span></td>
|
||||
<td>Pass if a link with this text is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoLink($label)</span></td>
|
||||
<td>Pass if no link with this text is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertLinkById($id)</span></td>
|
||||
<td>Pass if a link with this id attribute is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoLinkById($id)</span></td>
|
||||
<td>Pass if no link with this id attribute is present</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertField($name, $value)</span></td>
|
||||
<td>Pass if an input tag with this name has this value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertFieldById($id, $value)</span></td>
|
||||
<td>Pass if an input tag with this id has this value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertResponse($codes)</span></td>
|
||||
<td>Pass if HTTP response matches this list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertMime($types)</span></td>
|
||||
<td>Pass if MIME type is in this list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertAuthentication($protocol)</span></td>
|
||||
<td>Pass if the current challenge is this protocol</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoAuthentication()</span></td>
|
||||
<td>Pass if there is no current challenge</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertRealm($name)</span></td>
|
||||
<td>Pass if the current challenge realm matches</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertHeader($header, $content)</span></td>
|
||||
<td>Pass if a header was fetched matching this value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoHeader($header)</span></td>
|
||||
<td>Pass if a header was not fetched</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertCookie($name, $value)</span></td>
|
||||
<td>Pass if there is currently a matching cookie</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">assertNoCookie($name)</span></td>
|
||||
<td>Pass if there is currently no cookie of this name</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
As usual with the SimpleTest assertions, they all return
|
||||
false on failure and true on pass.
|
||||
They also allow an optional test message and you can embed
|
||||
the original test message inside using "%s" inside
|
||||
your custom message.
|
||||
</p>
|
||||
<p>
|
||||
So now we could instead test against the title tag with...
|
||||
<pre>
|
||||
<strong>$this->assertTitle('The Last Craft? Web developer tutorials on PHP, Extreme programming and Object Oriented development');</strong>
|
||||
</pre>
|
||||
...or, if that is too long and fragile...
|
||||
<pre>
|
||||
<strong>$this->assertTitle(new PatternExpectation('/The Last Craft/'));</strong>
|
||||
</pre>
|
||||
As well as the simple HTML content checks we can check
|
||||
that the MIME type is in a list of allowed types with...
|
||||
<pre>
|
||||
<strong>$this->assertMime(array('text/plain', 'text/html'));</strong>
|
||||
</pre>
|
||||
More interesting is checking the HTTP response code.
|
||||
Like the MIME type, we can assert that the response code
|
||||
is in a list of allowed values...
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
|
||||
function testRedirects() {
|
||||
$this->get('http://www.lastcraft.com/test/redirect.php');
|
||||
$this->assertResponse(200);</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
Here we are checking that the fetch is successful by
|
||||
allowing only a 200 HTTP response.
|
||||
This test will pass, but it is not actually correct to do so.
|
||||
There is no page, instead the server issues a redirect.
|
||||
The <span class="new_code">WebTestCase</span> will
|
||||
automatically follow up to three such redirects.
|
||||
The tests are more robust this way and we are usually
|
||||
interested in the interaction with the pages rather
|
||||
than their delivery.
|
||||
If the redirects are of interest then this ability must
|
||||
be disabled...
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
|
||||
function testHomepage() {<strong>
|
||||
$this->setMaximumRedirects(0);</strong>
|
||||
$this->get('http://www.lastcraft.com/test/redirect.php');
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The assertion now fails as expected...
|
||||
<pre class="shell">
|
||||
Web site tests
|
||||
1) Expecting response in [200] got [302]
|
||||
in testhomepage
|
||||
in testoflastcraft
|
||||
in lastcraft_test.php
|
||||
FAILURES!!!
|
||||
Test cases run: 1/1, Failures: 1, Exceptions: 0
|
||||
</pre>
|
||||
We can modify the test to correctly assert redirects with...
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
|
||||
function testHomepage() {
|
||||
$this->setMaximumRedirects(0);
|
||||
$this->get('http://www.lastcraft.com/test/redirect.php');
|
||||
$this->assertResponse(<strong>array(301, 302, 303, 307)</strong>);
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
This now passes.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="navigation"><h2>Navigating a web site</h2></a></p>
|
||||
<p>
|
||||
Users don't often navigate sites by typing in URLs, but by
|
||||
clicking links and buttons.
|
||||
Here we confirm that the contact details can be reached
|
||||
from the home page...
|
||||
<pre>
|
||||
class TestOfLastcraft extends WebTestCase {
|
||||
...
|
||||
function testContact() {
|
||||
$this->get('http://www.lastcraft.com/');<strong>
|
||||
$this->clickLink('About');
|
||||
$this->assertTitle(new PatternExpectation('/About Last Craft/'));</strong>
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
The parameter is the text of the link.
|
||||
</p>
|
||||
<p>
|
||||
If the target is a button rather than an anchor tag, then
|
||||
<span class="new_code">clickSubmit()</span> can be used
|
||||
with the button title...
|
||||
<pre>
|
||||
<strong>$this->clickSubmit('Go!');</strong>
|
||||
</pre>
|
||||
If you are not sure or don't care, the usual case, then just
|
||||
use the <span class="new_code">click()</span> method...
|
||||
<pre>
|
||||
<strong>$this->click('Go!');</strong>
|
||||
</pre>
|
||||
</p>
|
||||
<p>
|
||||
The list of navigation methods is...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">getUrl()</span></td>
|
||||
<td>The current location</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">get($url, $parameters)</span></td>
|
||||
<td>Send a GET request with these parameters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">post($url, $parameters)</span></td>
|
||||
<td>Send a POST request with these parameters</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">head($url, $parameters)</span></td>
|
||||
<td>Send a HEAD request without replacing the page content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">retry()</span></td>
|
||||
<td>Reload the last request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">back()</span></td>
|
||||
<td>Like the browser back button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">forward()</span></td>
|
||||
<td>Like the browser forward button</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">authenticate($name, $password)</span></td>
|
||||
<td>Retry after a challenge</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">restart()</span></td>
|
||||
<td>Restarts the browser as if a new session</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getCookie($name)</span></td>
|
||||
<td>Gets the cookie value for the current context</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ageCookies($interval)</span></td>
|
||||
<td>Ages current cookies prior to a restart</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clearFrameFocus()</span></td>
|
||||
<td>Go back to treating all frames as one page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmit($label)</span></td>
|
||||
<td>Click the first button with this label</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitByName($name)</span></td>
|
||||
<td>Click the button with this name attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickSubmitById($id)</span></td>
|
||||
<td>Click the button with this ID attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImage($label, $x, $y)</span></td>
|
||||
<td>Click an input tag of type image by title or alt text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageByName($name, $x, $y)</span></td>
|
||||
<td>Click an input tag of type image by name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickImageById($id, $x, $y)</span></td>
|
||||
<td>Click an input tag of type image by ID attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">submitFormById($id)</span></td>
|
||||
<td>Submit a form without the submit value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLink($label, $index)</span></td>
|
||||
<td>Click an anchor by the visible label text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">clickLinkById($id)</span></td>
|
||||
<td>Click an anchor by the ID attribute</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">getFrameFocus()</span></td>
|
||||
<td>The name of the currently selected frame</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocusByIndex($choice)</span></td>
|
||||
<td>Focus on a frame counting from 1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setFrameFocus($name)</span></td>
|
||||
<td>Focus on a frame by name</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</p>
|
||||
<p>
|
||||
The parameters in the <span class="new_code">get()</span>, <span class="new_code">post()</span> or
|
||||
<span class="new_code">head()</span> methods are optional.
|
||||
The HTTP HEAD fetch does not change the browser context, only loads
|
||||
cookies.
|
||||
This can be useful for when an image or stylesheet sets a cookie
|
||||
for crafty robot blocking.
|
||||
</p>
|
||||
<p>
|
||||
The <span class="new_code">retry()</span>, <span class="new_code">back()</span> and
|
||||
<span class="new_code">forward()</span> commands work as they would on
|
||||
your web browser.
|
||||
They use the history to retry pages.
|
||||
This can be handy for checking the effect of hitting the
|
||||
back button on your forms.
|
||||
</p>
|
||||
<p>
|
||||
The frame methods need a little explanation.
|
||||
By default a framed page is treated just like any other.
|
||||
Content will be searced for throughout the entire frameset,
|
||||
so clicking a link will work no matter which frame
|
||||
the anchor tag is in.
|
||||
You can override this behaviour by focusing on a single
|
||||
frame.
|
||||
If you do that, all searches and actions will apply to that
|
||||
frame alone, such as authentication and retries.
|
||||
If a link or button is not in a focused frame then it cannot
|
||||
be clicked.
|
||||
</p>
|
||||
<p>
|
||||
Testing navigation on fixed pages only tells you when you
|
||||
have broken an entire script.
|
||||
For highly dynamic pages, such as for bulletin boards, this can
|
||||
be crucial for verifying the correctness of the application.
|
||||
For most applications though, the really tricky logic is usually in
|
||||
the handling of forms and sessions.
|
||||
Fortunately SimpleTest includes
|
||||
<a href="form_testing_documentation.html">tools for testing web forms</a>
|
||||
as well.
|
||||
</p>
|
||||
|
||||
<p><a class="target" name="request"><h2>Modifying the request</h2></a></p>
|
||||
<p>
|
||||
Although SimpleTest does not have the goal of testing networking
|
||||
problems, it does include some methods to modify and debug
|
||||
the requests it makes.
|
||||
Here is another method list...
|
||||
<table><tbody>
|
||||
<tr>
|
||||
<td><span class="new_code">getTransportError()</span></td>
|
||||
<td>The last socket error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">showRequest()</span></td>
|
||||
<td>Dump the outgoing request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">showHeaders()</span></td>
|
||||
<td>Dump the incoming headers</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">showSource()</span></td>
|
||||
<td>Dump the raw HTML page content</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">ignoreFrames()</span></td>
|
||||
<td>Do not load framesets</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setCookie($name, $value)</span></td>
|
||||
<td>Set a cookie from now on</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">addHeader($header)</span></td>
|
||||
<td>Always add this header to the request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setMaximumRedirects($max)</span></td>
|
||||
<td>Stop after this many redirects</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">setConnectionTimeout($timeout)</span></td>
|
||||
<td>Kill the connection after this time between bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="new_code">useProxy($proxy, $name, $password)</span></td>
|
||||
<td>Make requests via this proxy URL</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
These methods are principally for debugging.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
References and related information...
|
||||
<ul>
|
||||
<li>
|
||||
SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.
|
||||
</li>
|
||||
<li>
|
||||
SimpleTest download page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>.
|
||||
</li>
|
||||
<li>
|
||||
The <a href="http://simpletest.org/api/">developer's API for SimpleTest</a>
|
||||
gives full detail on the classes and assertions available.
|
||||
</li>
|
||||
</ul>
|
||||
<div class="menu_back"><div class="menu">
|
||||
<a href="index.html">SimpleTest</a>
|
||||
|
|
||||
<a href="overview.html">Overview</a>
|
||||
|
|
||||
<a href="unit_test_documentation.html">Unit tester</a>
|
||||
|
|
||||
<a href="group_test_documentation.html">Group tests</a>
|
||||
|
|
||||
<a href="mock_objects_documentation.html">Mock objects</a>
|
||||
|
|
||||
<a href="partial_mocks_documentation.html">Partial mocks</a>
|
||||
|
|
||||
<a href="reporter_documentation.html">Reporting</a>
|
||||
|
|
||||
<a href="expectation_documentation.html">Expectations</a>
|
||||
|
|
||||
<span class="chosen">Web tester</span>
|
||||
|
|
||||
<a href="form_testing_documentation.html">Testing forms</a>
|
||||
|
|
||||
<a href="authentication_documentation.html">Authentication</a>
|
||||
|
|
||||
<a href="browser_documentation.html">Scriptable browser</a>
|
||||
</div></div>
|
||||
<div class="copyright">
|
||||
Copyright<br>Marcus Baker 2006
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user