From 322bb9cd2be9e51422cb2b82684692e825c2bfb7 Mon Sep 17 00:00:00 2001 From: brettp Date: Fri, 2 Oct 2009 18:40:04 +0000 Subject: Added simpletest and start of unit tests. git-svn-id: http://code.elgg.org/elgg/trunk@3503 36083f99-b078-4883-b0ff-0f9b5a30f544 --- vendors/simpletest/docs/en/index.html | 538 ++++++++++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100755 vendors/simpletest/docs/en/index.html (limited to 'vendors/simpletest/docs/en/index.html') diff --git a/vendors/simpletest/docs/en/index.html b/vendors/simpletest/docs/en/index.html new file mode 100755 index 000000000..03b6c5cef --- /dev/null +++ b/vendors/simpletest/docs/en/index.html @@ -0,0 +1,538 @@ + + + + + Download the Simple Test testing framework - + Unit tests and mock objects for PHP + + + + + +

Simple Test for PHP

+ This page... + +
+ + +

+ 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. +

+ +

Using the tester quickly

+

+ 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, and includes + mock objects. +

+

+ What makes this tool immediately useful to the PHP developer is the internal + web browser. + This allows tests that navigate web sites, fill in forms and test pages. + Being able to write these test in PHP means that it is easy to write + integrated tests. + An example might be confirming that a user was written to a database + after a signing up through the web site. +

+

+ The quickest way to demonstrate SimpleTest 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... +

+<?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+
+class TestOfLogging extends UnitTestCase {
+}
+?>
+
+ Here the simpletest folder is either local or in the path. + You would have to edit these locations depending on where you + unpacked the toolset. + The "autorun.php" file does more than just include the + SimpleTest files, it also runs our test for us. +

+

+ The TestOfLogging is our first test case and it's + currently empty. + Each test case is a class that extends one of the SimpleTet base classes + and we can have as many of these in the file as we want. +

+

+ With three lines of scaffolding, and our Log class + include, we have a test suite. + No tests though. +

+

+ For our first test, 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/autorun.php');
+require_once('../classes/log.php');
+
+class TestOfLogging extends UnitTestCase {
+    function testLogCreatesNewFileOnFirstMessage() {
+        @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. + If the method starts "test", it's a test. + Note the very long name testLogCreatesNewFileOnFirstMessage(). + This is considered good style and makes the test output more readable. +

+

+ We would normally have more than one test method in a test case, + but that's for later. +

+

+ 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 debugging content right next to the assertion concerned. +

+

+ To see these results we have to actually run the tests. + No other code is necessary - we can just open the page + with our browser. +

+

+ On failure the display looks like this... +

+

TestOfLogging

+ Fail: testLogCreatesNewFileOnFirstMessage->True assertion failed.
+
1/1 test cases complete. + 1 passes and 1 fails.
+
+ ...and if it passes like this... +
+

TestOfLogging

+
1/1 test cases complete. + 2 passes and 0 fails.
+
+ And if you get this... +
+ 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... +
+<?php
+class Log {
+    function Log($file_path) {
+    }
+
+    function message() {
+    }
+}
+?>
+
+ It's fun to write the code after the test. + More than fun even - + this system is called "Test Driven Development". +

+

+ For more information about UnitTestCase, see + the unit test documentation. +

+ +

Building test suites

+

+ It is unlikely in a real application that we will only ever run + one test case. + This means that we need a way of grouping cases into a test + script that can, if need be, run every test for the application. +

+

+ Our first step is to create a new file called tests/all_tests.php + and insert the following code... +

+<?php
+require_once('simpletest/autorun.php');
+
+class AllTests extends TestSuite {
+    function AllTests() {
+        $this->TestSuite('All tests');
+        $this->addFile('log_test.php');
+    }
+}
+?>
+
+ The "autorun" include allows our upcoming test suite + to be run just by invoking this script. +

+

+ The TestSuite subclass must chain it's constructor. + This limitation will be removed in future versions. +

+

+ The method TestSuite::addFile() + will include the test case file and read any new classes + that are descended from SimpleTestCase. + UnitTestCase is just one example of a class derived from + SimpleTestCase, and you can create your own. + TestSuite::addFile() can include other test suites. +

+

+ The class will not be instantiated yet. + When the test suite runs it will construct each instance once + it reaches that test, then destroy it straight after. + This means that the constructor is run just before each run + of that test case, and the destructor is run before the next test case starts. +

+

+ It is common to group test case code into superclasses which are not + supposed to run, but become the base classes of other tests. + For "autorun" to work properly the test case file should not blindly run + any other test case extensions that do not actually run tests. + This could result in extra test cases being counted during the test + run. + Hardly a major problem, but to avoid this inconvenience simply mark your + base class as abstract. + SimpleTest won't run abstract classes. + If you are still using PHP4, then + a SimpleTestOptions::ignore() directive + somewhere in the test case file will have the same effect. +

+

+ 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 TestSuite::addFile() + method will not detect them. +

+

+ To display the results it is necessary only to invoke + tests/all_tests.php from the web server or the command line. +

+

+ For more information about building test suites, + see the test suite documentation. +

+ +

Using mock objects

+

+ Let's move further into the future and do something really complicated. +

+

+ 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... +


+class SessionPool {
+    ...
+    function logIn($username) {
+        ...
+        $this->_log->message("User $username logged in.");
+        ...
+    }
+    ...
+}
+
+
+ In the spirit of reuse, we are using our + Log class. + A conventional test case might look like this... +
+<?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+require_once('../classes/session_pool.php');
+
+class TestOfSessionLogging extends UnitTestCase {
+    
+    function setUp() {
+        @unlink('/temp/test.log');
+    }
+    
+    function tearDown() {
+        @unlink('/temp/test.log');
+    }
+    
+    function testLoggingInIsLogged() {
+        $log = new Log('/temp/test.log');
+        $session_pool = &new SessionPool($log);
+        $session_pool->logIn('fred');
+        $messages = file('/temp/test.log');
+        $this->assertEqual($messages[0], "User fred logged in.\n");
+    }
+}
+?>
+
+ We'll explain the setUp() and tearDown() + methods later. +

+

+ This test case design is not all bad, but it could be improved. + We are spending time fiddling with log files which are + not part of our test. + We have created close ties with the Log class and + this test. + What if we don't use files any more, but use ths + syslog library instead? + It means that our TestOfSessionLogging test will + fail, even thouh it's not testing Logging. +

+

+ It's fragile in smaller ways too. + Did you notice the extra carriage return in the message? + Was that added by the logger? + What if it also added a time stamp or other data? +

+

+ The only part that we really want to test is that a particular + message was sent to the logger. + We can reduce coupling if we pass in a fake logging class + that simply records the message calls for testing, but + takes no action. + It would have to look exactly like our original though. +

+

+ 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? + We can create such an object easily... +
+<?php
+require_once('simpletest/autorun.php');
+require_once('../classes/log.php');
+require_once('../classes/session_pool.php');
+
+Mock::generate('Log');
+
+class TestOfSessionLogging extends UnitTestCase {
+    
+    function testLoggingInIsLogged() {
+        $log = &new MockLog();
+        $log->expectOnce('message', array('User fred logged in.'));
+        $session_pool = &new SessionPool($log);
+        $session_pool->logIn('fred');
+    }
+}
+?>
+
+ The Mock::generate() call code generated a new class + called MockLog. + This looks like an identical clone, except that we can wire test code + to it. + That's what expectOnce() does. + It says that if message() is ever called on me, it had + better be with the parameter "User fred logged in.". +

+

+ The test will be triggered when the call to + message() is invoked on the + MockLog object by SessionPool::logIn() code. + The mock call will trigger a parameter comparison and then send the + resulting pass or fail event to the test display. + Wildcards can be included here too, so you don't have to test every parameter of + a call when you only want to test one. +

+

+ If the mock reaches the end of the test case without the + method being called, the expectOnce() + expectation will trigger a test failure. + In other words the mocks can detect the absence of + behaviour as well as the presence. +

+

+ 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 more information about mocking and stubbing, see the + mock objects documentation. +

+ +

Web page testing

+

+ 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, as there is + no JavaScript. + Most other browser operations are simulated. +

+

+ 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/autorun.php');
+require_once('simpletest/web_tester.php');
+
+class TestOfAbout extends WebTestCase {
+    function testOurAboutPageGivesFreeReignToOurEgo() {
+        $this->get('http://test-server/index.php');
+        $this->click('About');
+        $this->assertTitle('About why we are so great');
+        $this->assertText('We are really great');
+    }
+}
+?>
+
+ 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. +

+

+ You can navigate forms too... +

+<?php
+require_once('simpletest/autorun.php');
+require_once('simpletest/web_tester.php');
+
+class TestOfRankings extends WebTestCase {
+    function testWeAreTopOfGoogle() {
+        $this->get('http://google.com/');
+        $this->setField('q', 'simpletest');
+        $this->click("I'm Feeling Lucky");
+        $this->assertTitle('SimpleTest - Unit Testing for PHP');
+    }
+}
+?>
+
+ ...although this could violate Google's(tm) terms and conditions. +

+

+ For more information about web testing, see the + scriptable + browser documentation and the + WebTestCase. +

+

+ SourceForge.net Logo +

+ +
+ References and related information... + + + + + -- cgit v1.2.3