partial_mocks_documentation.html.svn-base

来自「PHP 知识管理系统(基于树结构的知识管理系统), 英文原版的PHP源码。」· SVN-BASE 代码 · 共 446 行 · 第 1/2 页

SVN-BASE
446
字号
            </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>&lt;?phprequire_once('socket.php');    class Telnet {    ...    function &amp;connect($ip, $port, $username, $password) {<strong>        $socket = &amp;$this-&gt;_createSocket($ip, $port);</strong>        $socket-&gt;read( ... );        ...    }<strong>            function &amp;_createSocket($ip, $port) {        return new Socket($ip, $port);    }</strong>}?&gt;</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(&amp;$mock) {        $this-&gt;_mock = &amp;$mock;        $this-&gt;Telnet();    }        function &amp;_createSocket() {        return $this-&gt;_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 = &amp;new MockSocket($this);        ...        $telnet = &amp;new TelnetTestVersion($socket);        $telnet-&gt;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 = &amp;new MockSocket($this);        ...        $telnet = &amp;new TelnetTestVersion($this);        $telnet-&gt;setReturnReference('_createSocket', $socket);        $telnet-&gt;Telnet();        $telnet-&gt;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 = &amp;new MockSocket($this);        ...        $telnet = &amp;new TelnetTestVersion($this);        $telnet-&gt;setReturnReference('_createSocket', $socket);<strong>        $telnet-&gt;expectOnce('_createSocket', array('127.0.0.1', 21));</strong>        $telnet-&gt;Telnet();        $telnet-&gt;connect('127.0.0.1', 21, 'Me', 'Secret');        ...<strong>        $telnet-&gt;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>        References and related information...        <ul><li>            SimpleTest project page on <a href="http://sourceforge.net/projects/simpletest/">SourceForge</a>.        </li><li>            <a href="http://simpletest.org/api/">Full API for SimpleTest</a>            from the PHPDoc.        </li><li>            The protected factory is described in            <a href="http://www-106.ibm.com/developerworks/java/library/j-mocktest.html">this paper from IBM</a>.            This is the only formal comment I have seen on this problem.        </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>                |                <a href="mock_objects_documentation.html">Mock objects</a>                |                <span class="chosen">Partial mocks</span>                |                <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>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?