Scott's Blog

学则不固, 知则不惑

0%

Python 测试驱动开发介绍

介绍了 Python 中各种测试方法与场景。

Test runner

一个特别设计的应用,用来跑测试,检查输出,调试和诊断你的代码。

Python 有几种测试工具:

  • unittest
  • nose or nose2
  • pytest

unittest

unittest 要求:

  1. 把你的测试代码写进 class 或者方法
  2. 在 unittest.TestClass 类中使用一系列特殊断言

一个简单的测试例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import unittest


class TestSum(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()

nose

nose 兼容其他测试框架写的代码,nose 会在你的目录寻找所有 py 文件和测试用例进行测试。

1
2
$ pip install nose2
$ python -m nose2

pytest

也支持 unitters,它的测试用例是以 test_ 开头的一些函数。 pytest 的优点是:

  • 支持内置的 assert 命令
  • 可以 filter 测试用例
  • 支持从上次失败的测试重新开始
  • 生态系统丰富,支持很多插件

一个 pytest 的测试代码:

1
2
3
4
5
def test_sum():
assert sum([1, 2, 3]) == 6, "Should be 6"

def test_sum_tuple():
assert sum((1, 2, 2)) == 6, "Should be 6"

编写测试

一个简单的测试项目:

1
2
3
4
5
6
project/

├── my_sum/
│ └── __init__.py
|
└── test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# __init__.py

def sum(arg):
total = 0
for val in arg:
total += val
return total

# test.py

import unittest

from my_sum import sum


class TestSum(unittest.TestCase):
def test_list_int(self):
"""
Test that it can sum a list of integers
"""
data = [1, 2, 3]
result = sum(data)
self.assertEqual(result, 6)

if __name__ == '__main__':
unittest.main()

断言是你的测试条件,比如你预期,可以通过 .assert*() 编写你的断言,比如 .assertEqual(a, b),它和 a==b 其实是相等的。

类似的函数还有:

  • assertTrue(x)
  • assertFalse(x)
  • assertIs(a, b)

执行测试

一般通过执行 test.py 文件来测试,在这个文件中你会调用 unittest.main() 函数。

或者也可以通过命令行的 python -m unittest test 来执行测试。

通过 python -m unittest -v test 来执行测试并输出每一个测试的内容。

通过 python -m unittest discover 让其自动发现目前目录戏的 test*.py 文件进行测试,此命名还支持子包的路径: python -m unittest discover -s tests -t src, 其中 src 是子目录名。

在 PyCharm 中,你可以执行 Run 'Unittests in ..' 来执行测试,在 VS Code 中,进入命令模式,输入 test 即可以看到相关指令。

Web 框架测试

Django, Flask

这类测试很不一样,因为它们内置了一些诸如路由、视图、模型等必要的模块,在启动的时候需要导入,这就好像你要测试一辆车,则需要先将电脑准备好。

但这类框架也提供了测试工具,比如 Django 在你执行 startapp 的时候,会自动创建 test.py 文件。

如果你的 Django 项目中没有的话,也可以自己创建,与前面的例子不一样的是,你需要将你的测试类继承自 django.test.TestCase,而不是 unittest.TestCase.

1
2
3
4
from django.test import TestCase

class MyTestCase(TestCase):
# Your test methods

在执行的时候,也需要换成 python manage.py test.

如果你有多个测试文件,则创建一个 test 文件夹,将你的测试文件名更改为 test_*.py,django 会自动发现并执行这些测试。

更多 Django 测试的内容可以参考这里

对于 Flask 则又有一些不同,需要设置应用的模式为 test, 然后你可以是实例化一个测试客户端,根据路由去做一些测试。

实例化的配置都在 setup 方法中,参考下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
import my_app
import unittest


class MyTestCase(unittest.TestCase):

def setUp(self):
my_app.app.testing = True
self.app = my_app.app.test_client()

def test_home(self):
result = self.app.get('/')
# Make your assertions

配置好后,可以直接使用 -m unittest discover 进行测试。

更多 Flask 测试的内容可以参考 Flask Documentation Website

高级测试场景

基本的测试步骤:

  1. 创建你的输入
  2. 执行代码,捕捉输出
  3. 对比输出和预期的结果

这里输入又叫 fixture, 一般创建后可以重复使用。

如果你跑一个测试多次,但使用不同的参数,并预期结果是一样的,这叫 parameterization。

集成测试

集成测试,顾名思义,会集成多个测试用来检查其每个模块是否都工作,它会站在用户的角度去测试你的软件。

通常集成测试的代码会和单元测试的分开,因为集成测试需要跑更多的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
project/

├── my_app/
│ └── __init__.py

└── tests/
|
├── unit/
| ├── __init__.py
| └── test_sum.py
|
└── integration/
├── __init__.py
└── test_integration.py

使用 unitters, 你可以通过 -s 来指定只包含某个部分的代码来进行测试。

python -m unittest discover -s tests/integration

如果你的测试依赖一些文件,可以新建fixtures文件夹,将数据放到里面。

环境测试

你想知道你的代码放在不同版本的 python 中是否工作,可以使用 Tox.

Tox 使用需要配置,它有一个配置文件,里面包括:

  • 测试需要执行的代码
  • 执行测试需要的包
  • 目标环境版本

你可以执行 tox-quickstart 来启动快速配置脚本,回答问题即可生成 tox.ini 配置文件。

自动测试

自动化测试工具,又被称为 CI/CD 工具,意思是 持续集成持续部署(Continuous Integration/Continuous Deployment)。

Travis CI 是在 Python 下不错的 CI 工具,并且对于所有 Github,GitLab 上开源的项目都免费,它会创建一个 .travis.yml 配置文件:

1
2
3
4
5
6
7
8
language: python
python:
- "2.7"
- "3.7"
install:
- pip install -r requirements.txt
script:
- python -m unittest discover

这个配置会测试代码在 python 2.7, 3.7 中是否工作,安装你配置文件中的包,并执行所有自动发现的单元测试。

当你配置好之后,Travis CI 会在你每次 push 后自动测试你的代码。

其他测试相关内容

  • 使用 Linters, 比如 flake8
  • 保持干净的测试代码,遵循 DRY( Don’t Repeat Yourself) 原则
  • 使用 timeit 或是 pytest-benchmark 模块,查看测试性能
  • 使用 bandit 测试你的包的安全问题.