diff options
author | wei <> | 2006-07-05 07:35:50 +0000 |
---|---|---|
committer | wei <> | 2006-07-05 07:35:50 +0000 |
commit | b6dfb6c447cf502e694d299dbda1b2e092c3312d (patch) | |
tree | b1bbd0abf857cca4b297d575942efa60edd12480 /test_tools/simpletest/docs/en | |
parent | b5c7c7b77d33aa3e04ed6c16a489a2076a30f57a (diff) |
move tests to test_tools
Diffstat (limited to 'test_tools/simpletest/docs/en')
14 files changed, 5606 insertions, 0 deletions
diff --git a/test_tools/simpletest/docs/en/authentication_documentation.html b/test_tools/simpletest/docs/en/authentication_documentation.html new file mode 100755 index 00000000..0623023c --- /dev/null +++ b/test_tools/simpletest/docs/en/authentication_documentation.html @@ -0,0 +1,320 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<span class="chosen">Authentication</span> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Authentication documentation</h1> +<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> + 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 testAuthentication() { + $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. + </p> + <p> + 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->clickSubmit('Log in'); + $this->assertWantedPattern('/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->assertWantedPattern('/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->clickSubmit('Log in'); + $this->assertWantedPattern('/Welcome Me/');<strong> + + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertWantedPattern('/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->clickSubmit('Log in'); + $this->assertWantedPattern('/Welcome Me/'); + <strong> + $this->ageCookies(3600);</strong> + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertWantedPattern('/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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/browser_documentation.html b/test_tools/simpletest/docs/en/browser_documentation.html new file mode 100755 index 00000000..ef54aaea --- /dev/null +++ b/test_tools/simpletest/docs/en/browser_documentation.html @@ -0,0 +1,386 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<span class="chosen">Scriptable browser</span> +</li> +</ul> +</div> +</div> +<h1>PHP Scriptable Web Browser</h1> +<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->clickLink('reporting bugs'); + $browser->clickLink('statistics'); + $page = $browser->clickLink('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">isLink($label)</span></td><td>See if a link is present 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">isLinkById($id)</span></td><td>See if a link is present by attribut</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 the image by alt text</td> +</tr> + <tr> +<td><span class="new_code">clickImageByName($name, $x, $y)</span></td><td>Clicks the image by attribute</td> +</tr> + <tr> +<td><span class="new_code">clickImageById($id, $x, $y)</span></td><td>Clicks the image by 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> + </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> and + <span class="new_code">SimpleBrowser::useFrames()</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->clickSubmit('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->clickSubmit('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->clickSubmit('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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/docs.css b/test_tools/simpletest/docs/en/docs.css new file mode 100755 index 00000000..93226cd7 --- /dev/null +++ b/test_tools/simpletest/docs/en/docs.css @@ -0,0 +1,84 @@ +body { + padding-left: 3%; + padding-right: 3%; +} +pre { + font-family: courier; + font-size: 80%; + border: 1px solid; + background-color: #cccccc; + padding: 5px; + margin-left: 5%; + margin-right: 8%; +} +.code, .new_code, pre.new_code { + font-weight: bold; +} +div.copyright { + font-size: 80%; + color: gray; +} +div.copyright a { + color: gray; +} +ul.api { + padding-left: 0em; + padding-right: 25%; +} +ul.api li { + margin-top: 0.2em; + margin-bottom: 0.2em; + list-style: none; + text-indent: -3em; + padding-left: 3em; +} +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; +} +table { + border: 2px outset; + border-color: gray; + background-color: white; + margin: 5px; + margin-left: 5%; + margin-right: 5%; +} +td { + font-size: 80%; +} +.shell { + color: white; +} +pre.shell { + border: 4px ridge; + border-color: gray; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: black; +} +form.demo { + background-color: lightgray; + border: 4px outset; + border-color: lightgray; + padding: 10px; + margin-right: 40%; +} diff --git a/test_tools/simpletest/docs/en/expectation_documentation.html b/test_tools/simpletest/docs/en/expectation_documentation.html new file mode 100755 index 00000000..0165988c --- /dev/null +++ b/test_tools/simpletest/docs/en/expectation_documentation.html @@ -0,0 +1,356 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<span class="chosen">Expectations</span> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Expectation documentation</h1> +<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($this); + $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); + + $writer->tally();</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($this);<strong> + $writer->expectOnce( + 'write', + array(new WantedPatternExpectation('/cannot connect/i')));</strong> + + $service = &new NewsService('BBC News'); + $service->publish($writer); + + $writer->tally(); + } +} +</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">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">WantedPatternExpectation</span></td><td>Uses a Perl Regex to match a string</td> +</tr> + <tr> +<td><span class="new_code">NoUnwantedPatternExpectation</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> +<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 either + the + <a href="mock_objects_documentation.html">mock objects</a> + or the + <a href="server_stubs_documentation.html">server stubs</a>. + Anywhere a list of arguments is given, a list of expectation objects + can be inserted instead. + </p> + <p> + Suppose we want an authorisation server stub to simulate a successful login + only if it receives a valid session object. + We can do this as follows... +<pre> +Stub::generate('Authorisation'); +<strong> +$authorisation = new StubAuthorisation(); +$authorisation->setReturnValue( + 'isAllowed', + true, + array(new IsAExpectation('Session', 'Must be a session'))); +$authorisation->setReturnValue('isAllowed', false);</strong> +</pre> + We have set the default stub 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::assertExpectation()</span> method to + test against it directly... +<pre> +<strong>class TestOfNetworking extends UnitTestCase { + ... + function testGetValidIp() { + $server = &new Server(); + $this->assertExpectation( + 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->assertExpectation(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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/form_testing_documentation.html b/test_tools/simpletest/docs/en/form_testing_documentation.html new file mode 100755 index 00000000..b1e15b3d --- /dev/null +++ b/test_tools/simpletest/docs/en/form_testing_documentation.html @@ -0,0 +1,277 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<span class="chosen">Testing forms</span> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Form testing documentation</h1> +<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" + </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->clickSubmit('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> + Although most standard HTML widgets are catered for by <em>SimpleTest</em>'s + built in parser, it is unlikely that JavaScript will be implemented + anytime soon. + </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->clickSubmit('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="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->assertNoUnwantedPattern('/user created/i'); + }</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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/group_test_documentation.html b/test_tools/simpletest/docs/en/group_test_documentation.html new file mode 100755 index 00000000..adbc66ef --- /dev/null +++ b/test_tools/simpletest/docs/en/group_test_documentation.html @@ -0,0 +1,357 @@ +<html> +<head> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>SimpleTest for PHP group test documentation</title> +<link rel="stylesheet" type="text/css" href="docs.css" title="Styles"> +</head> +<body> +<div class="menu_back"> +<div class="menu"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<span class="chosen">Group tests</span> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Group Test documentation</h1> +<div class="content"> + <p> +<a class="target" name="group"> +<h2>Grouping tests</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. +<pre> +<?php + require_once('../classes/io.php'); +<strong> + class MyFileTestCase extends UnitTestCase { + ... + } + SimpleTestOptions::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). + We will call this sample <em>file_test.php</em>. + </p> + <p> + Next we create a group test file, called say <em>group_test.php</em>. + You will think of a better name I am sure. + 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 GroupTest('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, so another method is provided that will only + instantiate the class when it is needed... +<pre> +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('file_test.php'); + + $test = &new GroupTest('All file tests');<strong> + $test->addTestClass('FileTestCase');</strong> + $test->run(new HtmlReporter()); +?> +</pre> + The problem with this method 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. + We can save a lot of typing with... +<pre> +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('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">GroupTest</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> + 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. + </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">GroupTest::addTestFile()</span> + line. + </li> + </ol> + </p> + + <p> +<a class="target" name="higher"> +<h2>Higher groupings</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">GroupTest</span> and then instantiate it as needed... +<pre> +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + <strong> + class FileGroupTest extends GroupTest { + function FileGroupTest() { + $this->GroupTest('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_group_test.php'); + <strong> + $test = &new FileGroupTest(); + $test->run(new HtmlReporter());</strong> +?> +</pre> + ...or we can group them into even larger group tests... +<pre> +<?php + require_once('file_group_test.php'); + <strong> + $test = &new BigGroupTest('Big group'); + $test->addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter());</strong> +?> +</pre> + 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 FileGroupTest extends GroupTest { + function FileGroupTest() { + $this->GroupTest('All file tests'); + $test->addTestFile('file_test.php'); + } + } + <strong> + if (! defined('RUNNER')) { + define('RUNNER', true);</strong> + $test = &new FileGroupTest(); + $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_group_test.php'); + + $test = &new BigGroupTest('Big group'); + $test->addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> +</pre> + As with the normal test cases, a <span class="new_code">GroupTest</span> can + be loaded with the <span class="new_code">GroupTest::addTestFile()</span> method. +<pre> +<?php + define('RUNNER', true); + + $test = &new BigGroupTest('Big group');<strong> + $test->addTestFile('file_group_test.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 GroupTest('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 GroupTest('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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/index.html b/test_tools/simpletest/docs/en/index.html new file mode 100755 index 00000000..04797272 --- /dev/null +++ b/test_tools/simpletest/docs/en/index.html @@ -0,0 +1,467 @@ +<html> +<head> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title> + Download the Simple Test testing framework - + Unit tests and mock objects for PHP + </title> +<link rel="stylesheet" type="text/css" href="docs.css" title="Styles"> +</head> +<body> +<div class="menu_back"> +<div class="menu"> +<h2> +<span class="chosen">SimpleTest</span> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Simple Test for PHP</h1> +<div class="content"> + + + <p> + The following assumes that you are familiar with the concept + of unit testing as well as the PHP web development language. + It is a guide for the impatient new user of + <a href="https://sourceforge.net/project/showfiles.php?group_id=76550">SimpleTest</a>. + For fuller documentation, especially if you are new + to unit testing see the ongoing + <a href="unit_test_documentation.html">documentation</a>, and for + example test cases see the + <a href="http://www.lastcraft.com/first_test_tutorial.php">unit testing tutorial</a>. + </p> + + <p> +<a class="target" name="unit"> +<h2>Using the tester quickly</h2> +</a> +</p> + <p> + Amongst software testing tools, a unit tester is the one + closest to the developer. + In the context of agile development the test code sits right + next to the source code as both are written simultaneously. + In this context SimpleTest aims to be a complete PHP developer + test solution and is called "Simple" because it + should be easy to use and extend. + It wasn't a good choice of name really. + It includes all of the typical functions you would expect from + <a href="http://www.junit.org/">JUnit</a> and the + <a href="http://sourceforge.net/projects/phpunit/">PHPUnit</a> + ports, but also adds + <a href="http://www.mockobjects.com">mock objects</a>. + It has some <a href="http://sourceforge.net/projects/jwebunit/">JWebUnit</a> + functionality as well. + This includes web page navigation, cookie testing and form submission. + </p> + <p> + The quickest way to demonstrate is with an example. + </p> + <p> + Let us suppose we are testing a simple file logging class called + <span class="new_code">Log</span> in <em>classes/log.php</em>. + We start by creating a test script which we will call + <em>tests/log_test.php</em> and populate it as follows... +<pre> +<strong><?php +require_once('simpletest/unit_tester.php'); +require_once('simpletest/reporter.php'); +require_once('../classes/log.php'); +?></strong> +</pre> + Here the <em>simpletest</em> folder is either local or in the path. + You would have to edit these locations depending on where you + placed the toolset. + Next we create a test case... +<pre> +<?php +require_once('simpletest/unit_tester.php'); +require_once('simpletest/reporter.php'); +require_once('../classes/log.php'); +<strong> +class TestOfLogging extends UnitTestCase { +}</strong> +?> +</pre> + Now we have five lines of scaffolding code and still no tests. + However from this part on we get return on our investment very quickly. + We'll assume that the <span class="new_code">Log</span> class + takes the file name to write to in the constructor and we have + a temporary folder in which to place this file... +<pre> +<?php +require_once('simpletest/unit_tester.php'); +require_once('simpletest/reporter.php'); +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { + <strong> + function testCreatingNewFile() { + @unlink('/temp/test.log'); + $log = new Log('/temp/test.log'); + $this->assertFalse(file_exists('/temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('/temp/test.log')); + }</strong> +} +?> +</pre> + When a test case runs it will search for any method that + starts with the string <span class="new_code">test</span> + and execute that method. + We would normally have more than one test method of course. + Assertions within the test methods trigger messages to the + test framework which displays the result immediately. + This immediate response is important, not just in the event + of the code causing a crash, but also so that + <span class="new_code">print</span> statements can display + their content right next to the test case concerned. + </p> + <p> + To see these results we have to actually run the tests. + If this is the only test case we wish to run we can achieve + it with... +<pre> +<?php +require_once('simpletest/unit_tester.php'); +require_once('simpletest/reporter.php'); +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { + + function testCreatingNewFile() { + @unlink('/temp/test.log'); + $log = new Log('/temp/test.log'); + $this->assertFalse(file_exists('/temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('/temp/test.log')); + } +} +<strong> +$test = &new TestOfLogging(); +$test->run(new HtmlReporter());</strong> +?> +</pre> + </p> + <p> + On failure the display looks like this... + <div class="demo"> + <h1>testoflogging</h1> + <span class="fail">Fail</span>: testcreatingnewfile->True assertion failed.<br> + <div style="padding: 8px; margin-top: 1em; background-color: red; color: white;">1/1 test cases complete. + <strong>1</strong> passes and <strong>1</strong> fails.</div> + </div> + ...and if it passes like this... + <div class="demo"> + <h1>testoflogging</h1> + <div style="padding: 8px; margin-top: 1em; background-color: green; color: white;">1/1 test cases complete. + <strong>2</strong> passes and <strong>0</strong> fails.</div> + </div> + And if you get this... + <div class="demo"> + <b>Fatal error</b>: Failed opening required '../classes/log.php' (include_path='') in <b>/home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php</b> on line <b>7</b> + </div> + it means you're missing the <em>classes/Log.php</em> file that could look like... +<pre> +<?php +class Log { + + function Log($file_path) { + } + + function message() { + } +} +?>; +</pre> + </p> + + <p> +<a class="target" name="group"> +<h2>Building group tests</h2> +</a> +</p> + <p> + It is unlikely in a real application that we will only ever run + one test case. + This means that we need a way of grouping cases into a test + script that can, if need be, run every test in the application. + </p> + <p> + Our first step is to strip the includes and to undo our + previous hack... +<pre> +<?php<strong> +require_once('../classes/log.php');</strong> + +class TestOfLogging extends UnitTestCase { + + function testCreatingNewFile() { + @unlink('/temp/test.log'); + $log = new Log('/temp/test.log'); + $this->assertFalse(file_exists('/temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('/temp/test.log'));<strong> + } +} +?></strong> +</pre> + Next we create a new file called <em>tests/all_tests.php</em> + and insert the following code... +<pre> +<strong><?php +require_once('simpletest/unit_tester.php'); +require_once('simpletest/reporter.php'); + +$test = &new GroupTest('All tests'); +$test->addTestFile('log_test.php'); +$test->run(new HtmlReporter()); +?></strong> +</pre> + The method <span class="new_code">GroupTest::addTestFile()</span> + will include the test case file and read any new classes created + that are descended from <span class="new_code">SimpleTestCase</span>, of which + <span class="new_code">UnitTestCase</span> is one example. + Just the class names are stored for now, so that the test runner + can instantiate the class when it works its way + through your test suite. + </p> + <p> + For this to work properly the test case file should not blindly include + any other test case extensions that do not actually run tests. + This could result in extra test cases being counted during the test + run. + Hardly a major problem, but to avoid this inconvenience simply add + a <span class="new_code">SimpleTestOptions::ignore()</span> directive + somewhere in the test case file. + Also the test case file should not have been included + elsewhere or no cases will be added to this group test. + This would be a more serious error as if the test case classes are + already loaded by PHP the <span class="new_code">GroupTest::addTestFile()</span> + method will not detect them. + </p> + <p> + To display the results it is necessary only to invoke + <em>tests/all_tests.php</em> from the web server. + </p> + + <p> +<a class="target" name="mock"> +<h2>Using mock objects</h2> +</a> +</p> + <p> + Let's move further into the future. + </p> + <p> + Assume that our logging class is tested and completed. + Assume also that we are testing another class that is + required to write log messages, say a + <span class="new_code">SessionPool</span>. + We want to test a method that will probably end up looking + like this... +<pre> +<strong> +class SessionPool { + ... + function logIn($username) { + ... + $this->_log->message("User $username logged in."); + ... + } + ... +} +</strong> +</pre> + In the spirit of reuse we are using our + <span class="new_code">Log</span> class. + A conventional test case might look like this... +<pre> +<strong> +<?php +require_once('../classes/log.php'); +require_once('../classes/session_pool.php'); + +class TestOfSessionLogging extends UnitTestCase { + + function setUp() { + @unlink('/temp/test.log'); + } + + function tearDown() { + @unlink('/temp/test.log'); + } + + function testLogInIsLogged() { + $log = new Log('/temp/test.log'); + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred'); + $messages = file('/temp/test.log'); + $this->assertEqual($messages[0], "User fred logged in.\n"); + } +} +?></strong> +</pre> + This test case design is not all bad, but it could be improved. + We are spending time fiddling with log files which are + not part of our test. Worse, we have created close ties + with the <span class="new_code">Log</span> class and + this test. + What if we don't use files any more, but use ths + <em>syslog</em> library instead? + Did you notice the extra carriage return in the message? + Was that added by the logger? + What if it also added a time stamp or other data? + </p> + <p> + The only part that we really want to test is that a particular + message was sent to the logger. + We reduce coupling if we can pass in a fake logging class + that simply records the message calls for testing, but + takes no action. + It would have to look exactly like our original though. + </p> + <p> + If the fake object doesn't write to a file then we save on deleting + the file before and after each test. We could save even more + test code if the fake object would kindly run the assertion for us. + <p> + </p> + Too good to be true? + Luckily we can create such an object easily... +<pre> +<?php +require_once('../classes/log.php'); +require_once('../classes/session_pool.php');<strong> +Mock::generate('Log');</strong> + +class TestOfSessionLogging extends UnitTestCase { + + function testLogInIsLogged() {<strong> + $log = &new MockLog($this); + $log->expectOnce('message', array('User fred logged in.'));</strong> + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred');<strong> + $log->tally();</strong> + } +} +?> +</pre> + The <span class="new_code">tally()</span> call is needed to + tell the mock object that time is up for the expected call + count. + Without it the mock would wait forever for the method + call to come in without ever actually notifying the test case. + The other test will be triggered when the call to + <span class="new_code">message()</span> is invoked on the + <span class="new_code">MockLog</span> object. + The mock call will trigger a parameter comparison and then send the + resulting pass or fail event to the test display. + Wildcards can be included here too so as to prevent tests + becoming too specific. + </p> + <p> + The mock objects in the SimpleTest suite can have arbitrary + return values set, sequences of returns, return values + selected according to the incoming arguments, sequences of + parameter expectations and limits on the number of times + a method is to be invoked. + </p> + <p> + For this test to run the mock objects library must have been + included in the test suite, say in <em>all_tests.php</em>. + </p> + + <p> +<a class="target" name="web"> +<h2>Web page testing</h2> +</a> +</p> + <p> + One of the requirements of web sites is that they produce web + pages. + If you are building a project top-down and you want to fully + integrate testing along the way then you will want a way of + automatically navigating a site and examining output for + correctness. + This is the job of a web tester. + </p> + <p> + The web testing in SimpleTest is fairly primitive, there is + no JavaScript for example. + To give an idea here is a trivial example where a home + page is fetched, from which we navigate to an "about" + page and then test some client determined content. +<pre> +<?php<strong> +require_once('simpletest/web_tester.php');</strong> +require_once('simpletest/reporter.php'); +<strong> +class TestOfAbout extends WebTestCase { + + function setUp() { + $this->get('http://test-server/index.php'); + $this->clickLink('About'); + } + + function testSearchEngineOptimisations() { + $this->assertTitle('A long title about us for search engines'); + $this->assertWantedPattern('/a popular keyphrase/i'); + } +}</strong> +$test = &new TestOfAbout(); +$test->run(new HtmlReporter()); +?> +</pre> + With this code as an acceptance test you can ensure that + the content always meets the specifications of both the + developers and the other project stakeholders. + </p> + <p> + <a href="http://sourceforge.net/projects/simpletest/"><img src="http://sourceforge.net/sflogo.php?group_id=76550&type=5" width="210" height="62" border="0" alt="SourceForge.net Logo"></a> + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/mock_objects_documentation.html b/test_tools/simpletest/docs/en/mock_objects_documentation.html new file mode 100755 index 00000000..2f8a1f90 --- /dev/null +++ b/test_tools/simpletest/docs/en/mock_objects_documentation.html @@ -0,0 +1,713 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<span class="chosen">Mock objects</span> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Mock objects documentation</h1> +<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 <a href="server_stubs_documentation.html">server stubs</a>. + </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. + 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($this); + } +}</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. + As with stubs we can replace the default null return values... +<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. + As with the stubs we can set wildcards and we can overload the + wildcard parameter. + 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> + All of the patterns available with server stubs are available + to mock objects... +<pre> +class Iterator { + function Iterator() { + } + + function next() { + } +} +</pre> + Again, 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($this); + $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> + A repeat of the stubbed information holder with name/value pairs... +<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($this); +$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> + There are times when you want a specific object to be + dished out by the mock rather than a copy. + Again this is identical to the server stubs mechanism... +<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($this); +$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. + </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> +</php> + While our logging code looks like this... +<php><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($this); + $pool = &new MockSessionPool($this); + $pool->setReturnReference('findSession', $session); + $pool->expectOnce('findSession', array('abc')); + + $log = &new MockLog($this); + $log->expectOnce('message', array('Starting session abc')); + + $logging_pool = &new LoggingSessionPool($pool, $log); + $this->assertReference($logging_pool->findSession('abc'), $session); + $pool->tally(); + $log->tally();</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> + If the call is never made then neither a pass nor a failure will + generated. + To get around this we must tell the mock when the test is over + so that the object can decide if the expectation has been met. + The unit tester assertion for this is triggered by the + <span class="new_code">tally()</span> call at the end of + the test. + </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. + Finally we confirm that the + <span class="new_code">$session</span> we gave our decorator + is the one that we get back and tell the mocks to run their + internal call count tests with the + <span class="new_code">tally()</span> calls. + </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">expectArguments($method, $args)</span></td> + <td style="text-align: center">No</td> + </tr> + <tr> + <td><span class="new_code">expectArgumentsAt($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> + 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(&$test, $wildcard = '*') { + $this->BasicMockConnection($test, $wildcard); + $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> + + <p> +<a class="target" name="other_testers"> +<h2>I think SimpleTest stinks!</h2> +</a> +</p> + <p> + But at the time of writing it is the only one with mock objects, + so are you stuck with it? + </p> + <p> + No, not at all. + <a href="simple_test.html">SimpleTest</a> is a toolkit and one of those + tools is the mock objects which can be employed independently. + Suppose you have your own favourite unit tester and all your current + test cases are written using it. + Pretend that you have called your unit tester PHPUnit (everyone else has) + and the core test class looks like this... +<pre> +class PHPUnit { + function PHPUnit() { + } + + function assertion($message, $assertion) { + } + ... +} +</pre> + All the <span class="new_code">assertion()</span> method does + is print some fancy output and the boolean assertion parameter determines + whether to print a pass or a failure. + Let's say that it is used like this... +<pre> +$unit_test = new PHPUnit(); +$unit_test>assertion('I hope this file exists', file_exists('my_file')); +</pre> + How do you use mocks with this? + </p> + <p> + There is a protected method on the base mock class + <span class="new_code">SimpleMock</span> called + <span class="new_code">_assertTrue()</span> and + by overriding this method we can use our own assertion format. + We start with a subclass, in say <em>my_mock.php</em>... +<pre> +<strong><?php + require_once('simpletest/mock_objects.php'); + + class MyMock extends SimpleMock() { + function MyMock(&$test, $wildcard) { + $this->SimpleMock($test, $wildcard); + } + + function _assertTrue($assertion, $message) { + $test = &$this->getTest(); + $test->assertion($message, $assertion); + } + } +?></strong> +</pre> + Now instantiating <span class="new_code">MyMock</span> will create + an object that speaks the same language as your tester. + The catch is of course that we never create such an object, the + code generator does. + We need just one more line of code to tell the generator to use + your mock instead... +<pre> +<?php + require_once('simpletst/mock_objects.php'); + + class MyMock extends SimpleMock() { + function MyMock($test, $wildcard) { + $this->SimpleMock(&$test, $wildcard); + } + + function _assertTrue($assertion, $message , &$test) { + $test->assertion($message, $assertion); + } + }<strong> + SimpleTestOptions::setMockBaseClass('MyMock');</strong> +?> +</pre> + From now on you just include <em>my_mock.php</em> instead of the + default <em>mock_objects.php</em> version and you can introduce + mock objects into your existing test suite. + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/overview.html b/test_tools/simpletest/docs/en/overview.html new file mode 100755 index 00000000..d4965de3 --- /dev/null +++ b/test_tools/simpletest/docs/en/overview.html @@ -0,0 +1,422 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<span class="chosen">Overview</span> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Overview of SimpleTest</h1> +<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 +class <strong>MyTestCase</strong> extends UnitTestCase { + <strong> + function testLog() { + $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 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 +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->assertWantedPattern('/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 CVS directly. + A released feature is marked as "Done". + <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;">Done</td> + </tr> + <tr> + <td>Html display</td> + <td>Simplest possible display</td> + <td style="color: green;">Done</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;">Done</td> + </tr> + <tr> + <td>Mock objects code generator</td> + <td> + Objects capable of simulating other objects removing + test dependencies + </td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Server stubs</td> + <td> + Mocks without expectations to be used outside of test cases, + e.g. for prototyping + </td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Integration of other unit testers</td> + <td> + The ability to read and simulate test cases from PHPUnit + and PEAR::PhpUnit + </td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Web test case</td> + <td>Basic pattern matching of fetched pages</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>HTML parsing of pages</td> + <td>Allows link following and title tag matching</td> + <td style="color: green;">Done</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;">Done</td> + </tr> + <tr> + <td>Web cookie handling</td> + <td>Correct handling of cookies when fetching pages</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Following redirects</td> + <td>Page fetching automatically follows 300 redirects</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Form parsing</td> + <td>Ability to submit simple forms and read default form values</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Command line interface</td> + <td>Test display without the need of a web browser</td> + <td style="color: green;">Done</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;">Done</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;">Done</td> + </tr> + <tr> + <td>Command line test case</td> + <td>Allows testing of utilities and file handling</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>PHP Documentor compatibility</td> + <td>Fully generated class level documentation</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Browser interface</td> + <td> + Exposure of lower level web browser interface for more + detailed test cases + </td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>HTTP authentication</td> + <td> + Fetching protected web pages with basic authentication + only + </td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Browser navigation buttons</td> + <td>Back, forward and retry</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>SSL support</td> + <td>Can connect to https: pages</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Proxy support</td> + <td>Can connect via. common proxies</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Frames support</td> + <td>Handling of frames in web test cases</td> + <td style="color: green;">Done</td> + </tr> + <tr> + <td>Improved display</td> + <td>Better web GUI with tree display of test cases</td> + <td style="color: red;">1.1</td> + </tr> + <tr> + <td>Localisation</td> + <td>Messages abstracted and code generated from XML</td> + <td style="color: red;">1.1</td> + </tr> + <tr> + <td>File upload testing</td> + <td>Can simulate the input type file tag</td> + <td style="color: red;">1.1</td> + </tr> + <tr> + <td>Mocking interfaces</td> + <td>Can generate mock objects to interfaces as well as classes</td> + <td style="color: red;">2.0</td> + </tr> + <tr> + <td>Testing exceptions</td> + <td>Similar to testing PHP errors</td> + <td style="color: red;">2.0</td> + </tr> + <tr> + <td>XPath searching of elements</td> + <td>Can make use of HTML tidy for faster and more flexible content matching</td> + <td style="color: red;">2.0</td> + </tr> + </tbody> +</table> + PHP5 migraton will start straight after the version 1.1 series, + whereupon PHP4 will no longer be supported. + SimpleTest is currently compatible with PHP5, but will not + make use of all of the new features until version 2. + </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 the main choices + are <a href="http://phpunit.sourceforge.net/">PHPUnit</a> + and <a href="http://pear.php.net/manual/en/package.php.phpunit.php">Pear PHP::PHPUnit</a>. + They currently lack a lot of features found in + <a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a>, but the PEAR + version at least has been upgraded for PHP5 and is recommended if you are porting + existing <a href="http://www.junit.org/">JUnit</a> test cases. + </p> + <p> + Library writers don't seem to ship tests with their code very often + which is a shame. + Library code that includes tests can be more safely refactored and + the test code can act as additional documentation in a fairly standard + form. + This can save trawling the source code for clues when problems occour, + especially when upgrading such a library. + Libraries using SimpleTest for their unit testing include + <a href="http://wact.sourceforge.net/">WACT</a> and + <a href="http://sourceforge.net/projects/htmlsax">PEAR::XML_HTMLSax</a>. + </p> + <p> + There is currently a sad lack of material on 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. + 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/wiki/MocksObjectsPaper">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 most 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. + As SimpleTest does not support JavaScript you would probably + have to look at these tools anyway if you have highly dynamic + pages. + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/partial_mocks_documentation.html b/test_tools/simpletest/docs/en/partial_mocks_documentation.html new file mode 100755 index 00000000..20749415 --- /dev/null +++ b/test_tools/simpletest/docs/en/partial_mocks_documentation.html @@ -0,0 +1,426 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<span class="chosen">Partial mocks</span> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Partial mock objects documentation</h1> +<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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/reporter_documentation.html b/test_tools/simpletest/docs/en/reporter_documentation.html new file mode 100755 index 00000000..44be8b1e --- /dev/null +++ b/test_tools/simpletest/docs/en/reporter_documentation.html @@ -0,0 +1,515 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<span class="chosen">Reporting</span> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Test reporter documentation</h1> +<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 GroupTest('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 GroupTest('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 GroupTest('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 GroupTest('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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/server_stubs_documentation.html b/test_tools/simpletest/docs/en/server_stubs_documentation.html new file mode 100755 index 00000000..4b18bb0b --- /dev/null +++ b/test_tools/simpletest/docs/en/server_stubs_documentation.html @@ -0,0 +1,388 @@ +<html> +<head> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>SimpleTest for PHP server stubs documentation</title> +<link rel="stylesheet" type="text/css" href="docs.css" title="Styles"> +</head> +<body> +<div class="menu_back"> +<div class="menu"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<span class="chosen">Server stubs</span> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Server stubs documentation</h1> +<div class="content"> + <p> +<a class="target" name="what"> +<h2>What are server stubs?</h2> +</a> +</p> + <p> + This was originally a pattern named by Robert Binder (Testing + object-oriented systems: models, patterns, and tools, + Addison-Wesley) in 1999. + 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> +<a class="target" name="creation"> +<h2>Creating server stubs</h2> +</a> +</p> + <p> + 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 stub version of the class we need to include the + server stub library and run the generator... +<pre> +<strong>require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); +Stub::generate('DatabaseConnection');</strong> +</pre> + This generates a clone class called + <span class="new_code">StubDatabaseConnection</span>. + We can now create instances of the new class within + our prototype script... +<pre> +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); +Stub::generate('DatabaseConnection'); +<strong> +$connection = new StubDatabaseConnection(); +</strong> +</pre> + The stub 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> +<a class="target" name="patterns"> +<h2>Simulation patterns</h2> +</a> +</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> +<strong>Stub::generate('Iterator'); + +$iterator = new StubIterator(); +$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 + stub 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 stub 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 stubs have a filter system... +<pre> +<strong>Stub::generate('Configuration'); + +$config = &new StubConfiguration(); +$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 server stub 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 stub rather than just a copy. + The PHP 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 stub's + return list... +<pre> +Stub::generate('Vector'); + +$thing = new Thing();<strong> +$vector = &new StubVector(); +$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. + </p> + <p> + These three factors, timing, parameters and whether to copy, + can be combined orthogonally. + For example... +<pre> +$complex = &new StubComplexThing(); +$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 stubbed using the mechanics above. + </p> + <p> + Here's how... +<pre> +Stub::generate('DatabaseConnection'); +Stub::generate('ResultIterator'); + +class DatabaseTest extends UnitTestCase { + + function testUserFinder() {<strong> + $result = &new StubResultIterator(); + $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 StubDatabaseConnection(); + $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="options"> +<h2>Stub creation options</h2> +</a> +</p> + <p> + There are some additional options when creating stubs. + At the generation stage we can change the class name... +<pre> +<strong>Stub::generate('Iterator', 'MyStubIterator'); +$iterator = &new MyStubIterator(); +</strong> +</pre> + This is not very useful in itself as there would be no difference + in this class and the default except for the name. + However we can also add additional methods not found in the + original interface... +<pre> +class Iterator { +} +<strong>Stub::generate('Iterator', 'PrototypeIterator', array('next', 'isError')); +$iterator = &new PrototypeIterator(); +$iterator->setReturnValue('next', 0); +</strong> +</pre> + The <span class="new_code">next()</span> and + <span class="new_code">isError()</span> methods can now have + return values set just as if they existed in the original class. + </p> + <p> + One other esoteric way of customising the stubs is to change + the default wildcard used for parameter matching. +<pre> +<strong>Stub::generate('Connection'); +$iterator = &new StubConnection('wild'); +$iterator->setReturnValue('query', array('id' => 33), array('wild')); +</strong> +</pre> + The only reason to do this is if you genuinely wanted to test + against the literal string "*" and didn't want it + interpreted as "any". + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/unit_test_documentation.html b/test_tools/simpletest/docs/en/unit_test_documentation.html new file mode 100755 index 00000000..6aa8d8a7 --- /dev/null +++ b/test_tools/simpletest/docs/en/unit_test_documentation.html @@ -0,0 +1,387 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<span class="chosen">Unit tester</span> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>PHP Unit Test documentation</h1> +<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> + If no test name is supplied when chaining the constructor then + the class name will be taken instead. + This will be the name displayed in the test results. + </p> + <p> + 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. + For example... +<pre> +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">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">assertCopy($x, $y)</span></td><td>Fail if $x and $y are the same variable</td> +</tr> + <tr> +<td><span class="new_code">assertWantedPattern($p, $x)</span></td><td>Fail unless the regex $p matches $x</td> +</tr> + <tr> +<td><span class="new_code">assertNoUnwantedPattern($p, $x)</span></td><td>Fail if the regex $p matches $x</td> +</tr> + <tr> +<td><span class="new_code">assertNoErrors()</span></td><td>Fail if any PHP error occoured</td> +</tr> + <tr> +<td><span class="new_code">assertError($x)</span></td><td>Fail if no PHP error or incorrect message</td> +</tr> + <tr> +<td><span class="new_code">assertErrorPattern($p)</span></td><td>Fail unless the error matches the regex $p</td> +</tr> + </tbody> +</table> + All assertion methods can take an optional description 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> +<strong>$variable = null; +$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. + This also allows us to nest test messages. +<pre> +<strong>$a = 1; +$b = $a; +$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->assertWantedPattern('/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>trigger_error('Disaster'); +trigger_error('Catastrophe'); +$this->assertError(); +$this->assertError('Catastrophe'); +$this->assertNoErrors();</strong> +</pre> + This one takes some explanation as in fact they all pass! + </p> + <p> + PHP errors in SimpleTest are trapped and placed in a queue. + Here the first error check catches the "Disaster" + message without checking the text and passes. + This removes the error from the queue. + The next error check tests not only the existence of the error, + but also the text which here matches so another pass. + With the queue now empty the last test will pass as well. + If any unchecked errors are left at the end of a test method then + an exception will be reported in the test. + 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">sendMessage()</span></td><td>Sends a status message to those displays that support it</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> + <tr> +<td><span class="new_code">swallowErrors()</span></td><td>Clears the error queue</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 too so as to extend framework... +<pre> +require_once('simpletest/unit_tester.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> + 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 <span class="new_code">assertTrue()</span>, + 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. + Here is the scaffolding needed to run the a lone test case... +<pre> +<?php + require_once('simpletest/unit_tester.php');<strong> + require_once('simpletest/reporter.php');</strong> + require_once('../classes/writer.php'); + + class FileTestCase extends UnitTestCase { + function FileTestCase() { + $this->UnitTestCase('File test'); + } + }<strong> + + $test = &new FileTestCase(); + $test->run(new HtmlReporter());</strong> +?> +</pre> + This script will run as is, but will output zero passes + and zero failures until test methods are added. + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> diff --git a/test_tools/simpletest/docs/en/web_tester_documentation.html b/test_tools/simpletest/docs/en/web_tester_documentation.html new file mode 100755 index 00000000..51f604be --- /dev/null +++ b/test_tools/simpletest/docs/en/web_tester_documentation.html @@ -0,0 +1,508 @@ +<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"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<span class="chosen">Web tester</span> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Web tester documentation</h1> +<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<strong> + require_once('simpletest/web_tester.php');</strong> + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('Web site tests');<strong> + $test->addTestFile('lastcraft_test.php');</strong> + exit ($test->run(new TextReporter()) ? 0 : 1); +?> +</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->assertWantedPattern('/why the last craft/i');</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">assertWantedPattern($pattern)</span></td><td>A Perl pattern match against the page content</td> +</tr> + <tr> +<td><span class="new_code">assertNoUnwantedPattern($pattern)</span></td><td>A Perl pattern match to not find content</td> +</tr> + <tr> +<td><span class="new_code">assertWantedText($text)</span></td><td>Pass if matches visible and "alt" text</td> +</tr> + <tr> +<td><span class="new_code">assertNoUnwantedText($text)</span></td><td>Pass if doesn't match visible and "alt" text</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">assertNoUnwantedHeader($header)</span></td><td>Pass if a header was not fetched</td> +</tr> + <tr> +<td><span class="new_code">assertHeaderPattern($header, $pattern)</span></td><td>Pass if a header was fetched matching this Perl regex</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> + 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('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> should be used + with the button title... +<pre> +<strong>$this->clickSubmit('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> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> |