单元测试
单元测试
软件系统的开发是一个很复杂的过程,随着系统复杂性的提高,代码中隐藏的 bug 也可能变得越来越多。为了保证软件的质量,测试是一个必不可少的部分,甚至还有测试驱动开发(Test-driven development, TDD)的理念,也就是先测试再编码。在计算机编程中,单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作,所谓的单元是指一个函数,一个模块,一个类等。
在 Python 中,我们可以使用 unittest 模块编写单元测试。
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')    # 判断两个值是否相等
    def test_isupper(self):
        self.assertTrue('FOO'.isupper())          # 判断值是否为 True
        self.assertFalse('Foo'.isupper())         # 判断值是否为 False
    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):         # 检测异常
            s.split(2)
在上面,我们定义了一个 TestStringMethods 类,它从 unittest.TestCase 继承。注意到,我们的方法名都是以 test 开头,表明该方法是测试方法,不以 test 开头的方法测试的时候不会被执行。
在方法里面,我们使用了断言(assert)判断程序运行的结果是否和预期相符。其中:
- assertEqual用于判断两个值是否相等;
- assertTrue/assertFalse用于判断表达式的值是 True 还是 False;
- assertRaises用于检测异常;
断言方法主要有三种类型:
- 检测两个值的大小关系:相等,大于,小于等
- 检查逻辑表达式的值:True/Flase
- 检查异常
下面列举了部分常用的断言方法:
| Method | Checks that | 
|---|---|
| assertEqual(a, b) | a == b | 
| assertNotEqual(a, b) | a != b | 
| assertGreater(a, b) | a > b | 
| assertGreaterEqual(a, b) | a >= b | 
| assertLess(a, b) | a < b | 
| assertLessEqual(a, b) | a <= b | 
| assertTrue(x) | bool(x) is True | 
| assertFalse(x) | bool(x) is False | 
| assertIs(a, b) | a is b | 
| assertIsNot(a, b) | a is not b | 
| assertIsNone(x) | x is None | 
| assertIsNotNone(x) | x is not None | 
| assertIn(a, b) | a in b | 
| assertNotIn(a, b) | a not in b | 
| assertIsInstance(a, b) | isinstance(a, b) | 
| assertNotIsInstance(a, b) | not isinstance(a, b) | 
现在,让我们来运行上面的单元测试,将上面的代码保存为文件 mytest.py,通过 -m unittest 参数运行单元测试:
$ python -m unittest mytest
test_isupper (mytest.TestStringMethods) ... ok
test_split (mytest.TestStringMethods) ... ok
test_upper (mytest.TestStringMethods) ... ok
执行结果:
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上面的结果表明测试通过,我们也可以加 -v 参数得到更加详细的测试结果:
$ python -m unittest -v mytest
test_isupper (mytest.TestStringMethods) ... ok
test_split (mytest.TestStringMethods) ... ok
test_upper (mytest.TestStringMethods) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
上面这种运行单元测试的方法是我们推荐的做法,当然,你也可以在代码的最后添加两行:
if __name__ == '__main__':
    unittest.main()
然后再直接运行:
$ python mytest.py
setUp 和 tearDown
在某些情况下,我们需要在每个测试方法执行前和执行后做一些相同的操作,比如我们想在每个测试方法执行前连接数据库,执行后断开数据库连接,为了避免在每个测试方法中编写同样的代码,我们可以使用 setUp 和 tearDown 方法,比如:
# -*- coding: utf-8 -*-
import unittest
class TestStringMethods(unittest.TestCase):
    def setUp(self):                              # 在每个测试方法执行前被调用
        print 'setUp, Hello'
    def tearDown(self):                           # 在每个测试方法执行后被调用
        print 'tearDown, Bye!'
    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')    # 判断两个值是否相等
    def test_isupper(self):
        self.assertTrue('FOO'.isupper())          # 判断值是否为 True
        self.assertFalse('Foo'.isupper())         # 判断值是否为 False
    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):         # 检测异常
            s.split(2)
看看执行结果:
$ python -m unittest -v mytest
test_isupper (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
test_split (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
test_upper (mytest.TestStringMethods) ... setUp, Hello
tearDown, Bye!
ok
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
