diff -r e436fc4313f8 doc/core/howto/trial.xhtml
|
a
|
b
|
|
| 20 | 20 | |
| 21 | 21 | <h2>Simple (stupid) example</h2> |
| 22 | 22 | |
| | 23 | <h3>Creating an API and writing tests</h3> |
| | 24 | |
| 23 | 25 | <p>Let's say you want to create a calculation library. Create your project |
| 24 | 26 | structure with a directory called calculus, a file called base.py (and a |
| 25 | 27 | __init__.py file too).</p> |
| 26 | 28 | |
| 27 | 29 | <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> |
| | 30 | apply here: some like to write the tests first, others write the API first. |
| | 31 | We're not going to worry too much about that.</p> |
| 30 | 32 | |
| 31 | 33 | <pre class="python"> |
| 32 | 34 | # -*- test-case-name: calculus.test.test_base -*- |
| … |
… |
|
| 47 | 49 | |
| 48 | 50 | <p>See, we've written the interface, not the code. Now we'll write what we |
| 49 | 51 | expect 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. |
| 51 | 53 | </p> |
| 52 | 54 | |
| 53 | 55 | <p> |
| 54 | | To resume you'll have this structure: |
| | 56 | To make a long story short, you'll have this structure: |
| 55 | 57 | <ul> |
| 56 | 58 | <li> |
| 57 | 59 | calculus/ |
| … |
… |
|
| 69 | 71 | </ul> |
| 70 | 72 | </p> |
| 71 | 73 | |
| | 74 | <p>Now open test_base.py and fill in the following code:</p> |
| | 75 | |
| 72 | 76 | <pre class="python"> |
| 73 | 77 | from calculus.base import Calculation |
| 74 | 78 | from twisted.trial import unittest |
| … |
… |
|
| 97 | 101 | </pre> |
| 98 | 102 | |
| 99 | 103 | <p>To run the tests, call <code>trial calculus.test</code> in the command |
| 100 | | line. You hould have the following output:</p> |
| | 104 | line. You should have the following output:</p> |
| 101 | 105 | |
| 102 | 106 | <pre> |
| 103 | 107 | Running 4 tests. |
| … |
… |
|
| 175 | 179 | if you add additionnal log to your tests. |
| 176 | 180 | </p> |
| 177 | 181 | |
| 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 |
| | 185 | in calculus/base.py:</p> |
| | 186 | |
| | 187 | <pre class="python"> |
| | 188 | # -*- test-case-name: calculus.test.test_base -*- |
| | 189 | |
| 182 | 190 | class Calculation(object): |
| 183 | 191 | def add(self, a, b): |
| 184 | 192 | return a + b |
| … |
… |
|
| 193 | 201 | return a / b |
| 194 | 202 | </pre> |
| 195 | 203 | |
| 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> |
| 197 | 205 | |
| 198 | 206 | <pre> |
| 199 | 207 | Running 4 tests. |
| … |
… |
|
| 210 | 218 | PASSED (successes=4) |
| 211 | 219 | </pre> |
| 212 | 220 | |
| | 221 | <h3>Factoring out common test logic</h3> |
| | 222 | |
| 213 | 223 | <p>You may observe that our test file contains a lot of redundant code, which |
| 214 | 224 | is bad. For this purpose, trial defines a setUp method that is called before |
| 215 | 225 | each test methods: this allows you to build some objects that will be use in |
| 216 | 226 | all your tests methods. Also, we'll use a parameterized test method.</p> |
| 217 | 227 | |
| 218 | 228 | <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> |
| | 229 | each test, wherever it succeeded or failed; it's mainly useful for cleanup |
| | 230 | purposes.</p> |
| 221 | 231 | |
| 222 | 232 | <pre class="python"> |
| 223 | 233 | from calculus.base import Calculation |
| … |
… |
|
| 279 | 289 | |
| 280 | 290 | </pre> |
| 281 | 291 | |
| 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, |
| | 293 | which takes the function or method to run and its arguments and checks that |
| | 294 | it raises the given exception.</p> |
| 285 | 295 | |
| 286 | 296 | <p>Surprisingly, there are not so much tests that failed. But we still need |
| 287 | | to add some checking.</p> |
| | 297 | to add some checking in our Calculation object:</p> |
| 288 | 298 | |
| 289 | 299 | <pre class="python"> |
| 290 | 300 | # -*- test-case-name: calculus.test.test_base -*- |
| … |
… |
|
| 317 | 327 | </pre> |
| 318 | 328 | |
| 319 | 329 | <p>We just need a little trick because <code>int()</code> of a string |
| 320 | | raises a <code>ValueError</code>.</p> |
| | 330 | raises a <code>ValueError</code> when it fails.</p> |
| 321 | 331 | |
| 322 | 332 | <h2>Testing a protocol</h2> |
| 323 | 333 | |
| 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 |
| 325 | 335 | telnet-like session. We'll basically call the command with the arguments and |
| 326 | 336 | then get the commands.</p> |
| 327 | 337 | |
| 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. |
| | 341 | Open the file calculus/test/test_remote.py:</p> |
| | 342 | |
| | 343 | <pre class="python"> |
| | 344 | from calculus.remote import RemoteCalculationFactory |
| | 345 | from twisted.trial import unittest |
| 331 | 346 | from twisted.test import proto_helpers |
| 332 | 347 | |
| 333 | 348 | class RemoteCalculationTestCase(unittest.TestCase): |
| … |
… |
|
| 364 | 379 | </p> |
| 365 | 380 | |
| 366 | 381 | <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 |
| | 382 | inside Twisted that use the network, most good tests don't. The problem with |
| | 383 | unittests and networking is that the network isn't reliable enough to be sure |
| 369 | 384 | it'll have a sane behavior all the time. Thus it creates intermittent failures, |
| 370 | | which is a pain for continuous integration. |
| | 385 | which is a pain for continuous integration. By using a fake transport, we are |
| | 386 | able to write 100% reliable tests. |
| 371 | 387 | </p> |
| 372 | 388 | |
| 373 | 389 | <p>Let's now implement this protocol in calculus/remote.py.</p> |
| 374 | 390 | |
| 375 | 391 | <pre class="python"> |
| | 392 | # -*- test-case-name: calculus.test.test_remote -*- |
| | 393 | |
| 376 | 394 | from twisted.protocols import basic |
| 377 | 395 | from twisted.internet import protocol |
| 378 | 396 | |
| … |
… |
|
| 410 | 428 | <p>Now if you run the tests, everything should be fine! You can also run |
| 411 | 429 | a server to test it with a telnet client.</p> |
| 412 | 430 | |
| | 431 | <h3>Creating and testing the client</h3> |
| | 432 | |
| 413 | 433 | <p>Of course you can't let your users with this: we'll now build a client |
| 414 | 434 | to our server, to be able to use it inside a python program. And it'll |
| 415 | 435 | serve our next purpose.</p> |
| | 436 | |
| | 437 | <p>We first add the following code at the end of calculus/test/test_remote.py:</p> |
| 416 | 438 | |
| 417 | 439 | <pre class="python"> |
| 418 | 440 | class ClientCalculationTestCase(unittest.TestCase): |
| … |
… |
|
| 453 | 475 | </p> |
| 454 | 476 | |
| 455 | 477 | <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 |
| | 479 | asynchronous code, your tests may pass while your callbacks are not called, |
| | 480 | which should be a failure condition.</p> |
| | 481 | |
| | 482 | <p>Actually, we don't need to do this check explicitly, |
| | 483 | since Trial can do it for us if our test methods return the Deferred objects. |
| | 484 | Thus we could have written the test logic in the following way:</p> |
| 461 | 485 | |
| 462 | 486 | <pre class="python"> |
| 463 | 487 | def _test(self, operation, a, b, expected): |
| … |
… |
|
| 469 | 493 | d.addCallback(cb) |
| 470 | 494 | self.proto.dataReceived("%d\r\n" % (expected,)) |
| 471 | 495 | 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> |
| 479 | 511 | |
| 480 | 512 | <pre class="python"> |
| 481 | 513 | class RemoteCalculationClient(basic.LineReceiver): |
| … |
… |
|
| 510 | 542 | <p>The client is really straightforward. We just factor operations, |
| 511 | 543 | everything else should be obvious.</p> |
| 512 | 544 | |
| 513 | | <h2>Good pratices</h2> |
| | 545 | <h2>More good pratices</h2> |
| 514 | 546 | |
| 515 | 547 | <h3>Testing scheduling</h3> |
| 516 | 548 | |
| 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 |
| 518 | 550 | around and be able to determically test it. There's a convenient object for |
| 519 | 551 | that in <code>twisted.task</code> called <code>Clock</code>.</p> |
| 520 | 552 | |
| 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 |
| 522 | 554 | uses TCP it can hang for a long time (firewall, connectivity problems, etc...). |
| 523 | 555 | So 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. |
| | 556 | just that we send a request, don't receive a response and expect a timeout error |
| | 557 | to be triggered after a certain duration. |
| 525 | 558 | </p> |
| 526 | 559 | |
| 527 | 560 | <pre class="python"> |
| … |
… |
|
| 593 | 626 | processes created in their tests. If it's still not obvious, you must try to |
| 594 | 627 | avoid that like the plague, because it ends up with a lot of problems, one of |
| 595 | 628 | them being intermittent failures. And intermittent failures are the plague |
| 596 | | to every automated tests.</p> |
| | 629 | of automated tests.</p> |
| 597 | 630 | |
| 598 | 631 | <p>To actually test that, we'll launch a server with our protocol.</p> |
| 599 | 632 | |
| … |
… |
|
| 624 | 657 | <code>stopListening</code> call, which is good.</p> |
| 625 | 658 | |
| 626 | 659 | <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 |
| | 660 | or failure. So don't expect that every objects you created in the test method are |
| 628 | 661 | present, because your tests may have failed in the middle.</p> |
| 629 | 662 | |
| 630 | 663 | <h3>Resolve a bug</h3> |