python中的单元测试

python中的单元测试框架

python中的单元测试框架有很多,什么unittest, testtools, subunit, coverage, testrepository, nose, mox, mock, fixtures, discover等等,看着就眼花撩乱。单元测试的重要性就不多说了,作为一名合格的程序员,会写单元测试是必要条件,这里我们就从最简单的单元测试开始看起。

Unittest

Unittest是python的一个标准模块,是其他框架的基础。unittest有几个重要的概念,test fixture, test case, test suite, test runner,只有理解了这几个概念,才能真正的理解单元测试的基本原理。

  • TestCase: 一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。Unittest的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。

  • TestSuit: 而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite

  • TestLoader: 是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom__()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。

  • TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。

  • 测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息

下面我们就写一个简单的测试用例。

1
2
3
4
class NumberAdd(object):
def Add(self,num):
return num+100

然后写一个测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import unittest
from model.models import NumberAdd
class TestNum(unittest.TestCase):
def setUp(self):
self.num = NumberAdd()
def testAdd(self):
assert self.num.Add(100) == 200
def tearDown(self):
self.num = None
if __name__ == "__main__":
unittest.main()

运行这个测试用例:

1
2
3
4
5
6
7
8
python3 test_num.py
kevin@qzoo:~/codes/scripts/python/untest$ python3 -m unittest discover
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

关于python -m

相信你一定见过python -m的用法,但是你知道python test.py 和 python -m test.py的区别吗?

python test.py: 直接运行, 就是把test.py所在的目录放到了sys.path中。

python -m test.py: 把模块当作脚本启动,也就是把你当前输入命令的目录放到sys.path中。

比如,我们有一个工程文件夹,结构如下:

1
2
3
4
5
6
model/
__init__.py
models.py
tests/
__init__.py
test_num.py

如果我们直接运行,python3 tests/test_num.py会报如下的错误:

1
2
3
4
Traceback (most recent call last):
File "tests/test_num.py", line 3, in <module>
from model.models import NumberAdd
ModuleNotFoundError: No module named 'model'

但是我们如果以模块的方式启动,则可以正常运行:

1
2
3
4
5
6
kevin@qzoo:~/codes/scripts/python/untest$ python3 -m tests.test_num
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

这就是python -m的作用。

运行unittest

可以使用命令

1
python3 -m unittest tests.test_num

也可以使用discover自动查找测试用例并执行

1
2
3
4
5
6
kevin@qzoo:~/codes/scripts/python/untest$ python3 -m unittest discover
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK

集成Unittest到SonarQube

在CI/CD的过程中,用到了代码测试工具SonarQube,并集成到了jenkins中,这样使得在构建之前就可以使用SonarQube对代码进行单元测试。

SonarQube集成python框架可以使用nose,但是这里我们先使用最原始的unittest,看一下如何在SonarQbue中部署。

在Jenkins的构建步骤中首先配置好SonarQube:

1
2
3
4
5
6
7
8
9
sonar.projectKey=order_management_of_petrocn
sonar.projectName=order_management_of_petrocn
sonar.language=py
sonar.python.coverage.reportPath=./flask_order/coverage.xml
sonar.python.xunit.reportPath=./flask_order/nosetests.xml
sonar.projectVersion=0.1
sonar.sources=./flask_order/
sonar.sourceEncoding=UTF-8
sonar.python.coveragePlugin=cobertura

然后再shell中执行命令:

1
sudo nosetests --with-xcoverage --with-xunit --cover-package=app/tests --cover-erase --cover-tests --cover-branches -w app/tests --exe

因为nosetests命令默认是会忽略掉可执行文件的,所以要加上–exe来确保可以正常启动测试。

关于单元测试的其他框架和内容,等有时间再继续。