From b2e97539e7af7712b04dd5c2610a454d09aa0333 Mon Sep 17 00:00:00 2001 From: wei <> Date: Fri, 7 Jul 2006 23:18:19 +0000 Subject: Update simpletest --- .../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
  • -Server stubs -
  • -
  • Mock objects
  • @@ -76,7 +73,17 @@

    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.
                 

    +

    + 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); } }

    @@ -426,15 +540,6 @@ class LoggingSessionPoolTest extends UnitTestCase { 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 @@ -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. -

    -