- 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... -
-<?php - require_once('simpletest/web_tester.php'); - require_once('simpletest/reporter.php'); - - $test = &new GroupTest('Web site tests'); - $test->addTestFile('lastcraft_test.php'); - exit ($test->run(new TextReporter()) ? 0 : 1); -?> -- I am using the text reporter here to more clearly - distinguish the web content from the test output. - -
- Nothing is being tested yet. - We can fetch the home page by using the - get() method... -
-class TestOfLastcraft extends WebTestCase { - - function testHomepage() { - $this->assertTrue($this->get('http://www.lastcraft.com/')); - } -} -- 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... -
-Web site tests -OK -Test cases run: 1/1, Failures: 0, Exceptions: 0 -- All we have really checked is that any kind of page was - returned. - We don't yet know if it was the right one. - - - -
- 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 | -
- 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... -
-$this->assertMime(array('text/plain', 'text/html')); -- 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... -
-class TestOfLastcraft extends WebTestCase { - - function testRedirects() { - $this->get('http://www.lastcraft.com/test/redirect.php'); - $this->assertResponse(200);</strong> - } -} -- 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... -
-class TestOfLastcraft extends WebTestCase { - - function testHomepage() { - $this->setMaximumRedirects(0); - $this->get('http://www.lastcraft.com/test/redirect.php'); - $this->assertResponse(200); - } -} -- The assertion now fails as expected... -
-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 -- We can modify the test to correctly assert redirects with... -
-class TestOfLastcraft extends WebTestCase { - - function testHomepage() { - $this->setMaximumRedirects(0); - $this->get('http://www.lastcraft.com/test/redirect.php'); - $this->assertResponse(array(301, 302, 303, 307)); - } -} -- This now passes. - - - -
- 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... -
-class TestOfLastcraft extends WebTestCase { - ... - function testContact() { - $this->get('http://www.lastcraft.com/'); - $this->clickLink('About'); - $this->assertTitle('About Last Craft'); - } -} -- The parameter is the text of the link. - -
- 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 | -