diff options
| author | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2009-10-02 18:40:04 +0000 | 
|---|---|---|
| committer | brettp <brettp@36083f99-b078-4883-b0ff-0f9b5a30f544> | 2009-10-02 18:40:04 +0000 | 
| commit | 322bb9cd2be9e51422cb2b82684692e825c2bfb7 (patch) | |
| tree | 1e8e75c1ff0c47d6eda9db114a13dfa2b83adf72 /vendors/simpletest/docs/en/mock_objects_documentation.html | |
| parent | 4ffb02ad9c1b95013a0fbf97cefde827600ecc3f (diff) | |
| download | elgg-322bb9cd2be9e51422cb2b82684692e825c2bfb7.tar.gz elgg-322bb9cd2be9e51422cb2b82684692e825c2bfb7.tar.bz2 | |
Added simpletest and start of unit tests.
git-svn-id: http://code.elgg.org/elgg/trunk@3503 36083f99-b078-4883-b0ff-0f9b5a30f544
Diffstat (limited to 'vendors/simpletest/docs/en/mock_objects_documentation.html')
| -rwxr-xr-x | vendors/simpletest/docs/en/mock_objects_documentation.html | 757 | 
1 files changed, 757 insertions, 0 deletions
| diff --git a/vendors/simpletest/docs/en/mock_objects_documentation.html b/vendors/simpletest/docs/en/mock_objects_documentation.html new file mode 100755 index 000000000..c3d002277 --- /dev/null +++ b/vendors/simpletest/docs/en/mock_objects_documentation.html @@ -0,0 +1,757 @@ +<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"> +<a href="index.html">SimpleTest</a> +                | +                <a href="overview.html">Overview</a> +                | +                <a href="unit_test_documentation.html">Unit tester</a> +                | +                <a href="group_test_documentation.html">Group tests</a> +                | +                <span class="chosen">Mock objects</span> +                | +                <a href="partial_mocks_documentation.html">Partial mocks</a> +                | +                <a href="reporter_documentation.html">Reporting</a> +                | +                <a href="expectation_documentation.html">Expectations</a> +                | +                <a href="web_tester_documentation.html">Web tester</a> +                | +                <a href="form_testing_documentation.html">Testing forms</a> +                | +                <a href="authentication_documentation.html">Authentication</a> +                | +                <a href="browser_documentation.html">Scriptable browser</a> +</div></div> +<h1>Mock objects documentation</h1> +        This page... +        <ul> +<li> +            <a href="#what">What are mock objects?</a> +        </li> +<li> +            <a href="#creation">Creating mock objects</a>. +        </li> +<li> +            <a href="#stub">Mocks as actors</a> or stubs. +        </li> +<li> +            <a href="#expectations">Mocks as critics</a> with expectations. +        </li> +<li> +            <a href="#approaches">Other approaches</a> including mock libraries. +        </li> +</ul> +<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 server stubs. +                This was originally a pattern named by Robert Binder (Testing +                object-oriented systems: models, patterns, and tools, +                Addison-Wesley) in 1999. +            </p> +            <p> +                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. +            </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. +            </p> +            <p> +                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(); +    } +}</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. +                The return value will be <span class="new_code">null</span>, +                but we can change that with... +<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. +                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. +            </p> +            <p> +                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> +                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... +<pre> +class Iterator { +    function Iterator() { +    } +     +    function next() { +    } +} +</pre> +                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... +<pre> +Mock::generate('Iterator'); + +class IteratorTest extends UnitTestCase() { +     +    function testASequence() {<strong> +        $iterator = &new MockIterator(); +        $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> +                Another tricky situation is an overloaded +                <span class="new_code">get()</span> operation. +                An example of this is an information holder with name/value pairs. +                Say we have a configuration class like... +<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(); +$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> +                You can set a default argument argument like so... +<pre><strong> +$config->setReturnValue('getValue', false, array('*'));</strong> +</pre> +                This is not the same as setting the return value without +                any argument requirements like this... +<pre><strong> +$config->setReturnValue('getValue', false);</strong> +</pre> +                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. +            </p> +            <p> +                There are times when you want a specific object to be +                dished out by the mock rather than a copy. +                The PHP4 copy semantics force us to use a different method +                for this. +                You might be simulating a container for example... +<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(); +$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. +                This is compatible with PHP5 as well. +            </p> +            <p> +                These three factors, timing, parameters and whether to copy, +                can be combined orthogonally. +                For example... +<pre> +$complex = &new MockComplexThing(); +$stuff = &new Stuff();<strong> +$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1));</strong> +</pre> +                This will return the <span class="new_code">$stuff</span> 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. +            </p> +            <p> +                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 <span class="new_code">next()</span> giving +                one row until false. +                This sounds like a simulation nightmare, but in fact it can all +                be mocked using the mechanics above. +            </p> +            <p> +                Here's how... +<pre> +Mock::generate('DatabaseConnection'); +Mock::generate('ResultIterator'); + +class DatabaseTest extends UnitTestCase { +     +    function testUserFinder() {<strong> +        $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'));</strong> +                 +        $finder = &new UserFinder($connection); +        $this->assertIdentical( +                $finder->findNames(), +                array('tom', 'dick', 'harry')); +    } +} +</pre> +                Now only if our +                <span class="new_code">$connection</span> is called with the correct +                <span class="new_code">query()</span> will the +                <span class="new_code">$result</span> be returned that is +                itself exhausted after the third call to <span class="new_code">next()</span>. +                This should be enough +                information for our <span class="new_code">UserFinder</span> class, +                the class actually +                being tested here, to come up with goods. +                A very precise test and not a real database in sight. +            </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> +</pre> +                While our logging code looks like this... +<pre> +<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(); +        $pool = &new MockSessionPool(); +        $pool->setReturnReference('findSession', $session); +        $pool->expectOnce('findSession', array('abc')); +         +        $log = &new MockLog(); +        $log->expectOnce('message', array('Starting session abc')); +         +        $logging_pool = &new LoggingSessionPool($pool, $log); +        $this->assertReference($logging_pool->findSession('abc'), $session);</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> +                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. +            </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">expect($method, $args)</span></td> +                        <td style="text-align: center">No</td> +                    </tr> +                    <tr> +                        <td><span class="new_code">expectAt($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> +                Also if you have juste one call in your test, make sure you're using +                <span class="new_code">expectOnce</span>.<br> +                Using <span class="new_code">$mocked->expectAt(0, 'method', 'args);</span> +                on its own will not be catched : +                checking the arguments and the overall call count +                are currently independant. +            </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() { +            $this->BasicMockConnection(); +            $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> +         +    </div> +        References and related information... +        <ul> +<li> +            The original +            <a href="http://www.mockobjects.com/">Mock objects</a> paper. +        </li> +<li> +            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>. +        </li> +<li> +            SimpleTest home page on <a href="http://www.lastcraft.com/simple_test.php">LastCraft</a>. +        </li> +</ul> +<div class="menu_back"><div class="menu"> +<a href="index.html">SimpleTest</a> +                | +                <a href="overview.html">Overview</a> +                | +                <a href="unit_test_documentation.html">Unit tester</a> +                | +                <a href="group_test_documentation.html">Group tests</a> +                | +                <span class="chosen">Mock objects</span> +                | +                <a href="partial_mocks_documentation.html">Partial mocks</a> +                | +                <a href="reporter_documentation.html">Reporting</a> +                | +                <a href="expectation_documentation.html">Expectations</a> +                | +                <a href="web_tester_documentation.html">Web tester</a> +                | +                <a href="form_testing_documentation.html">Testing forms</a> +                | +                <a href="authentication_documentation.html">Authentication</a> +                | +                <a href="browser_documentation.html">Scriptable browser</a> +</div></div> +<div class="copyright"> +            Copyright<br>Marcus Baker 2006 +        </div> +</body> +</html> | 
