一些名词
调试和测试
调试一般是由代码的作者进行,用于自行检查程序运行过程,是否存在思路和实现不匹配的错误,调试的代码一般和程序主体写在一起,主要包括错误处理和日志记录。简单的可以用 print 和 assert,复杂的程序可以用 logging。
测试一般由第三方进行,测试代码和程序代码分离,写测试的人甚至不需要理解程序的具体工作原理,只关注给定的输入能否得到程序宣称的输出。
单元测试
对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
检验的方法是写一堆测试用例,把测试员拍脑袋想的输入交给相应的模块,看模块的输出是否正确;以及不合理的输入是否被程序识别,抛出异常。
单元测试全通过了不代表程序整体一定就没错误,但是单元测试通不过的程序一定有问题。
文档测试
文档是对代码的功能介绍,其中不免要举例子,给出实例代码和相应的输出,这个过程很像是单元测试,只不过是纯嘴炮。
文档测试就是自动寻找文档中的示例代码,运行之后,和文档中的结果进行比对。
集成测试
集成测试模拟用户的行为,测试各个模块之间的配合,测试结果应该保证程序可以在生产环境中工作。
Python 中的测试
文件结构
上一篇文章里面提到了 src-layout,在 setuptools 的官方文档里提供了一篇博客文章,里面提到,这种文件结构的一个优点就是,测试代码的文件夹一般和 src/
而不是 package 平级,这就导致运行测试的时候只能先(在虚拟环境里)安装待测试的包,而不会无意中出现测试的代码和用户下载到的内容不同的问题。
<project_name>
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── <package_name>/
│ ├── __init__.py
│ └── example.py
└── tests/
单元测试·unittest
unittest
是 Python 自带的单元测试库。
测试脚本的内容基本如下:
import unittest
class TestName1(unittest.TestCase):
def test_sum(self):
self.assertEqual(sum([1, 2, 3]), 6, "Should be 6")
def test_sum_tuple(self):
self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
class TestName2(unittest.TestCase):
def test_sum(self):
self.assertEqual(sum([1, 2, 3]), 6, "Should be 6")
def test_sum_tuple(self):
self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
if __name__ == '__main__':
unittest.main()
- 测试用例包装在一个 class 里面,这个 class 继承自
unittest.TestCase
- 所有测试方法名字以 “test” 开头,能测试的性质有限,都是类自带的方法,以
self.
开头。支持的方法见下表,感觉[assertTrue(x)](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertTrue)
和assertRaises(Exception)
包打一切 - 作为
'__main__'
运行,运行的是自带的函数unittest.main()
。 - 运行时需要 python 指明脚本的文件名。安装了 nose2 这个库的话,可以直接运行
python -m nose2
, 它会自动寻找所有的测试依次运行。(后面发现unittest
好像也有自动发现功能)
Method | Checks that | New in |
---|---|---|
unittest.TestCase.assertEqual | a == b | |
unittest.TestCase.assertNotEqual | a != b | |
unittest.TestCase.assertTrue | bool(x) is True | |
unittest.TestCase.assertFalse | bool(x) is False | |
unittest.TestCase.assertIs | a is b | 3.1 |
unittest.TestCase.assertIsNot | a is not b | 3.1 |
unittest.TestCase.assertIsNone | x is None | 3.1 |
unittest.TestCase.assertIsNotNone | x is not None | 3.1 |
unittest.TestCase.assertIn | a in b | 3.1 |
unittest.TestCase.assertNotIn | a not in b | 3.1 |
unittest.TestCase.assertIsInstance | isinstance(a, b) | 3.2 |
unittest.TestCase.assertNotIsInstance | not isinstance(a, b) | 3.2 |
文档测试
https://www.liaoxuefeng.com/wiki/1016959663602400/1017605739507840
Python 的文档测试用的是 doctest
库,写法如下:
class Dict(dict):
'''
Simple dict but also support access as x.y style.
>>> d1 = Dict()
>>> d1['x'] = 100
>>> d1.x
100
>>> d1.y = 200
>>> d1['y']
200
>>> d2 = Dict(a=1, b=2, c='3')
>>> d2.c
'3'
>>> d2['empty']
Traceback (most recent call last):
...
KeyError: 'empty'
>>> d2.empty
Traceback (most recent call last):
...
AttributeError: 'Dict' object has no attribute 'empty'
'''
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
if __name__=='__main__':
import doctest
doctest.testmod()
在保持 docstring 缩进的前提下,>>>
开头的注释会被当作测试运行,紧随其后的行将作为对比基准。只有当预期报错的时候,可以用 ...
省略中间的报错信息。
集成测试
也可以用 unittest
完成。
和单元测试的区别在于,一般来说需要构建测试数据集等等。这需要重写 unittest.TestCase.setup()
class TestComplexData(unittest.TestCase):
def setUp(self):
# load test data
self.app = App(database='fixtures/test_complex.json')
def test_customer_count(self):
self.assertEqual(len(self.app.customers), 10000)
def test_existence_of_customer(self):
customer = self.app.get_customer(id=9999)
self.assertEqual(customer.name, u"バナナ")
self.assertEqual(customer.address, "10 Red Road, Akihabara, Tokyo")
if __name__ == '__main__':
unittest.main()