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.
+
+ 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.
-
-
Copyright
Marcus Baker, Jason Sweat, Perrick Penet 2004
--
cgit v1.2.3