+ 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.
+
+ 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...
+
1/1 test cases complete.
+ 0 passes, 0 fails and 0 exceptions.
+
+ 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...
+
+ Any one of these tests would normally do on it's own depending
+ on the amount of detail you want to see.
+
+
+ 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...
+
+ 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.
+
+
+ You can shortcut this step further by encoding the log in
+ details straight into the URL...
+
+ 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.
+
+
+ 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.
+
+ 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.
+
+ 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...
+
+ 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.
+
+
+ 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...
+
+ 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...
+
+ The WebTestCase::restart() 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.
+
+
+ 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.
+
+
+ To push the cookies over the hour limit you can age them
+ before you restart the session...
+
+ SimpleTest's web browser component can be used not just
+ outside of the WebTestCase class, but also
+ independently of the SimpleTest framework itself.
+
+ 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 PHP web site...
+
+ There are simpler methods to do this particular example in PHP
+ of course.
+ For example you can just use the PHP file()
+ 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.
+
+
+ All of the navigation methods used in the
+ WebTestCase
+ are present in the SimpleBrowser class, but
+ the assertions are replaced with simpler accessors.
+ Here is a full list of the page navigation methods...
+
+
+
+
addHeader($header)
Adds a header to every fetch
+
+
+
useProxy($proxy, $username, $password)
Use this proxy from now on
+
+
+
head($url, $parameters)
Perform a HEAD request
+
+
+
get($url, $parameters)
Fetch a page with GET
+
+
+
post($url, $parameters)
Fetch a page with POST
+
+
+
clickLink($label)
Follows a link by label
+
+
+
isLink($label)
See if a link is present by label
+
+
+
clickLinkById($id)
Follows a link by attribute
+
+
+
isLinkById($id)
See if a link is present by attribut
+
+
+
getUrl()
Current URL of page or frame
+
+
+
getTitle()
Page title
+
+
+
getContent()
Raw page or frame
+
+
+
getContentAsText()
HTML removed except for alt text
+
+
+
retry()
Repeat the last request
+
+
+
back()
Use the browser back button
+
+
+
forward()
Use the browser forward button
+
+
+
authenticate($username, $password)
Retry page or frame after a 401 response
+
+
+
restart($date)
Restarts the browser for a new session
+
+
+
ageCookies($interval)
Ages the cookies by the specified time
+
+
+
setCookie($name, $value)
Sets an additional cookie
+
+
+
getCookieValue($host, $path, $name)
Reads the most specific cookie
+
+
+
getCurrentCookieValue($name)
Reads cookie for the current context
+
+
+
+ The methods SimpleBrowser::useProxy() and
+ SimpleBrowser::addHeader() are special.
+ Once called they continue to apply to all subsequent fetches.
+
+
+ At the moment there aren't any methods to list available forms
+ and fields.
+ This will probably be added to later versions of SimpleTest.
+
+
+ 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.
+
+
+
+
getFrames()
A dump of the current frame structure
+
+
+
getFrameFocus()
Current frame label or index
+
+
+
setFrameFocusByIndex($choice)
Select a frame numbered from 1
+
+
+
setFrameFocus($name)
Select frame by label
+
+
+
clearFrameFocus()
Treat all the frames as a single page
+
+
+
+ When focused on a single frame, the content will come from
+ that frame only.
+ This includes links to click and forms to submit.
+
+
+
+ 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...
+
+
+
+
setConnectionTimeout($timeout)
Close the socket on overrun
+
+
+
getRequest()
Raw request header of page or frame
+
+
+
getHeaders()
Raw response header of page or frame
+
+
+
getTransportError()
Any socket level errors in the last fetch
+
+
+
getResponseCode()
HTTP response of page or frame
+
+
+
getMimeType()
Mime type of page or frame
+
+
+
getAuthentication()
Authentication type in 401 challenge header
+
+
+
getRealm()
Authentication realm in 401 challenge header
+
+
+
setMaximumRedirects($max)
Number of redirects before page is loaded anyway
+
+
+
setMaximumNestedFrames($max)
Protection against recursive framesets
+
+
+
ignoreFrames()
Disables frames support
+
+
+
useFrames()
Enables frames support
+
+
+
+ The methods SimpleBrowser::setConnectionTimeout()
+ SimpleBrowser::setMaximumRedirects(),
+ SimpleBrowser::setMaximumNestedFrames(),
+ SimpleBrowser::ignoreFrames() and
+ SimpleBrowser::useFrames() 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 SimpleBrowser::setFrameFocus()
+ and you can then use SimpleBrowser::getRequest(), etc to
+ see what happened.
+
+
+
+ Anything that could be done in a
+ WebTestCase can
+ now be done in a UnitTestCase.
+ This means that we can freely mix domain object testing with the
+ web interface...
+
+ 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.
+
+
+ A more useful case of where using the browser directly can be helpful
+ is where the WebTestCase cannot cope.
+ An example is where two browsers are needed at the same time.
+
+
+ For example, say we want to disallow multiple simultaneous
+ usage of a site with the same username.
+ This test case will do the job...
+
+ The default behaviour of the
+ mock objects
+ in
+ SimpleTest
+ 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.
+
+
+ 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.
+
+
+ For example, suppose we have a news service that has failed
+ to connect to its remote source.
+
+class NewsService {
+ ...
+ function publish(&$writer) {
+ if (! $this->isConnected()) {
+ $writer->write('Cannot connect to news service "' .
+ $this->_name . '" at this time. ' .
+ 'Please try again later.');
+ }
+ ...
+ }
+}
+
+ Here it is sending its content to a
+ Writer class.
+ We could test this behaviour with a
+ MockWriter like so...
+
+class TestOfNewsService extends UnitTestCase {
+ ...
+ function testConnectionFailure() {
+ $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();
+ }
+}
+
+ 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.
+
+
+ To get around this, we would like to do a regular expression
+ test rather than an exact match.
+ We can actually do this with...
+
+ Instead of passing in the expected parameter to the
+ MockWriter we pass an
+ expectation class called
+ WantedPatternExpectation.
+ 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.
+
+
+ The WantedPatternExpectation takes
+ the regular expression to match in its constructor.
+ Whenever a comparison is made by the MockWriter
+ against this expectation class, it will do a
+ preg_match() 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.
+
+
+ The possible expectation classes are...
+
+
+
+
EqualExpectation
An equality, rather than the stronger identity comparison
+
+
+
NotEqualExpectation
An inequality comparison
+
+
+
IndenticalExpectation
The default mock object check which must match exactly
+
+
+
NotIndenticalExpectation
Inverts the mock object logic
+
+
+
WantedPatternExpectation
Uses a Perl Regex to match a string
+
+
+
NoUnwantedPatternExpectation
Passes only if failing a Perl Regex
+
+
+
IsAExpectation
Checks the type or class name only
+
+
+
NotAExpectation
Opposite of the IsAExpectation
+
+
+
MethodExistsExpectation
Checks a method is available on an object
+
+
+
+ Most take the expected value in the constructor.
+ The exceptions are the pattern matchers, which take a regular expression,
+ and the IsAExpectation and NotAExpectation which takes a type
+ or class name as a string.
+
+
+
+ The expectation classes can be used not just for sending assertions
+ from mock objects, but also for selecting behaviour for either
+ the
+ mock objects
+ or the
+ server stubs.
+ Anywhere a list of arguments is given, a list of expectation objects
+ can be inserted instead.
+
+
+ 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...
+
+Stub::generate('Authorisation');
+
+$authorisation = new StubAuthorisation();
+$authorisation->setReturnValue(
+ 'isAllowed',
+ true,
+ array(new IsAExpectation('Session', 'Must be a session')));
+$authorisation->setReturnValue('isAllowed', false);
+
+ We have set the default stub behaviour to return false when
+ isAllowed is called.
+ When we call the method with a single parameter that
+ is a Session 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.
+
+
+ This kind of sophistication is rarely useful, but is included for
+ completeness.
+
+ The expectation classes have a very simple structure.
+ So simple that it is easy to create your own versions for
+ commonly used test logic.
+
+
+ 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
+ SimpleExpectation...
+
+class ValidIp extends SimpleExpectation {
+
+ function test($ip) {
+ return (ip2long($ip) != -1);
+ }
+
+ function testMessage($ip) {
+ return "Address [$ip] should be a valid IP address";
+ }
+}
+
+ There are only two methods to implement.
+ The test() method should
+ evaluate to true if the expectation is to pass, and
+ false otherwise.
+ The testMessage() method
+ should simply return some helpful text explaining the test
+ that was carried out.
+
+
+ This class can now be used in place of the earlier expectation
+ classes.
+
+ The SimpleTest unit testing framework
+ also uses the expectation classes internally for the
+ UnitTestCase class.
+ We can also take advantage of these mechanisms to reuse our
+ homebrew expectation classes within the test suites directly.
+
+
+ The most crude way of doing this is to use the
+ SimpleTest::assertExpectation() method to
+ test against it directly...
+
+ This is a little untidy compared with our usual
+ assert...() syntax.
+
+
+ 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...
+
+ 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.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+
+
diff --git a/tests/test_tools/simpletest/docs/en/form_testing_documentation.html b/tests/test_tools/simpletest/docs/en/form_testing_documentation.html
new file mode 100755
index 00000000..b1e15b3d
--- /dev/null
+++ b/tests/test_tools/simpletest/docs/en/form_testing_documentation.html
@@ -0,0 +1,277 @@
+
+
+
+Simple Test documentation for testing HTML forms
+
+
+
+
+ When a page is fetched by the WebTestCase
+ using get() or
+ post() 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...
+
+ We can navigate to this code, via the
+ LastCraft
+ site, with the following test...
+
+class SimpleFormTests extends WebTestCase {
+
+ function testDefaultValue() {
+ $this->get('http://www.lastcraft.com/form_testing_documentation.php');
+ $this->assertField('a', 'A default');
+ }
+}
+
+ 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"
+
+
+ We could submit the form straight away, but first we'll change
+ the value of the text field and only then submit it...
+
+ 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 GET
+ 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
+ HTMLTidy should be used.
+
+
+ If a field is not present in any form, or if an option is unavailable,
+ then WebTestCase::setField() will return
+ false.
+ For example, suppose we wish to verify that a "Superuser"
+ option is not present in this form...
+
+<strong>Select type of user to add:</strong>
+<select name="type">
+ <option>Subscriber</option>
+ <option>Author</option>
+ <option>Administrator</option>
+</select>
+
+ The selection will not be changed on a failure to set
+ a widget value.
+
+
+ Here is the full list of widgets currently supported...
+
+
Text fields, including hidden and password fields.
+
Submit buttons including the button tag, although not yet reset buttons
+
Text area. This includes text wrapping behaviour.
+
Checkboxes, including multiple checkboxes in the same form.
+
Drop down selections, including multiple selects.
+
Radio buttons.
+
Images.
+
+
+
+ Although most standard HTML widgets are catered for by SimpleTest's
+ built in parser, it is unlikely that JavaScript will be implemented
+ anytime soon.
+
+ 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...
+
+ 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.
+
+
+ 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.
+
+ 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.
+
+
+ If you have extended any test cases, you can include them
+ as well.
+
+ The FileTester class does
+ not contain any actual tests, but is a base class for other
+ test cases.
+ For this reason we use the
+ SimpleTestOptions::ignore() 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 file_test.php.
+
+
+ Next we create a group test file, called say group_test.php.
+ You will think of a better name I am sure.
+ We will add the test file using a safe method...
+
+ 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...
+
+ The problem with this method is that for every test case
+ that we add we will have
+ to require_once() the test code
+ file and manually instantiate each and every test case.
+ We can save a lot of typing with...
+
+ What happens here is that the GroupTest
+ class has done the require_once()
+ 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.
+
+
+ There are two things that could go wrong and which require care...
+
+
+ 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.
+
+
+ New test case extension classes that get included will be
+ placed in the group test and run also.
+ You will need to add a SimpleTestOptions::ignore()
+ directive for these classes or make sure that they are included
+ before the GroupTest::addTestFile()
+ line.
+
+ 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.
+
+
+ To get a more flexible group test we can subclass
+ GroupTest and then instantiate it as needed...
+
+ 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...
+
+ 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.
+
+ 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 run() will run once only
+ from the top level script that has been invoked.
+
+ 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.
+
+
+ Say we have the following
+ PhpUnit
+ test case in the file config_test.php...
+
+ 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.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+
+
diff --git a/tests/test_tools/simpletest/docs/en/index.html b/tests/test_tools/simpletest/docs/en/index.html
new file mode 100755
index 00000000..04797272
--- /dev/null
+++ b/tests/test_tools/simpletest/docs/en/index.html
@@ -0,0 +1,467 @@
+
+
+
+
+ Download the Simple Test testing framework -
+ Unit tests and mock objects for PHP
+
+
+
+
+
+ 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
+ SimpleTest.
+ For fuller documentation, especially if you are new
+ to unit testing see the ongoing
+ documentation, and for
+ example test cases see the
+ unit testing tutorial.
+
+ 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
+ JUnit and the
+ PHPUnit
+ ports, but also adds
+ mock objects.
+ It has some JWebUnit
+ functionality as well.
+ This includes web page navigation, cookie testing and form submission.
+
+
+ The quickest way to demonstrate is with an example.
+
+
+ Let us suppose we are testing a simple file logging class called
+ Log in classes/log.php.
+ We start by creating a test script which we will call
+ tests/log_test.php and populate it as follows...
+
+ Here the simpletest 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...
+
+ 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 Log class
+ takes the file name to write to in the constructor and we have
+ a temporary folder in which to place this file...
+
+<?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'));
+ }
+}
+?>
+
+ When a test case runs it will search for any method that
+ starts with the string test
+ 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
+ print statements can display
+ their content right next to the test case concerned.
+
+
+ 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...
+
+<?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'));
+ }
+}
+
+$test = &new TestOfLogging();
+$test->run(new HtmlReporter());
+?>
+
+ Fatal error: Failed opening required '../classes/log.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php on line 7
+
+ it means you're missing the classes/Log.php file that could look like...
+
+ 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.
+
+
+ Our first step is to strip the includes and to undo our
+ previous hack...
+
+<?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'));
+ }
+}
+?>
+
+ Next we create a new file called tests/all_tests.php
+ and insert the following code...
+
+ The method GroupTest::addTestFile()
+ will include the test case file and read any new classes created
+ that are descended from SimpleTestCase, of which
+ UnitTestCase 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.
+
+
+ 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 SimpleTestOptions::ignore() 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 GroupTest::addTestFile()
+ method will not detect them.
+
+
+ To display the results it is necessary only to invoke
+ tests/all_tests.php from the web server.
+
+ 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
+ SessionPool.
+ We want to test a method that will probably end up looking
+ like this...
+
+ 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 Log class and
+ this test.
+ What if we don't use files any more, but use ths
+ syslog 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?
+
+
+ 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.
+
+
+ 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.
+
+
+ Too good to be true?
+ Luckily we can create such an object easily...
+
+ The tally() 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
+ message() is invoked on the
+ MockLog 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.
+
+
+ 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.
+
+
+ For this test to run the mock objects library must have been
+ included in the test suite, say in all_tests.php.
+
+ 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.
+
+
+ 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.
+
+<?php
+require_once('simpletest/web_tester.php');
+require_once('simpletest/reporter.php');
+
+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');
+ }
+}
+$test = &new TestOfAbout();
+$test->run(new HtmlReporter());
+?>
+
+ 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.
+
+
+
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+ Mock objects have two roles during a test case: actor and critic.
+
+
+ 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.
+
+
+ If mock objects only behaved as actors they would simply be
+ known as server stubs.
+
+
+ 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.
+
+ In the same way that we create server stubs, all we need is an
+ existing class, say a database connection that looks like this...
+
+class DatabaseConnection {
+ function DatabaseConnection() {
+ }
+
+ function query() {
+ }
+
+ function selectQuery() {
+ }
+}
+
+ 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...
+
+ 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.
+
+
+ The mock version of a class has all the methods of the original
+ so that operations like
+ $connection->query() are still
+ legal.
+ As with stubs we can replace the default null return values...
+
+$connection->setReturnValue('query', 37);
+
+ Now every time we call
+ $connection->query() 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...
+
+ Here the mock will behave as if the setOptions()
+ existed in the original class.
+ This is handy if a class has used the PHP overload()
+ mechanism to add dynamic methods.
+ You can create a special mock to simulate this situation.
+
+
+ All of the patterns available with server stubs are available
+ to mock objects...
+
+class Iterator {
+ function Iterator() {
+ }
+
+ function next() {
+ }
+}
+
+ Again, assuming that this iterator only returns text until it
+ reaches the end, when it returns false, we can simulate it
+ with...
+
+ When next() 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 false 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.
+
+
+ A repeat of the stubbed information holder with name/value pairs...
+
+class Configuration {
+ function Configuration() {
+ }
+
+ function getValue($key) {
+ }
+}
+
+ 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
+ getValue() method and yet
+ we want different results for different keys.
+ Luckily the mocks have a filter system...
+
+ 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
+ getValue() method invoked
+ like this...
+
+$config->getValue('db_user')
+
+ ...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.
+
+
+ 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...
+
+ 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.
+
+
+ By way of example, suppose we have a
+ SessionPool 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 SessionPool code currently looks
+ like this...
+
+ Out of all of this, the only class we want to test here
+ is the LoggingSessionPool.
+ In particular we would like to check that the
+ findSession() method is
+ called with the correct session ID in the cookie and that
+ it sent the message "Starting session $cookie"
+ to the logger.
+
+
+ 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:
+
+
Create a log object.
+
Set a directory to place the log file.
+
Set the directory permissions so we can write the log.
+
Create a SessionPool object.
+
Hand start a session, which probably does lot's of things.
+
Invoke findSession().
+
Read the new Session ID (hope there is an accessor!).
+
Raise a test assertion to confirm that the ID matches the cookie.
+
Read the last line of the log file.
+
Pattern match out the extra logging timestamps, etc.
+
Assert that the session message is contained in the text.
+
+ 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.
+
+
+ Instead, here is the complete test method using mock object magic...
+
+ 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.
+
+
+ findSession() is a factory
+ method the simulation of which is described above.
+ The point of departure comes with the first
+ expectOnce() call.
+ This line states that whenever
+ findSession() 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.
+
+
+ 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
+ tally() call at the end of
+ the test.
+
+
+ We use the same pattern to set up the mock logger.
+ We tell it that it should have
+ message() 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.
+
+
+ We start to run our tests when we create the new
+ LoggingSessionPool and feed
+ it our preset mock objects.
+ Everything is now under our control.
+ Finally we confirm that the
+ $session we gave our decorator
+ is the one that we get back and tell the mocks to run their
+ internal call count tests with the
+ tally() calls.
+
+
+ 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
+ setUp() method.
+
+
+ Here is the full list of expectations you can set on a mock object
+ in SimpleTest...
+
+
+
+
Expectation
Needs tally()
+
+
+
+
+
expectArguments($method, $args)
+
No
+
+
+
expectArgumentsAt($timing, $method, $args)
+
No
+
+
+
expectCallCount($method, $count)
+
Yes
+
+
+
expectMaximumCallCount($method, $count)
+
No
+
+
+
expectMinimumCallCount($method, $count)
+
Yes
+
+
+
expectNever($method)
+
No
+
+
+
expectOnce($method, $args)
+
Yes
+
+
+
expectAtLeastOnce($method, $args)
+
Yes
+
+
+
+ Where the parameters are...
+
+
$method
+
The method name, as a string, to apply the condition to.
+
$args
+
+ The arguments as a list. Wildcards can be included in the same
+ manner as for setReturn().
+ This argument is optional for expectOnce()
+ and expectAtLeastOnce().
+
+
$timing
+
+ The only point in time to test the condition.
+ The first call starts at zero.
+
+
$count
+
The number of calls expected.
+
+ The method expectMaximumCallCount()
+ is slightly different in that it will only ever generate a failure.
+ It is silent if the limit is never reached.
+
+
+ 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".
+
+ 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.
+
+
+ Mock objects generated with SimpleTest
+ are dynamic.
+ They are created at run time in memory, using
+ eval(), 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.
+
+
+ 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.
+
+
+ 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 mocks/connection.php for a
+ database connection, create a mock and inherit to override
+ special methods or add presets...
+
+ The generate call tells the class generator to create
+ a class called BasicMockConnection
+ rather than the usual MockConnection.
+ We then inherit from this to get our version of
+ MockConnection.
+ By intercepting in this way we can add behaviour, here setting
+ the default value of query() 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 MockConnection 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.
+
+
+ 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.
+
+ But at the time of writing it is the only one with mock objects,
+ so are you stuck with it?
+
+
+ No, not at all.
+ SimpleTest 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...
+
+ All the assertion() 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...
+
+$unit_test = new PHPUnit();
+$unit_test>assertion('I hope this file exists', file_exists('my_file'));
+
+ How do you use mocks with this?
+
+
+ There is a protected method on the base mock class
+ SimpleMock called
+ _assertTrue() and
+ by overriding this method we can use our own assertion format.
+ We start with a subclass, in say my_mock.php...
+
+ Now instantiating MyMock 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...
+
+ From now on you just include my_mock.php instead of the
+ default mock_objects.php version and you can introduce
+ mock objects into your existing test suite.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+
+
diff --git a/tests/test_tools/simpletest/docs/en/overview.html b/tests/test_tools/simpletest/docs/en/overview.html
new file mode 100755
index 00000000..d4965de3
--- /dev/null
+++ b/tests/test_tools/simpletest/docs/en/overview.html
@@ -0,0 +1,422 @@
+
+
+
+
+ Overview and feature list for the SimpleTest PHP unit tester and web tester
+
+
+
+
+
+ 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 run()
+ 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
+ assertEqual().
+ 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.
+
+ 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.
+
+
+ The simplest type of test case is the
+ UnitTestCase.
+ 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.
+
+
+ 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 WebTestCase 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.
+
+ 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".
+
+
+
+
Feature
Description
Release
+
+
+
+
+
Unit test case
+
Core test case class and assertions
+
Done
+
+
+
Html display
+
Simplest possible display
+
Done
+
+
+
Autoloading of test cases
+
+ Reading a file with test cases and loading them into a
+ group test automatically
+
+
Done
+
+
+
Mock objects code generator
+
+ Objects capable of simulating other objects removing
+ test dependencies
+
+
Done
+
+
+
Server stubs
+
+ Mocks without expectations to be used outside of test cases,
+ e.g. for prototyping
+
+
Done
+
+
+
Integration of other unit testers
+
+ The ability to read and simulate test cases from PHPUnit
+ and PEAR::PhpUnit
+
+
Done
+
+
+
Web test case
+
Basic pattern matching of fetched pages
+
Done
+
+
+
HTML parsing of pages
+
Allows link following and title tag matching
+
Done
+
+
+
Partial mocks
+
+ Mocking parts of a class for testing less than a class
+ or for complex simulations
+
+
Done
+
+
+
Web cookie handling
+
Correct handling of cookies when fetching pages
+
Done
+
+
+
Following redirects
+
Page fetching automatically follows 300 redirects
+
Done
+
+
+
Form parsing
+
Ability to submit simple forms and read default form values
+
Done
+
+
+
Command line interface
+
Test display without the need of a web browser
+
Done
+
+
+
Exposure of expectation classes
+
Can create precise tests with mocks as well as test cases
+
Done
+
+
+
XML output and parsing
+
+ Allows multi host testing and the integration of acceptance
+ testing extensions
+
+
Done
+
+
+
Command line test case
+
Allows testing of utilities and file handling
+
Done
+
+
+
PHP Documentor compatibility
+
Fully generated class level documentation
+
Done
+
+
+
Browser interface
+
+ Exposure of lower level web browser interface for more
+ detailed test cases
+
+
Done
+
+
+
HTTP authentication
+
+ Fetching protected web pages with basic authentication
+ only
+
+
Done
+
+
+
Browser navigation buttons
+
Back, forward and retry
+
Done
+
+
+
SSL support
+
Can connect to https: pages
+
Done
+
+
+
Proxy support
+
Can connect via. common proxies
+
Done
+
+
+
Frames support
+
Handling of frames in web test cases
+
Done
+
+
+
Improved display
+
Better web GUI with tree display of test cases
+
1.1
+
+
+
Localisation
+
Messages abstracted and code generated from XML
+
1.1
+
+
+
File upload testing
+
Can simulate the input type file tag
+
1.1
+
+
+
Mocking interfaces
+
Can generate mock objects to interfaces as well as classes
+
2.0
+
+
+
Testing exceptions
+
Similar to testing PHP errors
+
2.0
+
+
+
XPath searching of elements
+
Can make use of HTML tidy for faster and more flexible content matching
+
2.0
+
+
+
+ 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.
+
+
+
+ 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
+ Extreme Programming.
+ This is one of the
+ Agile Methodologies
+ which combine various practices to "flatten the cost curve" of software development.
+ More extreme still is Test Driven Development,
+ 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
+ RUP approach.
+ I haven't tried it, but even I can see that you will need test tools (see figure 9).
+
+
+ Most unit testers clone JUnit to some degree,
+ as far as the interface at least. There is a wealth of information on the
+ JUnit site including the
+ FAQ
+ which contains plenty of general advice on testing.
+ Once you get bitten by the bug you will certainly appreciate the phrase
+ test infected
+ coined by Eric Gamma.
+ If you are still reviewing which unit tester to use the main choices
+ are PHPUnit
+ and Pear PHP::PHPUnit.
+ They currently lack a lot of features found in
+ SimpleTest, but the PEAR
+ version at least has been upgraded for PHP5 and is recommended if you are porting
+ existing JUnit test cases.
+
+
+ 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
+ WACT and
+ PEAR::XML_HTMLSax.
+
+
+ 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 original mock objects paper
+ 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
+ Extreme Tuesday
+ or www.mockobjects.com
+ or the original C2 Wiki.
+ Injecting mocks into a class is the main area of debate for which this
+ paper on IBM
+ makes a good starting point.
+
+
+ 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
+ HTTPUnit,
+ HTMLUnit
+ or JWebUnit 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.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+ 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.
+
+ 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.
+
+
+ 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...
+
+ We would really like to have a mock object version of the socket
+ here, what can we do?
+
+
+ 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.
+
+ 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.
+
+
+ The next simplest compromise is to have the created object passed
+ in as an optional parameter...
+
+ 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.
+
+
+ The next method is to pass in a factory object to do the creation...
+
+ 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...
+
+ 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.
+
+
+ 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...
+
+ 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
+ connect().
+ Otherwise it could get a null value from
+ _createSocket().
+
+
+ After the completion of all of this extra work the
+ actual test case is fairly easy.
+ We just test our new class instead...
+
+ 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?
+
+
+
+ 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.
+
+
+ Here is the partial mock version of the test...
+
+ The partial mock is a subclass of the original with
+ selected methods "knocked out" with test
+ versions.
+ The generatePartial() call
+ takes three parameters: the class to be subclassed,
+ the new test class name and a list of methods to mock.
+
+
+ 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.
+
+
+ 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.
+
+
+ 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...
+
+ 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.
+
+
+ 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.
+
+
+ It's all going to come down to the coding standards of your
+ project to decide which mechanism you use.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+
+
diff --git a/tests/test_tools/simpletest/docs/en/reporter_documentation.html b/tests/test_tools/simpletest/docs/en/reporter_documentation.html
new file mode 100755
index 00000000..44be8b1e
--- /dev/null
+++ b/tests/test_tools/simpletest/docs/en/reporter_documentation.html
@@ -0,0 +1,515 @@
+
+
+
+SimpleTest for PHP test runner and display documentation
+
+
+
+
+ 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.
+
+ 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...
+
+
File test
+ Fail: createnewfile->True assertion failed.
+
1/1 test cases complete.
+ 0 passes, 1 fails and 0 exceptions.
+
+ And here all tests passed...
+
+
File test
+
1/1 test cases complete.
+ 1 passes, 0 fails and 0 exceptions.
+
+ The good news is that there are several points in the display
+ hiearchy for subclassing.
+
+
+ For web page based displays there is the
+ HtmlReporter class with the following
+ signature...
+
+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() { ... }
+}
+
+ Here is what some of these methods mean. First the display methods
+ that you will probably want to override...
+
+
+ HtmlReporter(string $encoding)
+
+ 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 html_entities()
+ function.
+
+
+ void paintHeader(string $test_name)
+
+ 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 $test_name
+ comes from.
+ It paints the page titles, CSS, body tag, etc.
+ It returns nothing (void).
+
+
+ void paintFooter(string $test_name)
+
+ 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.
+
+
+ void paintMethodStart(string $test_name)
+
+ 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.
+
+
+ void paintMethodEnd(string $test_name)
+
+ backs out of the test started with the same name.
+
+
+ void paintFail(string $message)
+
+ 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.
+
+
+ void paintPass(string $message)
+
+ by default does nothing.
+
+
+ string _getCss()
+
+ 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.
+
+
+ There are also some accessors to get information on the current
+ state of the test suite.
+ Use these to enrich the display...
+
+
+ array getTestList()
+
+ 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.
+
+
+ integer getPassCount()
+
+ returns the number of passes chalked up so far.
+ Needed for the display at the end.
+
+
+ integer getFailCount()
+
+ is likewise the number of fails so far.
+
+
+ integer getExceptionCount()
+
+ is likewise the number of errors so far.
+
+
+ integer getTestCaseCount()
+
+ is the total number of test cases in the test run.
+ This includes the grouping tests themselves.
+
+
+ integer getTestCaseProgress()
+
+ is the number of test cases completed so far.
+
+
+ One simple modification is to get the HtmlReporter to display
+ the passes as well as the failures and errors...
+
+ One method that was glossed over was the makeDry()
+ 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.
+
+
+ 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.
+
+ 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
+ HtmlReporter we can take one
+ step up the class hiearchy to SimpleReporter
+ in the simple_test.php source file.
+
+
+ A do nothing display, a blank canvas for your own creation, would
+ be...
+
+ 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...
+
+File test
+1) True assertion failed.
+ in createnewfile
+FAILURES!!!
+Test cases run: 1/1, Failures: 1, Exceptions: 0
+
+
+
+ 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 false
+ is returned from the SimpleTest::run()
+ method.
+ We can use that result to exit the script with the desired return
+ code...
+
+ 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...
+
+ You can make use of this format with the parser
+ supplied as part of SimpleTest itself.
+ This is called SimpleTestXmlParser and
+ resides in xml.php within the SimpleTest package...
+
+ The $test_output 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.
+
+
+ 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.
+
+
+ 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...
+
+ The RemoteTestCase 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 RemoteTestCase can be added to test suites
+ just like any other group test.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+
+
+
diff --git a/tests/test_tools/simpletest/docs/en/server_stubs_documentation.html b/tests/test_tools/simpletest/docs/en/server_stubs_documentation.html
new file mode 100755
index 00000000..4b18bb0b
--- /dev/null
+++ b/tests/test_tools/simpletest/docs/en/server_stubs_documentation.html
@@ -0,0 +1,388 @@
+
+
+
+SimpleTest for PHP server stubs documentation
+
+
+
+
+ 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.
+
+ All we need is an existing class, say a database connection
+ that looks like this...
+
+class DatabaseConnection {
+ function DatabaseConnection() {
+ }
+
+ function query() {
+ }
+
+ function selectQuery() {
+ }
+}
+
+ 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...
+
+ This generates a clone class called
+ StubDatabaseConnection.
+ We can now create instances of the new class within
+ our prototype script...
+
+require_once('simpletest/mock_objects.php');
+require_once('database_connection.php');
+Stub::generate('DatabaseConnection');
+
+$connection = new StubDatabaseConnection();
+
+
+ The stub version of a class has all the methods of the original
+ so that operations like
+ $connection->query() are still
+ legal.
+ The return value will be null,
+ but we can change that with...
+
+$connection->setReturnValue('query', 37)
+
+ Now every time we call
+ $connection->query() 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.
+
+
+
+ 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...
+
+class Iterator {
+ function Iterator() {
+ }
+
+ function next() {
+ }
+}
+
+ 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...
+
+ When next() 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 false 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.
+
+
+ Another tricky situation is an overloaded
+ get() operation.
+ An example of this is an information holder with name/value pairs.
+ Say we have a configuration class like...
+
+class Configuration {
+ function Configuration() {
+ }
+
+ function getValue($key) {
+ }
+}
+
+ 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
+ getValue() method and yet
+ we want different results for different keys.
+ Luckily the stubs have a filter system...
+
+ 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
+ getValue() method invoked
+ like this...
+
+$config->getValue('db_user');
+
+ ...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.
+
+
+ You can set a default argument argument like so...
+
+ This is not the same as setting the return value without
+ any argument requirements like this...
+
+
+$config->setReturnValue('getValue', false);
+
+ 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.
+
+
+ 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...
+
+ This will return the $stuff 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.
+
+
+ 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 next() giving
+ one row until false.
+ This sounds like a simulation nightmare, but in fact it can all
+ be stubbed using the mechanics above.
+
+ Now only if our
+ $connection is called with the correct
+ query() will the
+ $result be returned that is
+ itself exhausted after the third call to next().
+ This should be enough
+ information for our UserFinder class,
+ the class actually
+ being tested here, to come up with goods.
+ A very precise test and not a real database in sight.
+
+
+
+ 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...
+
+ The core system is a regression testing framework built around
+ test cases.
+ A sample test case looks like this...
+
+class FileTestCase extends UnitTestCase {
+}
+
+ 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.
+
+
+ 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...
+
+ The constructor is optional and usually omitted.
+ Without a name, the class name is taken as the name of the test case.
+
+
+ Our only test method at the moment is testCreation()
+ where we check that a file has been created by our
+ Writer object.
+ We could have put the unlink()
+ code into this method as well, but by placing it in
+ setUp() and
+ tearDown() we can use it with
+ other test methods that we add.
+
+
+ The setUp() method is run
+ just before each and every test method.
+ tearDown() is run just after
+ each and every test method.
+
+
+ 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 setUp(), but
+ supplies additional hooks for library writers.
+
+
+ The means of reporting test results (see below) are by a
+ visiting display class
+ that is notified by various assert...()
+ methods.
+ Here is the full list for the UnitTestCase
+ class, the default for SimpleTest...
+
+
+
+
assertTrue($x)
Fail if $x is false
+
+
+
assertFalse($x)
Fail if $x is true
+
+
+
assertNull($x)
Fail if $x is set
+
+
+
assertNotNull($x)
Fail if $x not set
+
+
+
assertIsA($x, $t)
Fail if $x is not the class or type $t
+
+
+
assertNotA($x, $t)
Fail if $x is of the class or type $t
+
+
+
assertEqual($x, $y)
Fail if $x == $y is false
+
+
+
assertNotEqual($x, $y)
Fail if $x == $y is true
+
+
+
assertIdentical($x, $y)
Fail if $x == $y is false or a type mismatch
+
+
+
assertNotIdentical($x, $y)
Fail if $x == $y is true and types match
+
+
+
assertReference($x, $y)
Fail unless $x and $y are the same variable
+
+
+
assertCopy($x, $y)
Fail if $x and $y are the same variable
+
+
+
assertWantedPattern($p, $x)
Fail unless the regex $p matches $x
+
+
+
assertNoUnwantedPattern($p, $x)
Fail if the regex $p matches $x
+
+
+
assertNoErrors()
Fail if any PHP error occoured
+
+
+
assertError($x)
Fail if no PHP error or incorrect message
+
+
+
assertErrorPattern($p)
Fail unless the error matches the regex $p
+
+
+
+ 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.
+
+
+ Some examples...
+
+$variable = null;
+$this->assertNull($variable, 'Should be cleared');
+
+ ...will pass and normally show no message.
+ If you have
+ set up the tester to display passes
+ as well then the message will be displayed as is.
+
+$this->assertIdentical(0, false, 'Zero is not false [%s]');
+
+ 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.
+
+ This one takes some explanation as in fact they all pass!
+
+
+ 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.
+
+
+ The test cases also have some convenience methods for debugging
+ code or extending the suite...
+
+
+
+
setUp()
Runs this before each test method
+
+
+
tearDown()
Runs this after each test method
+
+
+
pass()
Sends a test pass
+
+
+
fail()
Sends a test failure
+
+
+
error()
Sends an exception event
+
+
+
sendMessage()
Sends a status message to those displays that support it
+
+
+
signal($type, $payload)
Sends a user defined message to the test reporter
+
+
+
dump($var)
Does a formatted print_r() for quick and dirty debugging
+ If you want a test case that does not have all of the
+ UnitTestCase assertions,
+ only your own and assertTrue(),
+ you need to extend the SimpleTestCase
+ class instead.
+ It is found in simple_test.php rather than
+ unit_tester.php.
+ See later if you
+ want to incorporate other unit tester's
+ test cases in your test suites.
+
+ 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...
+
+ 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.
+
+
+ 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.
+
+
+ SimpleTest includes a special form of test case for the testing
+ of web page actions.
+ The WebTestCase includes facilities
+ for navigation, content and cookie checks and form handling.
+ Usage of these test cases is similar to the
+ UnitTestCase...
+
+class TestOfLastcraft extends WebTestCase {
+}
+
+ Here we are about to test the
+ Last Craft site itself.
+ If this test case is in a file called lastcraft_test.php
+ then it can be loaded in a runner script just like unit tests...
+
+ The get() 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 get() method will still return true.
+
+
+ Assuming that the web server for the Last Craft site is up
+ (sadly not always the case), we should see...
+
+ To confirm that the page we think we are on is actually the
+ page we are on, we need to verify the page content.
+
+class TestOfLastcraft extends WebTestCase {
+
+ function testHomepage() {
+ $this->get('http://www.lastcraft.com/');
+ $this->assertWantedPattern('/why the last craft/i');
+ }
+}
+
+ 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.
+
+
+ Here is the list of possible content assertions...
+
+
+
+
assertTitle($title)
Pass if title is an exact match
+
+
+
assertWantedPattern($pattern)
A Perl pattern match against the page content
+
+
+
assertNoUnwantedPattern($pattern)
A Perl pattern match to not find content
+
+
+
assertWantedText($text)
Pass if matches visible and "alt" text
+
+
+
assertNoUnwantedText($text)
Pass if doesn't match visible and "alt" text
+
+
+
assertLink($label)
Pass if a link with this text is present
+
+
+
assertNoLink($label)
Pass if no link with this text is present
+
+
+
assertLinkById($id)
Pass if a link with this id attribute is present
+
+
+
assertNoLinkById($id)
Pass if no link with this id attribute is present
+
+
+
assertField($name, $value)
Pass if an input tag with this name has this value
+
+
+
assertFieldById($id, $value)
Pass if an input tag with this id has this value
+
+
+
assertResponse($codes)
Pass if HTTP response matches this list
+
+
+
assertMime($types)
Pass if MIME type is in this list
+
+
+
assertAuthentication($protocol)
Pass if the current challenge is this protocol
+
+
+
assertNoAuthentication()
Pass if there is no current challenge
+
+
+
assertRealm($name)
Pass if the current challenge realm matches
+
+
+
assertHeader($header, $content)
Pass if a header was fetched matching this value
+
+
+
assertNoUnwantedHeader($header)
Pass if a header was not fetched
+
+
+
assertHeaderPattern($header, $pattern)
Pass if a header was fetched matching this Perl regex
+
+
+
assertCookie($name, $value)
Pass if there is currently a matching cookie
+
+
+
assertNoCookie($name)
Pass if there is currently no cookie of this name
+
+
+
+ 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.
+
+
+ So now we could instead test against the title tag with...
+
+$this->assertTitle('The Last Craft? Web developer tutorials on PHP, Extreme programming and Object Oriented development');
+
+ As well as the simple HTML content checks we can check
+ that the MIME type is in a list of allowed types with...
+
+ 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...
+
+ 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 WebTestCase 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...
+
+ 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...
+
+ If the target is a button rather than an anchor tag, then
+ clickSubmit() should be used
+ with the button title...
+
+$this->clickSubmit('Go!');
+
+
+
+ The list of navigation methods is...
+
+
+
+
getUrl()
The current location
+
+
+
get($url, $parameters)
Send a GET request with these parameters
+
+
+
post($url, $parameters)
Send a POST request with these parameters
+
+
+
head($url, $parameters)
Send a HEAD request without replacing the page content
+
+
+
retry()
Reload the last request
+
+
+
back()
Like the browser back button
+
+
+
forward()
Like the browser forward button
+
+
+
authenticate($name, $password)
Retry after a challenge
+
+
+
restart()
Restarts the browser as if a new session
+
+
+
getCookie($name)
Gets the cookie value for the current context
+
+
+
ageCookies($interval)
Ages current cookies prior to a restart
+
+
+
clearFrameFocus()
Go back to treating all frames as one page
+
+
+
clickSubmit($label)
Click the first button with this label
+
+
+
clickSubmitByName($name)
Click the button with this name attribute
+
+
+
clickSubmitById($id)
Click the button with this ID attribute
+
+
+
clickImage($label, $x, $y)
Click an input tag of type image by title or alt text
+
+
+
clickImageByName($name, $x, $y)
Click an input tag of type image by name
+
+
+
clickImageById($id, $x, $y)
Click an input tag of type image by ID attribute
+
+
+
submitFormById($id)
Submit a form without the submit value
+
+
+
clickLink($label, $index)
Click an anchor by the visible label text
+
+
+
clickLinkById($id)
Click an anchor by the ID attribute
+
+
+
getFrameFocus()
The name of the currently selected frame
+
+
+
setFrameFocusByIndex($choice)
Focus on a frame counting from 1
+
+
+
setFrameFocus($name)
Focus on a frame by name
+
+
+
+
+
+ The parameters in the get(), post() or
+ head() 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.
+
+
+ The retry(), back() and
+ forward() 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.
+
+
+ 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.
+
+
+ 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
+ tools for testing web forms
+ as well.
+
+ 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...
+
+
+
+
getTransportError()
The last socket error
+
+
+
showRequest()
Dump the outgoing request
+
+
+
showHeaders()
Dump the incoming headers
+
+
+
showSource()
Dump the raw HTML page content
+
+
+
ignoreFrames()
Do not load framesets
+
+
+
setCookie($name, $value)
Set a cookie from now on
+
+
+
addHeader($header)
Always add this header to the request
+
+
+
setMaximumRedirects($max)
Stop after this many redirects
+
+
+
setConnectionTimeout($timeout)
Kill the connection after this time between bytes
+
+
+
useProxy($proxy, $name, $password)
Make requests via this proxy URL
+
+
+
+ These methods are principally for debugging.
+
+
+
+
+ Copyright Marcus Baker, Jason Sweat, Perrick Penet 2004
+