Beginning Python - From Novice to Professional
Beginning Python - From Novice to Professional Beginning Python - From Novice to Professional
CHAPTER 16 ■ TESTING, 1-2-3 343 Writing a test before you write your code isn’t just a preparation for finding bugs; it’s much more profound than that. It’s a preparation for seeing whether your code works at all. It’s a bit like the old Zen koan: Does a tree falling in the forest make a sound if no one is there to hear it? Well, of course it does (sorry, Zen monks), but the sound doesn’t have any impact on you or anyone else. It’s a bit similar with the code. Until you test it, does it actually do anything? Philosophy aside, it can be useful to adopt the attitude that a feature doesn’t really exist (or isn’t really a feature) until you have a test for it. Then you can clearly demonstrate that it’s there, and is doing what it’s supposed to. This isn’t only useful while developing the program initially, but also when extending and maintaining later on... Planning for Change In addition to helping a great deal as you write the program, automated tests help you avoid accumulating errors when you introduce changes. As discussed in Chapter 19, you should be prepared to change your code rather than clinging frantically to what you’ve got; but change has its dangers. When you change some piece of your code, you very often introduce some unforeseen bug. If you have designed your program well (with lots of abstraction and encapsulation), the effects of a change should be local, and only affect a small piece of the code. That means that debugging is easier if you spot the bug. The point is that if you don’t have a thorough set of tests handy, you may not even discover that you have introduced a bug until later, when you no longer know how the error got introduced. And without a good test set, it is much more difficult to pinpoint exactly what is wrong. You can’t roll with the punches unless you see them coming. One way of making sure that you get good test coverage (that is, that your tests exercise much, if not most, of your code) is, in fact, to follow the tenets of test-driven programming. If you make sure that you have written the tests before you write the function, you can be certain that every function is tested. CODE COVERAGE The concept of coverage is an important part of testing lore. When you run your tests, chances are you won’t run all parts of your code, even though that would be the ideal situation. (Actually, the ideal situation would be to run through every possible state of your program, using every possible input, but that’s really not going to happen.) One of the goals of a good test suite is to get good coverage, and one way of ensuring that is to use a coverage tool, which measures the percent of your code that was actually run during the testing. At the time of writing, there is no really standardized coverage tool for Python, but a Web search for something like “test coverage python” should turn up a few options. One option is the (currently undocumented) program trace.py that comes with the Python distribution. You can either run it as a program on the command line, or you can import it as a module. For help on how to use it, you can either run the program with the --help switch or import the module and execute help(trace) in the interpreter. At times one may feel overwhelmed by the requirement to test everything extensively. Don’t worry so much about it to begin with. You don’t have to test hundreds of combinations of inputs and state variables—at least not to begin with. The most important part of test-driven programming is that you actually run your method (or function or script) repeatedly while coding, to get continual feedback on how you’re doing. If you want to increase your confidence in the correctness of the code (as well as the coverage), you can always add more tests later.
344 CHAPTER 16 ■ TESTING, 1-2-3 The 1-2-3 (and 4) of Testing Before we get into the nitty-gritty of writing tests, here’s a breakdown of the test-driven development process (or one variation of it): 1. Figure out the new feature you want. Possibly document it, and then write a test for it. 2. Write some skeleton code for the feature, so that your program runs without any syntax errors or the like, but so that your test fails. It is important to see your test fail, so you are sure that it actually can fail. If there is something wrong with the test, and it always succeeds no matter what (this has happened to me lots of times), you aren’t really testing anything. This bears repeating: See your test fail before you try to make it succeed. 3. Write dummy code for your skeleton, just to appease the test. This doesn’t have to accurately implement the functionality, it just has to make the test pass. This way, you can have all your tests pass all the time when developing (except the first time you run the test, remember?), even while implementing the functionality initially. 4. Now you rewrite (or refactor) the code so that it actually does what it’s supposed to, all the while making sure that your test keeps succeeding. You should keep your code in a healthy state when you leave it—don’t leave it with any tests failing. (Well, that’s what they say. I find that I sometimes leave it with one test failing, which is the point at which I’m currently working. This is really bad form if you’re developing together with others, though. You should never check failing code into the common code repository.) Tools for Testing You may think that writing lots of tests to make sure that every detail of your program works correctly sounds like a chore. Well, I have good news for you: There is help in the standard libraries (isn’t there always?). There are two brilliant modules available to automate the testing process for you: unittest, a generic testing framework, and simpler module, doctest, which is designed for checking documentation, but which is excellent for writing unit tests as well. Let’s take a look at doctest, which is a great starting point. doctest Throughout this book, I use examples taken directly from the interactive interpreter. I find that this is an effective way to show how things work, and when you have such an example, it’s easy to test it for yourself. In fact, interactive interpreter sessions can be a useful form of documentation to put in docstrings. For instance, let’s say I write a function for squaring a number, and add an example to its docstring:
- Page 324 and 325: CHAPTER 13 ■ DATABASE SUPPORT 293
- Page 326: CHAPTER 13 ■ DATABASE SUPPORT 295
- Page 329 and 330: 298 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 331 and 332: 300 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 333 and 334: 302 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 335 and 336: 304 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 337 and 338: 306 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 339 and 340: 308 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 341 and 342: 310 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 343 and 344: 312 CHAPTER 14 ■ NETWORK PROGRAMM
- Page 345 and 346: 314 CHAPTER 15 ■ PYTHON AND THE W
- Page 347 and 348: 316 CHAPTER 15 ■ PYTHON AND THE W
- Page 349 and 350: 318 CHAPTER 15 ■ PYTHON AND THE W
- Page 351 and 352: 320 CHAPTER 15 ■ PYTHON AND THE W
- Page 353 and 354: 322 CHAPTER 15 ■ PYTHON AND THE W
- Page 355 and 356: 324 CHAPTER 15 ■ PYTHON AND THE W
- Page 357 and 358: 326 CHAPTER 15 ■ PYTHON AND THE W
- Page 359 and 360: 328 CHAPTER 15 ■ PYTHON AND THE W
- Page 361 and 362: 330 CHAPTER 15 ■ PYTHON AND THE W
- Page 363 and 364: 332 CHAPTER 15 ■ PYTHON AND THE W
- Page 365 and 366: 334 CHAPTER 15 ■ PYTHON AND THE W
- Page 367 and 368: 336 CHAPTER 15 ■ PYTHON AND THE W
- Page 369 and 370: 338 CHAPTER 15 ■ PYTHON AND THE W
- Page 372 and 373: CHAPTER 16 ■ ■ ■ Testing, 1-2
- Page 376 and 377: CHAPTER 16 ■ TESTING, 1-2-3 345 d
- Page 378 and 379: CHAPTER 16 ■ TESTING, 1-2-3 347 u
- Page 380 and 381: CHAPTER 16 ■ TESTING, 1-2-3 349 F
- Page 382 and 383: CHAPTER 16 ■ TESTING, 1-2-3 351 P
- Page 384 and 385: CHAPTER 16 ■ TESTING, 1-2-3 353 "
- Page 386: CHAPTER 16 ■ TESTING, 1-2-3 355 N
- Page 389 and 390: 358 CHAPTER 17 ■ EXTENDING PYTHON
- Page 391 and 392: 360 CHAPTER 17 ■ EXTENDING PYTHON
- Page 393 and 394: 362 CHAPTER 17 ■ EXTENDING PYTHON
- Page 395 and 396: 364 CHAPTER 17 ■ EXTENDING PYTHON
- Page 397 and 398: 366 CHAPTER 17 ■ EXTENDING PYTHON
- Page 399 and 400: 368 CHAPTER 17 ■ EXTENDING PYTHON
- Page 401 and 402: 370 CHAPTER 17 ■ EXTENDING PYTHON
- Page 404 and 405: CHAPTER 18 ■ ■ ■ Packaging Yo
- Page 406 and 407: CHAPTER 18 ■ PACKAGING YOUR PROGR
- Page 408 and 409: CHAPTER 18 ■ PACKAGING YOUR PROGR
- Page 410 and 411: CHAPTER 18 ■ PACKAGING YOUR PROGR
- Page 412 and 413: CHAPTER 19 ■ ■ ■ Playful Prog
- Page 414 and 415: CHAPTER 19 ■ PLAYFUL PROGRAMMING
- Page 416 and 417: CHAPTER 19 ■ PLAYFUL PROGRAMMING
- Page 418 and 419: CHAPTER 19 ■ PLAYFUL PROGRAMMING
- Page 420: CHAPTER 19 ■ PLAYFUL PROGRAMMING
- Page 423 and 424: 392 CHAPTER 20 ■ PROJECT 1: INSTA
344 CHAPTER 16 ■ TESTING, 1-2-3<br />
The 1-2-3 (and 4) of Testing<br />
Before we get in<strong>to</strong> the nitty-gritty of writing tests, here’s a breakdown of the test-driven development<br />
process (or one variation of it):<br />
1. Figure out the new feature you want. Possibly document it, and then write a test for it.<br />
2. Write some skele<strong>to</strong>n code for the feature, so that your program runs without any syntax<br />
errors or the like, but so that your test fails. It is important <strong>to</strong> see your test fail, so you are<br />
sure that it actually can fail. If there is something wrong with the test, and it always succeeds<br />
no matter what (this has happened <strong>to</strong> me lots of times), you aren’t really testing<br />
anything. This bears repeating: See your test fail before you try <strong>to</strong> make it succeed.<br />
3. Write dummy code for your skele<strong>to</strong>n, just <strong>to</strong> appease the test. This doesn’t have <strong>to</strong> accurately<br />
implement the functionality, it just has <strong>to</strong> make the test pass. This way, you can<br />
have all your tests pass all the time when developing (except the first time you run the<br />
test, remember?), even while implementing the functionality initially.<br />
4. Now you rewrite (or refac<strong>to</strong>r) the code so that it actually does what it’s supposed <strong>to</strong>, all<br />
the while making sure that your test keeps succeeding.<br />
You should keep your code in a healthy state when you leave it—don’t leave it with any<br />
tests failing. (Well, that’s what they say. I find that I sometimes leave it with one test failing,<br />
which is the point at which I’m currently working. This is really bad form if you’re developing<br />
<strong>to</strong>gether with others, though. You should never check failing code in<strong>to</strong> the common code<br />
reposi<strong>to</strong>ry.)<br />
Tools for Testing<br />
You may think that writing lots of tests <strong>to</strong> make sure that every detail of your program works<br />
correctly sounds like a chore. Well, I have good news for you: There is help in the standard<br />
libraries (isn’t there always?). There are two brilliant modules available <strong>to</strong> au<strong>to</strong>mate the testing<br />
process for you: unittest, a generic testing framework, and simpler module, doctest, which is<br />
designed for checking documentation, but which is excellent for writing unit tests as well. Let’s<br />
take a look at doctest, which is a great starting point.<br />
doctest<br />
Throughout this book, I use examples taken directly from the interactive interpreter. I find that<br />
this is an effective way <strong>to</strong> show how things work, and when you have such an example, it’s easy<br />
<strong>to</strong> test it for yourself. In fact, interactive interpreter sessions can be a useful form of documentation<br />
<strong>to</strong> put in docstrings. For instance, let’s say I write a function for squaring a number, and<br />
add an example <strong>to</strong> its docstring: