Ticket #2443: tut1.patch

File tut1.patch, 10.1 KB (added by Antoine Pitrou, 11 years ago)
  • doc/core/howto/trial.xhtml

    diff -r e436fc4313f8 doc/core/howto/trial.xhtml
    a b Twisted. 
    2121<h2>Simple (stupid) example</h2>
     23<h3>Creating an API and writing tests</h3>
    2325<p>Let's say you want to create a calculation library. Create your project
    2426structure with a directory called calculus, a file called base.py (and a
    2527__init__.py file too).</p>
    2729<p>Then you write a simple API to your user in base.py. Different strategies
    28 apply here: some write first the tests, others write first the API. We're
    29 not going to worry too much about that.</p>
     30apply here: some like to write the tests first, others write the API first.
     31We're not going to worry too much about that.</p>
    3133<pre class="python">
    3234# -*- test-case-name: calculus.test.test_base -*-
    class Calculation(object): 
    4850<p>See, we've written the interface, not the code. Now we'll write what we
    4951expect the object to do. Create a test directory in the calculus one, with an
    50 __init__.py file, and a test_base.py file. Open test_base.py.
     52__init__.py file, and a test_base.py file.
    54 To resume you'll have this structure:
     56To make a long story short, you'll have this structure:
    5658    <li>
    5759        calculus/
    To resume you'll have this structure: 
     74<p>Now open test_base.py and fill in the following code:</p>
    7276<pre class="python">
    7377from calculus.base import Calculation
    7478from twisted.trial import unittest
    class CalculationTestCase(unittest.TestC 
    99103<p>To run the tests, call <code>trial calculus.test</code> in the command
    100 line. You hould have the following output:</p>
     104line. You should have the following output:</p>
    103107Running 4 tests.
    if you add additionnal log to your tests 
    175179if you add additionnal log to your tests.
    178 <p>Now that our tests failm when can actually implement the behaviour
    179 expected:</p>
    181 <pre class="python">
     182<h3>Writing code to match the tests' expectations</h3>
     184<p>Now that our tests fail whe can actually implement the expected behaviour
     185in calculus/base.py:</p>
     187<pre class="python">
     188# -*- test-case-name: calculus.test.test_base -*-
    182190class Calculation(object):
    183191    def add(self, a, b):
    184192        return a + b
    class Calculation(object): 
    193201        return a / b
    196 <p> Run trial again and hopefully your tests should now pass.</p>
     204<p>Run trial again and hopefully your tests should now pass.</p>
    199207Running 4 tests.
    PASSED (successes=4) 
    210218PASSED (successes=4)
     221<h3>Factoring out common test logic</h3>
    213223<p>You may observe that our test file contains a lot of redundant code, which
    214224is bad. For this purpose, trial defines a setUp method that is called before
    215225each test methods: this allows you to build some objects that will be use in
    216226all your tests methods. Also, we'll use a parameterized test method.</p>
    218228<p>Note that the counterpart of setUp is tearDown, which is called after
    219 each tests (either it succeed or failed); it's mainly useful for cleanups
    220 purpose.</p>
     229each test, wherever it succeeded or failed; it's mainly useful for cleanup
    222232<pre class="python">
    223233from calculus.base import Calculation
    class CalculationTestCase(unittest.TestC 
    282 <p>The only useful thing here is the <code>assertRaises</code> method,
    283 which takes the method to run and its arguments and assert it raises the
    284 given exception.</p>
     292<p>The only new thing here is the <code>assertRaises</code> method,
     293which takes the function or method to run and its arguments and checks that
     294it raises the given exception.</p>
    286296<p>Surprisingly, there are not so much tests that failed. But we still need
    287 to add some checking.</p>
     297to add some checking in our Calculation object:</p>
    289299<pre class="python">
    290300# -*- test-case-name: calculus.test.test_base -*-
    class Calculation(object): 
    319329<p>We just need a little trick because <code>int()</code> of a string
    320 raises a <code>ValueError</code>.</p>
     330raises a <code>ValueError</code> when it fails.</p>
    322332<h2>Testing a protocol</h2>
    324 <p>For the example, we'll create a custom protocol to make calculus within a
     334<p>We'll now create a custom protocol to invoke calculus within a
    325335telnet-like session. We'll basically call the command with the arguments and
    326336then get the commands.</p>
    328 <p>We first write the tests, and then explain what it does. Open the file calculus/test/test_remote.py.</p>
    330 <pre class="python">
     338<h3>Creating and testing the server</h3>
     340<p>First we'll write the tests, and then explain what they do.
     341Open the file calculus/test/test_remote.py:</p>
     343<pre class="python">
     344from calculus.remote import RemoteCalculationFactory
     345from twisted.trial import unittest
    331346from twisted.test import proto_helpers
    333348class RemoteCalculationTestCase(unittest.TestCase):
    purpose. This way we can emulate a netwo 
    366381<p>This concept is really important for Twisted. Even if there are many tests
    367 inside Twisted that use network, most good tests don't. The problem with
    368 unittests and network is that the network isn't reliable enough to be sure
     382inside Twisted that use the network, most good tests don't. The problem with
     383unittests and networking is that the network isn't reliable enough to be sure
    369384it'll have a sane behavior all the time. Thus it creates intermittent failures,
    370 which is a pain for continuous integration.
     385which is a pain for continuous integration. By using a fake transport, we are
     386able to write 100% reliable tests.
    373389<p>Let's now implement this protocol in calculus/remote.py.</p>
    375391<pre class="python">
     392# -*- test-case-name: calculus.test.test_remote -*-
    376394from twisted.protocols import basic
    377395from twisted.internet import protocol
    what methods you make accessible. 
    410428<p>Now if you run the tests, everything should be fine! You can also run
    411429a server to test it with a telnet client.</p>
     431<h3>Creating and testing the client</h3>
    413433<p>Of course you can't let your users with this: we'll now build a client
    414434to our server, to be able to use it inside a python program. And it'll
    415435serve our next purpose.</p>
     437<p>We first add the following code at the end of calculus/test/test_remote.py:</p>
    417439<pre class="python">
    418440class ClientCalculationTestCase(unittest.TestCase):
    client part, so we instantiate the proto 
    455477<p>Also, you can see that we assert that we've gone through the callback
    456 <code>cb</code> using a variable. It's very important, because sometimes, with
    457 asynchronous code, your tests are passing but because your callbacks are not
    458 called, which is generally a failure condition.</p>
    460 <p>We would have written the <code>_test</code> method more naturally this way:</p>
     478<code>cb</code> using a variable. It's very important, because otherwise, with
     479asynchronous code, your tests may pass while your callbacks are not called,
     480which should be a failure condition.</p>
     482<p>Actually, we don't need to do this check explicitly,
     483since Trial can do it for us if our test methods return the Deferred objects.
     484Thus we could have written the test logic in the following way:</p>
    462486<pre class="python">
    463487    def _test(self, operation, a, b, expected):
    called, which is generally a failure con 
    469493        d.addCallback(cb)
    470494        self.proto.dataReceived("%d\r\n" % (expected,))
    471495        return d
    472 </pre>
    474 <p>In this example, if you remove the <code>dataReceived</code> call, your tests will
    475 still succeed! That's because the callback is run synchronously, so returning the Deferred
    476 from the test doesn't help. So in doubt, use the <code>called</code> pattern.</p>
    478 <p>We'll now write the corresponding client.</p>
     497    def test_add(self):
     498        return self._test('add', 7, 6, 13)
     500    def test_substract(self):
     501        return self._test('substract', 82, 78, 4)
     503    def test_multiply(self):
     504        return self._test('multiply', 2, 8, 16)
     506    def test_divide(self):
     507        return self._test('divide', 14, 3, 4)
     510<p>We'll now write the client code at the end of calculus/remote.py:</p>
    480512<pre class="python">
    481513class RemoteCalculationClient(basic.LineReceiver):
    class RemoteCalculationClient(basic.Line 
    510542<p>The client is really straightforward. We just factor operations,
    511543everything else should be obvious.</p>
    513 <h2>Good pratices</h2>
     545<h2>More good pratices</h2>
    515547<h3>Testing scheduling</h3>
    517 <p>That's one of the tough part of Twisted: having a callLater call hanging
     549<p>That's one of the tough parts of Twisted: having a callLater call hanging
    518550around and be able to determically test it. There's a convenient object for
    519551that in <code>twisted.task</code> called <code>Clock</code>.</p>
    521 <p>The functionnality we'll use to test it is client request timeout: as it
     553<p>As an example we'll test the code for client request timeout: since our client
    522554uses TCP it can hang for a long time (firewall, connectivity problems, etc...).
    523555So generally we need to implement timeouts on the client side. Basically it's
    524 just that we send a request, don't have response and expect a timeout error.
     556just that we send a request, don't receive a response and expect a timeout error
     557to be triggered after a certain duration.
    527560<pre class="python">
    processes created in their tests. If it' 
    593626processes created in their tests. If it's still not obvious, you must try to
    594627avoid that like the plague, because it ends up with a lot of problems, one of
    595628them being intermittent failures. And intermittent failures are the plague
    596 to every automated tests.</p>
     629of automated tests.</p>
    598631<p>To actually test that, we'll launch a server with our protocol.</p>
    class RemoteRunCalculationTestCase(unitt 
    624657<code>stopListening</code> call, which is good.</p>
    626659<p>Also, you should be aware that tearDown will called in any case, after success
    627 or failure. So don't expect that every objects you create in the test method are
     660or failure. So don't expect that every objects you created in the test method are
    628661present, because your tests may have failed in the middle.</p>
    630663<h3>Resolve a bug</h3>