diff options
Diffstat (limited to 'tests/test_tools/simpletest/docs/en/partial_mocks_documentation.html')
-rwxr-xr-x | tests/test_tools/simpletest/docs/en/partial_mocks_documentation.html | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/tests/test_tools/simpletest/docs/en/partial_mocks_documentation.html b/tests/test_tools/simpletest/docs/en/partial_mocks_documentation.html new file mode 100755 index 00000000..20749415 --- /dev/null +++ b/tests/test_tools/simpletest/docs/en/partial_mocks_documentation.html @@ -0,0 +1,426 @@ +<html> +<head> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>SimpleTest for PHP partial mocks documentation</title> +<link rel="stylesheet" type="text/css" href="docs.css" title="Styles"> +</head> +<body> +<div class="menu_back"> +<div class="menu"> +<h2> +<a href="index.html">SimpleTest</a> +</h2> +<ul> +<li> +<a href="overview.html">Overview</a> +</li> +<li> +<a href="unit_test_documentation.html">Unit tester</a> +</li> +<li> +<a href="group_test_documentation.html">Group tests</a> +</li> +<li> +<a href="server_stubs_documentation.html">Server stubs</a> +</li> +<li> +<a href="mock_objects_documentation.html">Mock objects</a> +</li> +<li> +<span class="chosen">Partial mocks</span> +</li> +<li> +<a href="reporter_documentation.html">Reporting</a> +</li> +<li> +<a href="expectation_documentation.html">Expectations</a> +</li> +<li> +<a href="web_tester_documentation.html">Web tester</a> +</li> +<li> +<a href="form_testing_documentation.html">Testing forms</a> +</li> +<li> +<a href="authentication_documentation.html">Authentication</a> +</li> +<li> +<a href="browser_documentation.html">Scriptable browser</a> +</li> +</ul> +</div> +</div> +<h1>Partial mock objects documentation</h1> +<div class="content"> + + <p> + 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. + </p> + + <p> +<a class="target" name="inject"> +<h2>The mock injection problem</h2> +</a> +</p> + <p> + 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. + </p> + <p> + 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... +<pre> +<strong><?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) { + $socket = &new Socket($ip, $port); + $socket->read( ... ); + ... + } + } +?></strong> +</pre> + We would really like to have a mock object version of the socket + here, what can we do? + </p> + <p> + 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. + </p> + <p> + Here this would be... +<pre> +<?php + require_once('socket.php'); + + class Telnet { + ... + <strong>function &connect(&$socket, $username, $password) { + $socket->read( ... ); + ... + }</strong> + } +?> +</pre> + This means that the test code is typical for a test involving + mock objects. +<pre> +class TelnetTest extends UnitTestCase { + ... + function testConnection() {<strong> + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect($socket, 'Me', 'Secret'); + ...</strong> + } +} +</pre> + 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. + </p> + <p> + The next simplest compromise is to have the created object passed + in as an optional parameter... +<pre> +<?php + require_once('socket.php'); + + class Telnet { + ...<strong> + function &connect($ip, $port, $username, $password, $socket = false) { + if (!$socket) { + $socket = &new Socket($ip, $port); + } + $socket->read( ... );</strong> + ... + return $socket; + } + } +?> +</pre> + For a quick solution this is usually good enough. + The test now looks almost the same as if the parameter + was formally passed... +<pre> +class TelnetTest extends UnitTestCase { + ... + function testConnection() {<strong> + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket); + ...</strong> + } +} +</pre> + 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. + </p> + <p> + The next method is to pass in a factory object to do the creation... +<pre> +<?php + require_once('socket.php'); + + class Telnet {<strong> + function Telnet(&$network) { + $this->_network = &$network; + }</strong> + ... + function &connect($ip, $port, $username, $password) {<strong> + $socket = &$this->_network->createSocket($ip, $port); + $socket->read( ... );</strong> + ... + return $socket; + } + } +?> +</pre> + 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... +<pre> +class TelnetTest extends UnitTestCase { + ... + function testConnection() {<strong> + $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'); + ...</strong> + } +} +</pre> + 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. + </p> + <p> + Is there a middle ground? + </p> + + <p> +<a class="target" name="creation"> +<h2>Protected factory method</h2> +</a> +</p> + <p> + 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... +<pre> +<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) {<strong> + $socket = &$this->_createSocket($ip, $port);</strong> + $socket->read( ... ); + ... + }<strong> + + function &_createSocket($ip, $port) { + return new Socket($ip, $port); + }</strong> + } +?> +</pre> + This is the only change we make to the application code. + </p> + <p> + For the test case we have to create a subclass so that + we can intercept the socket creation... +<pre> +<strong>class TelnetTestVersion extends Telnet { + var $_mock; + + function TelnetTestVersion(&$mock) { + $this->_mock = &$mock; + $this->Telnet(); + } + + function &_createSocket() { + return $this->_mock; + } +}</strong> +</pre> + 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 + <span class="new_code">connect()</span>. + Otherwise it could get a null value from + <span class="new_code">_createSocket()</span>. + </p> + <p> + After the completion of all of this extra work the + actual test case is fairly easy. + We just test our new class instead... +<pre> +class TelnetTest extends UnitTestCase { + ... + function testConnection() {<strong> + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($socket); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ...</strong> + } +} +</pre> + 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? + </p> + + <p> +<a class="target" name="partial"> +<h2>A partial mock</h2> +</a> +</p> + <p> + 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. + </p> + <p> + Here is the partial mock version of the test... +<pre> +<strong>Mock::generatePartial( + 'Telnet', + 'TelnetTestVersion', + array('_createSocket'));</strong> + +class TelnetTest extends UnitTestCase { + ... + function testConnection() {<strong> + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ...</strong> + } +} +</pre> + The partial mock is a subclass of the original with + selected methods "knocked out" with test + versions. + The <span class="new_code">generatePartial()</span> call + takes three parameters: the class to be subclassed, + the new test class name and a list of methods to mock. + </p> + <p> + 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. + </p> + <p> + 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. + </p> + <p> + 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... +<pre> +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket);<strong> + $telnet->expectOnce('_createSocket', array('127.0.0.1', 21));</strong> + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ...<strong> + $telnet->tally();</strong> + } +} +</pre> + </p> + + <p> +<a class="target" name="less"> +<h2>Testing less than a class</h2> +</a> +</p> + <p> + 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. + </p> + <p> + 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. + </p> + <p> + It's all going to come down to the coding standards of your + project to decide which mechanism you use. + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> |