Python单元测试:unit testing best practices
Python单元测试:unit test code coverage
开发环境配置
略
单元测试覆盖率
- unittest: Python built-in standard library
- Pytest: a third party Python testing framework
- Coverage.py:one of the most popular code coverage tools for Python
- Pytest-cov:Python plugin to generate coverage reports. In addition to functionalities supported by coverage command, it also supports centralized and distributed testing
- Nose可以将所有的单元测试文件一次全部执行,并且提供了coverage的插件,能够统计整体的覆盖率,官网有详细的使用介绍https://pypi.org/project/nose/
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp$ pwd
/c/Users/bobho/Desktop/tmp
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp$ dir
app.py test.py
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp$ cat app.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
def process_input(a, b, operation):
if operation == "add":
return a + b
if operation == "subtract":
return a - b
if operation == "multiple":
return a * b
if operation == "divide":
if b == 0:
return "Invalid input"
return a / b
if __name__ == "__main__":
print(process_input(int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]))
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ cat app.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
def process_input(a, b, operation):
if operation == "add":
return a + b
if operation == "subtract":
return a - b
if operation == "multiple":
return a * b
if operation == "divide":
if b == 0:
return "Invalid input"
return a / b
if __name__ == "__main__":
print(process_input(int(sys.argv[1]), int(sys.argv[2]), sys.argv[3]))
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ python test.py
test_0010_add (__main__.TestApp) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ pip3 install coverage
Collecting coverage
Downloading https://files.pythonhosted.org/packages/9c/c2/036ccdc0bbcef7d980dd f89c132b39c81b2ba645f1c904b0c8edc957f60a/coverage-4.5.2-cp36-cp36m-win_amd64.whl (183kB)
Installing collected packages: coverage
Successfully installed coverage-4.5.2
You are using pip version 9.0.3, however version 18.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' comm and.
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ coverage run test.py
test_0010_add (__main__.TestApp) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ coverage report
Name Stmts Miss Cover
-----------------------------
app.py 14 9 36%
test.py 15 0 100%
-----------------------------
TOTAL 29 9 69%
添加单元测试,更新后的test.py如下
import unittest
from app import process_input
class TestApp(unittest.TestCase):
def setUp(self):
self.a = 10
self.b = 5
def test_0010_add(self):
result = process_input(self.a, self.b, "add")
self.assertEqual(result, 15)
def test_0020_subtract(self):
result = process_input(self.a, self.b, "subtract")
self.assertEqual(result, 5)
def suite():
suite = unittest.TestSuite()
suite.addTests(
unittest.TestLoader().loadTestsFromTestCase(TestApp)
)
return suite
if __name__ == "__main__":
unittest.TextTestRunner(verbosity=2).run(suite())
再次运行覆盖率检查,可以看到覆盖率已经提升
$ coverage run test.py
test_0010_add (__main__.TestApp) ... ok
test_0020_subtract (__main__.TestApp) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
bobho@LAPTOP-ET2KSKHR MINGW64 ~/Desktop/tmp
$ coverage report
Name Stmts Miss Cover
-----------------------------
app.py 14 7 50%
test.py 18 0 100%
-----------------------------
TOTAL 32 7 78%
运行coverage html
会在当前目录下面生成一个htmlcov,打开其中的app_py.html可以看到具体哪些行被覆盖到了

默认情况下,coverage生成的结果文件为.coverage,你可以通过修改环境变量COVERAGE_FILE来修改这个文件的后缀名。你也可以是用-a把多次运行的结果合并到一个文件里,否则,每次生成的结果文件都是上一次运行的结果。你可以是用coverage erase清空之前运行的结果文件。
coverage threshold
covearge可以使用参数--fail-under指定一个数字,当coverage的结果小于这个数字,coverage命令返回一个错误码2,但这个参数对annotate命令无效
$ coverage run run_my_tests.py
... running all tests
$ coverage report --fail-under=100
... display the report
$ echo $?
2
如果覆盖率不是100%,则返回值是2。也可以通过python api来验证覆盖率,示例如下:
cov = coverage.coverage(..)
cov.start()
ret = run_all_my_tests()
cov.stop()
if ret == 0:
covered = cov.report()
assert covered > 100, "Not enough coverage"
unittest框架
unittest 框架默认根据ASCII码的顺序加载测试用例,数字与字母的顺序为:09,AZ,a~z 如果要让某个测试用例先执行,不能使用默认的main()方法,需要通过TestSuite类的addTest()方法按照一定的顺序来加载
如果需要,用户可以自己实现discover(),遍历目录并添加和组合测试用例。利用discover获取指定目录或多级目录下的测试用例,但存放用例的目录属性必须是python package,必须要有init.py,不然不会获取成功
网友评论