diff options
author | wei <> | 2006-07-05 07:35:50 +0000 |
---|---|---|
committer | wei <> | 2006-07-05 07:35:50 +0000 |
commit | b6dfb6c447cf502e694d299dbda1b2e092c3312d (patch) | |
tree | b1bbd0abf857cca4b297d575942efa60edd12480 /test_tools/simpletest/docs/en/mock_objects_documentation.html | |
parent | b5c7c7b77d33aa3e04ed6c16a489a2076a30f57a (diff) |
move tests to test_tools
Diffstat (limited to 'test_tools/simpletest/docs/en/mock_objects_documentation.html')
-rwxr-xr-x | test_tools/simpletest/docs/en/mock_objects_documentation.html | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/test_tools/simpletest/docs/en/mock_objects_documentation.html b/test_tools/simpletest/docs/en/mock_objects_documentation.html new file mode 100755 index 00000000..2f8a1f90 --- /dev/null +++ b/test_tools/simpletest/docs/en/mock_objects_documentation.html @@ -0,0 +1,713 @@ +<html> +<head> +<META http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>SimpleTest for PHP mock objects 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> +<span class="chosen">Mock objects</span> +</li> +<li> +<a href="partial_mocks_documentation.html">Partial mocks</a> +</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>Mock objects documentation</h1> +<div class="content"> + <p> +<a class="target" name="what"> +<h2>What are mock objects?</h2> +</a> +</p> + <p> + Mock objects have two roles during a test case: actor and critic. + </p> + <p> + The actor behaviour is to simulate objects that are difficult to + set up or time consuming to set up for a test. + The classic example is a database connection. + Setting up a test database at the start of each test would slow + testing to a crawl and would require the installation of the + database engine and test data on the test machine. + If we can simulate the connection and return data of our + choosing we not only win on the pragmatics of testing, but can + also feed our code spurious data to see how it responds. + We can simulate databases being down or other extremes + without having to create a broken database for real. + In other words, we get greater control of the test environment. + </p> + <p> + If mock objects only behaved as actors they would simply be + known as <a href="server_stubs_documentation.html">server stubs</a>. + </p> + <p> + However, the mock objects not only play a part (by supplying chosen + return values on demand) they are also sensitive to the + messages sent to them (via expectations). + By setting expected parameters for a method call they act + as a guard that the calls upon them are made correctly. + 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. + Set them up with fairly tight expectations and you will + hardly need manual assertions at all. + </p> + + <p> +<a class="target" name="creation"> +<h2>Creating mock objects</h2> +</a> +</p> + <p> + In the same way that we create server stubs, all we need is an + existing class, say a database connection that looks like this... +<pre> +<strong>class DatabaseConnection { + function DatabaseConnection() { + } + + function query() { + } + + function selectQuery() { + } +}</strong> +</pre> + The class does not need to have been implemented yet. + To create a mock version of the class we need to include the + mock object library and run the generator... +<pre> +<strong>require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection');</strong> +</pre> + This generates a clone class called + <span class="new_code">MockDatabaseConnection</span>. + We can now create instances of the new class within + our test case... +<pre> +require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); +<strong> +class MyTestCase extends UnitTestCase { + + function testSomething() { + $connection = &new MockDatabaseConnection($this); + } +}</strong> +</pre> + Unlike the generated stubs the mock constructor needs a reference + to the test case so that it can dispatch passes and failures while + checking its expectations. + This means that mock objects can only be used within test cases. + Despite this their extra power means that stubs are hardly ever used + if mocks are available. + </p> + <p> + <a class="target" name="stub"> +<h2>Mocks as actors</h2> +</a> + </p> + <p> + The mock version of a class has all the methods of the original + so that operations like + <span class="new_code">$connection->query()</span> are still + legal. + As with stubs we can replace the default null return values... +<pre> +<strong>$connection->setReturnValue('query', 37);</strong> +</pre> + Now every time we call + <span class="new_code">$connection->query()</span> we get + the result of 37. + As with the stubs we can set wildcards and we can overload the + wildcard parameter. + We can also add extra methods to the mock when generating it + and choose our own class name... +<pre> +<strong>Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions'));</strong> +</pre> + Here the mock will behave as if the <span class="new_code">setOptions()</span> + existed in the original class. + This is handy if a class has used the PHP <span class="new_code">overload()</span> + mechanism to add dynamic methods. + You can create a special mock to simulate this situation. + </p> + <p> + All of the patterns available with server stubs are available + to mock objects... +<pre> +class Iterator { + function Iterator() { + } + + function next() { + } +} +</pre> + Again, assuming that this iterator only returns text until it + reaches the end, when it returns false, we can simulate it + with... +<pre> +Mock::generate('Iterator'); + +class IteratorTest extends UnitTestCase() { + + function testASequence() {<strong> + $iterator = &new MockIterator($this); + $iterator->setReturnValue('next', false); + $iterator->setReturnValueAt(0, 'next', 'First string'); + $iterator->setReturnValueAt(1, 'next', 'Second string');</strong> + ... + } +} +</pre> + When <span class="new_code">next()</span> is called on the + mock iterator it will first return "First string", + on the second call "Second string" will be returned + and on any other call <span class="new_code">false</span> will + be returned. + The sequenced return values take precedence over the constant + return value. + The constant one is a kind of default if you like. + </p> + <p> + A repeat of the stubbed information holder with name/value pairs... +<pre> +class Configuration { + function Configuration() { + } + + function getValue($key) { + } +} +</pre> + This is a classic situation for using mock objects as + actual configuration will vary from machine to machine, + hardly helping the reliability of our tests if we use it + directly. + The problem though is that all the data comes through the + <span class="new_code">getValue()</span> method and yet + we want different results for different keys. + Luckily the mocks have a filter system... +<pre> +<strong>$config = &new MockConfiguration($this); +$config->setReturnValue('getValue', 'primary', array('db_host')); +$config->setReturnValue('getValue', 'admin', array('db_user')); +$config->setReturnValue('getValue', 'secret', array('db_password'));</strong> +</pre> + The extra parameter is a list of arguments to attempt + to match. + In this case we are trying to match only one argument which + is the look up key. + Now when the mock object has the + <span class="new_code">getValue()</span> method invoked + like this... +<pre> +$config->getValue('db_user') +</pre> + ...it will return "admin". + It finds this by attempting to match the calling arguments + to its list of returns one after another until + a complete match is found. + </p> + <p> + 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... +<pre> +class Thing { +} + +class Vector { + function Vector() { + } + + function get($index) { + } +} +</pre> + In this case you can set a reference into the mock's + return list... +<pre> +$thing = new Thing();<strong> +$vector = &new MockVector($this); +$vector->setReturnReference('get', $thing, array(12));</strong> +</pre> + With this arrangement you know that every time + <span class="new_code">$vector->get(12)</span> is + called it will return the same + <span class="new_code">$thing</span> each time. + </p> + + <p> +<a class="target" name="expectations"> +<h2>Mocks as critics</h2> +</a> +</p> + <p> + Although the server stubs approach insulates your tests from + real world disruption, it is only half the benefit. + You can have the class under test receiving the required + messages, but is your new class sending correct ones? + Testing this can get messy without a mock objects library. + </p> + <p> + By way of example, suppose we have a + <span class="new_code">SessionPool</span> class that we + want to add logging to. + Rather than grow the original class into something more + complicated, we want to add this behaviour with a decorator (GOF). + The <span class="new_code">SessionPool</span> code currently looks + like this... +<pre> +<strong>class SessionPool { + function SessionPool() { + ... + } + + function &findSession($cookie) { + ... + } + ... +} + +class Session { + ... +}</strong> +</php> + While our logging code looks like this... +<php><strong> +class Log { + function Log() { + ... + } + + function message() { + ... + } +} + +class LoggingSessionPool { + function LoggingSessionPool(&$session_pool, &$log) { + ... + } + + function &findSession(\$cookie) { + ... + } + ... +}</strong> +</pre> + Out of all of this, the only class we want to test here + is the <span class="new_code">LoggingSessionPool</span>. + In particular we would like to check that the + <span class="new_code">findSession()</span> method is + called with the correct session ID in the cookie and that + it sent the message "Starting session $cookie" + to the logger. + </p> + <p> + Despite the fact that we are testing only a few lines of + production code, here is what we would have to do in a + conventional test case: + <ol> + <li>Create a log object.</li> + <li>Set a directory to place the log file.</li> + <li>Set the directory permissions so we can write the log.</li> + <li>Create a <span class="new_code">SessionPool</span> object.</li> + <li>Hand start a session, which probably does lot's of things.</li> + <li>Invoke <span class="new_code">findSession()</span>.</li> + <li>Read the new Session ID (hope there is an accessor!).</li> + <li>Raise a test assertion to confirm that the ID matches the cookie.</li> + <li>Read the last line of the log file.</li> + <li>Pattern match out the extra logging timestamps, etc.</li> + <li>Assert that the session message is contained in the text.</li> + </ol> + It is hardly surprising that developers hate writing tests + when they are this much drudgery. + To make things worse, every time the logging format changes or + the method of creating new sessions changes, we have to rewrite + parts of this test even though this test does not officially + test those parts of the system. + We are creating headaches for the writers of these other classes. + </p> + <p> + Instead, here is the complete test method using mock object magic... +<pre> +Mock::generate('Session'); +Mock::generate('SessionPool'); +Mock::generate('Log'); + +class LoggingSessionPoolTest extends UnitTestCase { + ... + function testFindSessionLogging() {<strong> + $session = &new MockSession($this); + $pool = &new MockSessionPool($this); + $pool->setReturnReference('findSession', $session); + $pool->expectOnce('findSession', array('abc')); + + $log = &new MockLog($this); + $log->expectOnce('message', array('Starting session abc')); + + $logging_pool = &new LoggingSessionPool($pool, $log); + $this->assertReference($logging_pool->findSession('abc'), $session); + $pool->tally(); + $log->tally();</strong> + } +} +</pre> + We start by creating a dummy session. + We don't have to be too fussy about this as the check + for which session we want is done elsewhere. + We only need to check that it was the same one that came + from the session pool. + </p> + <p> + <span class="new_code">findSession()</span> is a factory + method the simulation of which is described <a href="#stub">above</a>. + The point of departure comes with the first + <span class="new_code">expectOnce()</span> call. + This line states that whenever + <span class="new_code">findSession()</span> is invoked on the + mock, it will test the incoming arguments. + If it receives the single argument of a string "abc" + then a test pass is sent to the unit tester, otherwise a fail is + generated. + This was the part where we checked that the right session was asked for. + The argument list follows the same format as the one for setting + return values. + You can have wildcards and sequences and the order of + evaluation is the same. + </p> + <p> + 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 + <span class="new_code">tally()</span> call at the end of + the test. + </p> + <p> + We use the same pattern to set up the mock logger. + We tell it that it should have + <span class="new_code">message()</span> invoked + once only with the argument "Starting session abc". + By testing the calling arguments, rather than the logger output, + we insulate the test from any display changes in the logger. + </p> + <p> + We start to run our tests when we create the new + <span class="new_code">LoggingSessionPool</span> and feed + it our preset mock objects. + Everything is now under our control. + Finally we confirm that the + <span class="new_code">$session</span> we gave our decorator + is the one that we get back and tell the mocks to run their + internal call count tests with the + <span class="new_code">tally()</span> calls. + </p> + <p> + This is still quite a bit of test code, but the code is very + strict. + If it still seems rather daunting there is a lot less of it + than if we tried this without mocks and this particular test, + interactions rather than output, is always more work to set + up. + More often you will be testing more complex situations without + needing this level or precision. + Also some of this can be refactored into a test case + <span class="new_code">setUp()</span> method. + </p> + <p> + Here is the full list of expectations you can set on a mock object + in <a href="http://www.lastcraft.com/simple_test.php">SimpleTest</a>... + <table> +<thead> + <tr> +<th>Expectation</th><th>Needs <span class="new_code">tally()</span></th> +</tr> + </thead> +<tbody> +<tr> + <td><span class="new_code">expectArguments($method, $args)</span></td> + <td style="text-align: center">No</td> + </tr> + <tr> + <td><span class="new_code">expectArgumentsAt($timing, $method, $args)</span></td> + <td style="text-align: center">No</td> + </tr> + <tr> + <td><span class="new_code">expectCallCount($method, $count)</span></td> + <td style="text-align: center">Yes</td> + </tr> + <tr> + <td><span class="new_code">expectMaximumCallCount($method, $count)</span></td> + <td style="text-align: center">No</td> + </tr> + <tr> + <td><span class="new_code">expectMinimumCallCount($method, $count)</span></td> + <td style="text-align: center">Yes</td> + </tr> + <tr> + <td><span class="new_code">expectNever($method)</span></td> + <td style="text-align: center">No</td> + </tr> + <tr> + <td><span class="new_code">expectOnce($method, $args)</span></td> + <td style="text-align: center">Yes</td> + </tr> + <tr> + <td><span class="new_code">expectAtLeastOnce($method, $args)</span></td> + <td style="text-align: center">Yes</td> + </tr> + </tbody> +</table> + Where the parameters are... + <dl> + <dt class="new_code">$method</dt> + <dd>The method name, as a string, to apply the condition to.</dd> + <dt class="new_code">$args</dt> + <dd> + The arguments as a list. Wildcards can be included in the same + manner as for <span class="new_code">setReturn()</span>. + This argument is optional for <span class="new_code">expectOnce()</span> + and <span class="new_code">expectAtLeastOnce()</span>. + </dd> + <dt class="new_code">$timing</dt> + <dd> + The only point in time to test the condition. + The first call starts at zero. + </dd> + <dt class="new_code">$count</dt> + <dd>The number of calls expected.</dd> + </dl> + The method <span class="new_code">expectMaximumCallCount()</span> + is slightly different in that it will only ever generate a failure. + It is silent if the limit is never reached. + </p> + <p> + Like the assertions within test cases, all of the expectations + can take a message override as an extra parameter. + Also the original failure message can be embedded in the output + as "%s". + </p> + + <p> +<a class="target" name="approaches"> +<h2>Other approaches</h2> +</a> +</p> + <p> + There are three approaches to creating mocks including the one + that SimpleTest employs. + Coding them by hand using a base class, generating them to + a file and dynamically generating them on the fly. + </p> + <p> + Mock objects generated with <a href="simple_test.html">SimpleTest</a> + are dynamic. + They are created at run time in memory, using + <span class="new_code">eval()</span>, rather than written + out to a file. + This makes the mocks easy to create, a one liner, + especially compared with hand + crafting them in a parallel class hierarchy. + The problem is that the behaviour is usually set up in the tests + themselves. + If the original objects change the mock versions + that the tests rely on can get out of sync. + This can happen with the parallel hierarchy approach as well, + but is far more quickly detected. + </p> + <p> + The solution, of course, is to add some real integration + tests. + You don't need very many and the convenience gained + from the mocks more than outweighs the small amount of + extra testing. + You cannot trust code that was only tested with mocks. + </p> + <p> + If you are still determined to build static libraries of mocks + because you want to simulate very specific behaviour, you can + achieve the same effect using the SimpleTest class generator. + In your library file, say <em>mocks/connection.php</em> for a + database connection, create a mock and inherit to override + special methods or add presets... +<pre> +<?php + require_once('simpletest/mock_objects.php'); + require_once('../classes/connection.php'); +<strong> + Mock::generate('Connection', 'BasicMockConnection'); + class MockConnection extends BasicMockConnection { + function MockConnection(&$test, $wildcard = '*') { + $this->BasicMockConnection($test, $wildcard); + $this->setReturn('query', false); + } + }</strong> +?> +</pre> + The generate call tells the class generator to create + a class called <span class="new_code">BasicMockConnection</span> + rather than the usual <span class="new_code">MockConnection</span>. + We then inherit from this to get our version of + <span class="new_code">MockConnection</span>. + By intercepting in this way we can add behaviour, here setting + the default value of <span class="new_code">query()</span> to be false. + By using the default name we make sure that the mock class + generator will not recreate a different one when invoked elsewhere in the + tests. + It never creates a class if it already exists. + As long as the above file is included first then all tests + that generated <span class="new_code">MockConnection</span> should + now be using our one instead. + If we don't get the order right and the mock library + creates one first then the class creation will simply fail. + </p> + <p> + Use this trick if you find you have a lot of common mock behaviour + or you are getting frequent integration problems at later + stages of testing. + </p> + + <p> +<a class="target" name="other_testers"> +<h2>I think SimpleTest stinks!</h2> +</a> +</p> + <p> + But at the time of writing it is the only one with mock objects, + so are you stuck with it? + </p> + <p> + No, not at all. + <a href="simple_test.html">SimpleTest</a> 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... +<pre> +class PHPUnit { + function PHPUnit() { + } + + function assertion($message, $assertion) { + } + ... +} +</pre> + All the <span class="new_code">assertion()</span> 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... +<pre> +$unit_test = new PHPUnit(); +$unit_test>assertion('I hope this file exists', file_exists('my_file')); +</pre> + How do you use mocks with this? + </p> + <p> + There is a protected method on the base mock class + <span class="new_code">SimpleMock</span> called + <span class="new_code">_assertTrue()</span> and + by overriding this method we can use our own assertion format. + We start with a subclass, in say <em>my_mock.php</em>... +<pre> +<strong><?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); + } + } +?></strong> +</pre> + Now instantiating <span class="new_code">MyMock</span> 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... +<pre> +<?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); + } + }<strong> + SimpleTestOptions::setMockBaseClass('MyMock');</strong> +?> +</pre> + From now on you just include <em>my_mock.php</em> instead of the + default <em>mock_objects.php</em> version and you can introduce + mock objects into your existing test suite. + </p> + + </div> +<div class="copyright"> + Copyright<br>Marcus Baker, Jason Sweat, Perrick Penet 2004 + </div> +</body> +</html> |