From af68030fcf0c266300feb2c100149ecadef7d364 Mon Sep 17 00:00:00 2001 From: xue <> Date: Sun, 16 Jul 2006 01:50:23 +0000 Subject: Merge from 3.0 branch till 1264. --- .../docs/en/mock_objects_documentation.html | 272 +++++++++++---------- 1 file changed, 143 insertions(+), 129 deletions(-) (limited to 'tests/test_tools/simpletest/docs/en/mock_objects_documentation.html') diff --git a/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html b/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html index 2f8a1f90..eb32c619 100755 --- a/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html +++ b/tests/test_tools/simpletest/docs/en/mock_objects_documentation.html @@ -21,9 +21,6 @@ Group tests
If mock objects only behaved as actors they would simply be - known as server stubs. + known as server stubs. + This was originally a pattern named by Robert Binder (Testing + object-oriented systems: models, patterns, and tools, + Addison-Wesley) in 1999. +
++ 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.
However, the mock objects not only play a part (by supplying chosen @@ -87,6 +94,8 @@ 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. @@ -138,7 +147,7 @@ Mock::generate('DatabaseConnection'); class MyTestCase extends UnitTestCase { function testSomething() { - $connection = &new MockDatabaseConnection($this); + $connection = &new MockDatabaseConnection(); } } @@ -155,19 +164,27 @@ class MyTestCase extends UnitTestCase {
- The mock version of a class has all the methods of the original + 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... + The return value will be null, + but we can change that with...
-$connection->setReturnValue('query', 37); +$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 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. + +
We can also add extra methods to the mock when generating it and choose our own class name...
@@ -180,8 +197,12 @@ class MyTestCase extends UnitTestCase { You can create a special mock to simulate this situation.- All of the patterns available with server stubs are available - to mock objects... + 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() { @@ -191,7 +212,8 @@ class Iterator { } }- Again, assuming that this iterator only returns text until it + 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...@@ -200,7 +222,7 @@ Mock::generate('Iterator'); class IteratorTest extends UnitTestCase() { function testASequence() { - $iterator = &new MockIterator($this); + $iterator = &new MockIterator(); $iterator->setReturnValue('next', false); $iterator->setReturnValueAt(0, 'next', 'First string'); $iterator->setReturnValueAt(1, 'next', 'Second string'); @@ -218,7 +240,10 @@ class IteratorTest extends UnitTestCase() { The constant one is a kind of default if you like.- A repeat of the stubbed information holder with name/value pairs... + 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() { @@ -237,7 +262,7 @@ class Configuration { we want different results for different keys. Luckily the mocks have a filter system...-$config = &new MockConfiguration($this); +$config = &new MockConfiguration(); $config->setReturnValue('getValue', 'primary', array('db_host')); $config->setReturnValue('getValue', 'admin', array('db_user')); $config->setReturnValue('getValue', 'secret', array('db_password')); @@ -257,10 +282,36 @@ $config->getValue('db_user') to its list of returns one after another until a complete match is found. +@@ -426,15 +540,6 @@ class LoggingSessionPoolTest extends UnitTestCase { You can have wildcards and sequences and the order of evaluation is the same. -+ You can set a default argument argument like so... +
+ +$config->setReturnValue('getValue', false, array('*')); ++ 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 mock rather than a copy. - Again this is identical to the server stubs mechanism... + The PHP4 copy semantics force us to use a different method + for this. + You might be simulating a container for example...
class Thing { } @@ -276,14 +327,79 @@ class Vector { In this case you can set a reference into the mock's return list...} }-$thing = new Thing(); -$vector = &new MockVector($this); +$thing = &new Thing(); +$vector = &new MockVector(); $vector->setReturnReference('get', $thing, array(12));With this arrangement you know that every time $vector->get(12) is called it will return the same $thing each time. + This is compatible with PHP5 as well. + ++ These three factors, timing, parameters and whether to copy, + can be combined orthogonally. + For example... +
+$complex = &new MockComplexThing(); +$stuff = &new Stuff(); +$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1)); ++ 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 mocked using the mechanics above. +
++ Here's how... +
+Mock::generate('DatabaseConnection'); +Mock::generate('ResultIterator'); + +class DatabaseTest extends UnitTestCase { + + function testUserFinder() { + $result = &new MockResultIterator(); + $result->setReturnValue('next', false); + $result->setReturnValueAt(0, 'next', array(1, 'tom')); + $result->setReturnValueAt(1, 'next', array(3, 'dick')); + $result->setReturnValueAt(2, 'next', array(6, 'harry')); + + $connection = &new MockDatabaseConnection(); + $connection->setReturnValue('query', false); + $connection->setReturnReference( + 'query', + $result, + array('select id, name from users')); + + $finder = &new UserFinder($connection); + $this->assertIdentical( + $finder->findNames(), + array('tom', 'dick', 'harry')); + } +} ++ 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.@@ -388,18 +504,16 @@ Mock::generate('Log'); class LoggingSessionPoolTest extends UnitTestCase { ... function testFindSessionLogging() { - $session = &new MockSession($this); - $pool = &new MockSessionPool($this); + $session = &new MockSession(); + $pool = &new MockSessionPool(); $pool->setReturnReference('findSession', $session); $pool->expectOnce('findSession', array('abc')); - $log = &new MockLog($this); + $log = &new MockLog(); $log->expectOnce('message', array('Starting session abc')); $logging_pool = &new LoggingSessionPool($pool, $log); - $this->assertReference($logging_pool->findSession('abc'), $session); - $pool->tally(); - $log->tally(); + $this->assertReference($logging_pool->findSession('abc'), $session);
- 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 @@ -448,11 +553,6 @@ class LoggingSessionPoolTest extends UnitTestCase { 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 @@ -477,11 +577,11 @@ class LoggingSessionPoolTest extends UnitTestCase {
- expectArguments($method, $args) +expect($method, $args) No - expectArgumentsAt($timing, $method, $args) +expectAt($timing, $method, $args) No @@ -589,8 +689,8 @@ class LoggingSessionPoolTest extends UnitTestCase { Mock::generate('Connection', 'BasicMockConnection'); class MockConnection extends BasicMockConnection { - function MockConnection(&$test, $wildcard = '*') { - $this->BasicMockConnection($test, $wildcard); + function MockConnection() { + $this->BasicMockConnection(); $this->setReturn('query', false); } } @@ -619,92 +719,6 @@ class LoggingSessionPoolTest extends UnitTestCase { stages of testing. - - -
-I think SimpleTest stinks!
- -- 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... -
-class PHPUnit { - function PHPUnit() { - } - - function assertion($message, $assertion) { - } - ... -} -- 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... -
-<?php - require_once('simpletest/mock_objects.php'); - - class MyMock extends SimpleMock() { - function MyMock(&$test, $wildcard) { - $this->SimpleMock($test, $wildcard); - } - - function _assertTrue($assertion, $message) { - $test = &$this->getTest(); - $test->assertion($message, $assertion); - } - } -?> -- 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... --<?php - require_once('simpletst/mock_objects.php'); - - class MyMock extends SimpleMock() { - function MyMock($test, $wildcard) { - $this->SimpleMock(&$test, $wildcard); - } - - function _assertTrue($assertion, $message , &$test) { - $test->assertion($message, $assertion); - } - } - SimpleTestOptions::setMockBaseClass('MyMock'); -?> -- 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 -- cgit v1.2.3