|
| 1 | +# Item 56: Test everything with unittest |
| 2 | +from unittest import TestCase, main |
| 3 | +from item_56_utils import to_str |
| 4 | + |
| 5 | + |
| 6 | +# Python doesn't have static type checking. There's nothing int eh compiler |
| 7 | +# that ensure your program will work when you run it. With Python you don't |
| 8 | +# know whether the functions your program calls will be defined at runtime, |
| 9 | +# even when their existence is evident in the source code. This dynamic |
| 10 | +# behavior is a blessing and a curse. |
| 11 | + |
| 12 | +# The large numbers of Python programmers out there say it's worth it because |
| 13 | +# of the productivity gained from the resulting brevity and simplicity. But |
| 14 | +# most people have heard at least one horror story about Python in which a |
| 15 | +# program encountered a boneheaded error at run time. |
| 16 | + |
| 17 | +# One of the worst examples I've heard is when a SyntaxError was raised in |
| 18 | +# production as a side effect of a dynamic import (see Item 52: "Know how to |
| 19 | +# break circular dependencies"). The programmer I know who was hit by this |
| 20 | +# surprising occurrence has since ruled out using Python ever again. |
| 21 | + |
| 22 | +# But I have to wonder, why wasn't the code tested before the program was |
| 23 | +# deployed to production? Type safety isn't everything. You should always test |
| 24 | +# your code, regardless of what language it's written in. However, I'll admit |
| 25 | +# that the big difference between Python and many other languages is that the |
| 26 | +# only way to have any confidence in a Python program is by writing tests. |
| 27 | +# There is no veil of static type checking to make you feel safe. |
| 28 | + |
| 29 | +# Luckily, the same dynamic features that prevent static type checking in |
| 30 | +# Python also make it extremely easy to write test for your code. You can use |
| 31 | +# Python's dynamic nature and easily overridable behaviors to implement tests |
| 32 | +# and ensure that your programs work as expected. |
| 33 | + |
| 34 | +# You should think of tests as an insurance policy on your code. Good tests |
| 35 | +# give you confidence that your code is correct. If you refactor or expand |
| 36 | +# your code, tests make it easy to identify how behaviors have changed. It |
| 37 | +# sounds counter-intuitive, but having good tests actually makes it easier to |
| 38 | +# modify Python code, not harder. |
| 39 | + |
| 40 | +# The simplest way to write tests is to use the unittest built-in module. For |
| 41 | +# example, say you have the following utility function defined in utils.py |
| 42 | + |
| 43 | + |
| 44 | +def to_str(data): |
| 45 | + if isinstance(data, str): |
| 46 | + return data |
| 47 | + elif isinstance(data, bytes): |
| 48 | + return data.decode('utf-8') |
| 49 | + else: |
| 50 | + raise TypeError('Must supply str or bytes, ' |
| 51 | + 'found: %r' % data) |
| 52 | + |
| 53 | + |
| 54 | +# To define tests, I create a second file named test_utils.py or utils_test.py |
| 55 | + |
| 56 | + |
| 57 | +class UtilsTestCase(TestCase): |
| 58 | + def test_to_str_bytes(self): |
| 59 | + self.assertEqual('hello', to_str(b'hello')) |
| 60 | + |
| 61 | + def test_to_str_str(self): |
| 62 | + self.assertEqual('hello', to_str('hello')) |
| 63 | + |
| 64 | + def test_to_str_bad(self): |
| 65 | + self.assertRaises(TypeError, to_str, object()) |
| 66 | + |
| 67 | + |
| 68 | +if __name__ == '__main__': |
| 69 | + main() |
| 70 | + |
| 71 | + |
| 72 | +# Tests are organized into TestCase classes. Each test is a method beginning |
| 73 | +# with the word test. If a test method runs without raising any kind of |
| 74 | +# Exception (including AssertionError from assert statements), then the test |
| 75 | +# is considered to have passed successfully. |
| 76 | + |
| 77 | +# The TestCase class provides helper methods for making assertions in your |
| 78 | +# tests, such as assertEqual for verifying equality, assertTrue for verifying |
| 79 | +# that exceptions are raised when appropriate (see help (TestCase) for more). |
| 80 | +# You can define your own helper methods in TestCase subclasses to make your |
| 81 | +# tests more readable; just ensure that your method names don't begin with |
| 82 | +# the word test. |
| 83 | + |
| 84 | +# Note |
| 85 | +# Another common practice when writing test is to use mock functions and |
| 86 | +# classes to stub out certain behaviors. For this purpose, Python 3 provides |
| 87 | +# the unittest.mock built-in module, which is available for Python 2 as an |
| 88 | +# open source package. |
| 89 | + |
| 90 | +# Sometimes, your TestCase classes need to set up the test environment before |
| 91 | +# running test methods. To do this, you can override the setUp and tearDown |
| 92 | +# methods. These methods are called before and after each test method, |
| 93 | +# respectively, and they let you ensure that each test runs in isolation (an |
| 94 | +# important best practice of proper testing). For example, here I define a |
| 95 | +# TestCase that creates a temporary directory before each test and deletes its |
| 96 | +# contents after each test finishes: |
| 97 | + |
| 98 | + |
| 99 | +class MyTest(TestCase): |
| 100 | + def setUp(self): |
| 101 | + self.test_dir = TemporaryDirectory() |
| 102 | + |
| 103 | + def tearDown(self): |
| 104 | + self.test_dir.cleanup() |
| 105 | + # |
| 106 | + # def test_to_str_bytes(self): |
| 107 | + # self.assertEqual('hello', to_str(b'hello')) |
| 108 | + # |
| 109 | + # def test_to_str_str(self): |
| 110 | + # self.assertEqual('hello', to_str('hello')) |
| 111 | + # |
| 112 | + # def test_to_str_bad(self): |
| 113 | + # self.assertRaises(TypeError, to_str, object()) |
| 114 | + |
| 115 | +# I usually define one TestCase for each set of related tests. Sometimes I |
| 116 | +# have one TestCase for each function that has many edge cases. Other times, |
| 117 | +# a TestCase spans all functions in a single module. I'll also create one |
| 118 | +# TestCase for testing a single class and all of its methods. |
| 119 | + |
| 120 | +# When programs get complicated, you'll want additional test for verifying |
| 121 | +# the interactions between your modules, instead of only testing code in |
| 122 | +# isolation. This is the difference between unit tests and integration tests. |
| 123 | +# In Python, it's important to write both types of test for exactly the same |
| 124 | +# reason: You have no guarantee that your modules will actually work together |
| 125 | +# unless you prove it. |
| 126 | + |
| 127 | +# Note |
| 128 | +# Depending on your project, it can also be useful to define data-driven tests |
| 129 | +# or organize test into different suites of related functionality. For these |
| 130 | +# purpose, code coverage reports, and other advanced use cases, the nose |
| 131 | +# (http://nose.readthedocs.org/) and pytest (http://pytest.org/) open source |
| 132 | +# packages can be especially helpful. |
| 133 | + |
| 134 | + |
| 135 | +# Things to remember |
| 136 | + |
| 137 | +# 1. The only way to have confidence in a Python program is to write tests. |
| 138 | +# 2. The unittest built-in module provides most of the facilities you'll need |
| 139 | +# to write good tests. |
| 140 | +# 3. You can define tests by subclassing TestCase and defining one method per |
| 141 | +# behavior you'd like to test. Test methods on TestCase classes must start |
| 142 | +# with the word test. |
| 143 | +# 4. It's important to write both unit tests (for isolated functionality) and |
| 144 | +# integration tests (for modules that interact). |
0 commit comments