Ticket #2443: tut1.patch

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

    diff -r e436fc4313f8 doc/core/howto/trial.xhtml
    a b Twisted. 
    2020
    2121<h2>Simple (stupid) example</h2>
    2222
     23<h3>Creating an API and writing tests</h3>
     24
    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>
    2628
    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>
    3032
    3133<pre class="python">
    3234# -*- test-case-name: calculus.test.test_base -*-
    class Calculation(object): 
    4749
    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.
    5153</p>
    5254
    5355<p>
    54 To resume you'll have this structure:
     56To make a long story short, you'll have this structure:
    5557<ul>
    5658    <li>
    5759        calculus/
    To resume you'll have this structure: 
    6971</ul>
    7072</p>
    7173
     74<p>Now open test_base.py and fill in the following code:</p>
     75
    7276<pre class="python">
    7377from calculus.base import Calculation
    7478from twisted.trial import unittest
    class CalculationTestCase(unittest.TestC 
    97101</pre>
    98102
    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>
    101105
    102106<pre>
    103107Running 4 tests.
    if you add additionnal log to your tests 
    175179if you add additionnal log to your tests.
    176180</p>
    177181
    178 <p>Now that our tests failm when can actually implement the behaviour
    179 expected:</p>
    180 
    181 <pre class="python">
     182<h3>Writing code to match the tests' expectations</h3>
     183
     184<p>Now that our tests fail whe can actually implement the expected behaviour
     185in calculus/base.py:</p>
     186
     187<pre class="python">
     188# -*- test-case-name: calculus.test.test_base -*-
     189
    182190class Calculation(object):
    183191    def add(self, a, b):
    184192        return a + b
    class Calculation(object): 
    193201        return a / b
    194202</pre>
    195203
    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>
    197205
    198206<pre>
    199207Running 4 tests.
    PASSED (successes=4) 
    210218PASSED (successes=4)
    211219</pre>
    212220
     221<h3>Factoring out common test logic</h3>
     222
    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>
    217227
    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
     230purposes.</p>
    221231
    222232<pre class="python">
    223233from calculus.base import Calculation
    class CalculationTestCase(unittest.TestC 
    279289
    280290</pre>
    281291
    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>
    285295
    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>
    288298
    289299<pre class="python">
    290300# -*- test-case-name: calculus.test.test_base -*-
    class Calculation(object): 
    317327</pre>
    318328
    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>
    321331
    322332<h2>Testing a protocol</h2>
    323333
    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>
    327337
    328 <p>We first write the tests, and then explain what it does. Open the file calculus/test/test_remote.py.</p>
    329 
    330 <pre class="python">
     338<h3>Creating and testing the server</h3>
     339
     340<p>First we'll write the tests, and then explain what they do.
     341Open the file calculus/test/test_remote.py:</p>
     342
     343<pre class="python">
     344from calculus.remote import RemoteCalculationFactory
     345from twisted.trial import unittest
    331346from twisted.test import proto_helpers
    332347
    333348class RemoteCalculationTestCase(unittest.TestCase):
    purpose. This way we can emulate a netwo 
    364379</p>
    365380
    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.
    371387</p>
    372388
    373389<p>Let's now implement this protocol in calculus/remote.py.</p>
    374390
    375391<pre class="python">
     392# -*- test-case-name: calculus.test.test_remote -*-
     393
    376394from twisted.protocols import basic
    377395from twisted.internet import protocol
    378396
    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>
    412430
     431<h3>Creating and testing the client</h3>
     432
    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>
     436
     437<p>We first add the following code at the end of calculus/test/test_remote.py:</p>
    416438
    417439<pre class="python">
    418440class ClientCalculationTestCase(unittest.TestCase):
    client part, so we instantiate the proto 
    453475</p>
    454476
    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>
    459 
    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>
     481
     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>
    461485
    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>
    473 
    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>
    477 
    478 <p>We'll now write the corresponding client.</p>
     496
     497    def test_add(self):
     498        return self._test('add', 7, 6, 13)
     499
     500    def test_substract(self):
     501        return self._test('substract', 82, 78, 4)
     502
     503    def test_multiply(self):
     504        return self._test('multiply', 2, 8, 16)
     505
     506    def test_divide(self):
     507        return self._test('divide', 14, 3, 4)
     508</pre>
     509
     510<p>We'll now write the client code at the end of calculus/remote.py:</p>
    479511
    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>
    512544
    513 <h2>Good pratices</h2>
     545<h2>More good pratices</h2>
    514546
    515547<h3>Testing scheduling</h3>
    516548
    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>
    520552
    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.
    525558</p>
    526559
    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>
    597630
    598631<p>To actually test that, we'll launch a server with our protocol.</p>
    599632
    class RemoteRunCalculationTestCase(unitt 
    624657<code>stopListening</code> call, which is good.</p>
    625658
    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>
    629662
    630663<h3>Resolve a bug</h3>