From 61bb16ee2e5f0a66234e1575242169a10fde47b5 Mon Sep 17 00:00:00 2001 From: xue <> Date: Fri, 7 Jul 2006 14:54:15 +0000 Subject: Merge from 3.0 branch till 1253. --- .../docs/en/partial_mocks_documentation.html | 426 --------------------- 1 file changed, 426 deletions(-) delete mode 100644 tests/UnitTests/simpletest/docs/en/partial_mocks_documentation.html (limited to 'tests/UnitTests/simpletest/docs/en/partial_mocks_documentation.html') diff --git a/tests/UnitTests/simpletest/docs/en/partial_mocks_documentation.html b/tests/UnitTests/simpletest/docs/en/partial_mocks_documentation.html deleted file mode 100644 index f4a6af00..00000000 --- a/tests/UnitTests/simpletest/docs/en/partial_mocks_documentation.html +++ /dev/null @@ -1,426 +0,0 @@ - - - -SimpleTest for PHP partial mocks documentation - - - - -

Partial mock objects documentation

-
- -

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

- -

- -

The mock injection problem

- -

-

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

-<?php
-    require_once('socket.php');
-    
-    class Telnet {
-        ...
-        function &connect($ip, $port, $username, $password) {
-            $socket = &new Socket($ip, $port);
-            $socket->read( ... );
-            ...
-        }
-    }
-?>
-
- 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. -

-

- Here this would be... -

-<?php
-    require_once('socket.php');
-    
-    class Telnet {
-        ...
-        function &connect(&$socket, $username, $password) {
-            $socket->read( ... );
-            ...
-        }
-    }
-?>
-
- This means that the test code is typical for a test involving - mock objects. -
-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $telnet = &new Telnet();
-        $telnet->connect($socket, 'Me', 'Secret');
-        ...
-    }
-}
-
- 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... -

-<?php
-    require_once('socket.php');
-    
-    class Telnet {
-        ...
-        function &connect($ip, $port, $username, $password, $socket = false) {
-            if (!$socket) {
-                $socket = &new Socket($ip, $port);
-            }
-            $socket->read( ... );
-            ...
-            return $socket;
-        }
-    }
-?>
-
- For a quick solution this is usually good enough. - The test now looks almost the same as if the parameter - was formally passed... -
-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $telnet = &new Telnet();
-        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket);
-        ...
-    }
-}
-
- 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... -

-<?php
-    require_once('socket.php');
-    
-    class Telnet {
-        function Telnet(&$network) {
-            $this->_network = &$network;
-        }
-        ...
-        function &connect($ip, $port, $username, $password) {
-            $socket = &$this->_network->createSocket($ip, $port);
-            $socket->read( ... );
-            ...
-            return $socket;
-        }
-    }
-?>
-
- 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... -
-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $network = &new MockNetwork($this);
-        $network->setReturnReference('createSocket', $socket);
-        $telnet = &new Telnet($network);
-        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
-        ...
-    }
-}
-
- 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. -

-

- Is there a middle ground? -

- -

- -

Protected factory method

- -

-

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

-<?php
-    require_once('socket.php');
-    
-    class Telnet {
-        ...
-        function &connect($ip, $port, $username, $password) {
-            $socket = &$this->_createSocket($ip, $port);
-            $socket->read( ... );
-            ...
-        }
-        
-        function &_createSocket($ip, $port) {
-            return new Socket($ip, $port);
-        }
-    }
-?>
-
- This is the only change we make to the application code. -

-

- For the test case we have to create a subclass so that - we can intercept the socket creation... -

-class TelnetTestVersion extends Telnet {
-    var $_mock;
-    
-    function TelnetTestVersion(&$mock) {
-        $this->_mock = &$mock;
-        $this->Telnet();
-    }
-    
-    function &_createSocket() {
-        return $this->_mock;
-    }
-}
-
- 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... -

-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $telnet = &new TelnetTestVersion($socket);
-        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
-        ...
-    }
-}
-
- 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? -

- -

- -

A partial mock

- -

-

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

-Mock::generatePartial(
-        'Telnet',
-        'TelnetTestVersion',
-        array('_createSocket'));
-
-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $telnet = &new TelnetTestVersion($this);
-        $telnet->setReturnReference('_createSocket', $socket);
-        $telnet->Telnet();
-        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
-        ...
-    }
-}
-
- 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... -

-class TelnetTest extends UnitTestCase {
-    ...
-    function testConnection() {
-        $socket = &new MockSocket($this);
-        ...
-        $telnet = &new TelnetTestVersion($this);
-        $telnet->setReturnReference('_createSocket', $socket);
-        $telnet->expectOnce('_createSocket', array('127.0.0.1', 21));
-        $telnet->Telnet();
-        $telnet->connect('127.0.0.1', 21, 'Me', 'Secret');
-        ...
-        $telnet->tally();
-    }
-}
-
-

- -

- -

Testing less than a class

- -

-

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

- -
- - - -- cgit v1.2.3