Ticket #2443: tut1.patch

File tut1.patch, 10.1 KB (added by antoine, 7 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>