前言
单元测试的重要性不言而喻,比如经典的测试金字塔,最底层的便是单元测试,它越接近代码层,收益越大。
之前在Flask编写微服务的过程中,并未注意过单元测试,最近准备重构代码的过程中,发现之前的代码还是有很大的优化空间,而且不适合写单元测试,为了方便单元测试的开展,还需要重新整理之前的框架,自己感觉非常得不偿失。
虽然是测试出身,但是自己却从来未真正实践单元测试。真的负罪感满满。
最近看了很多单元测试方面的知识,有一句话印象颇深,与大家共勉。
Martin Fowler提到”在你不知道如何测试代码之前,就不该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。”
先介绍一下Python的单元测试常用框架
unittest是Python内置的标准类库。它的API跟Java的JUnit、.net的NUnit,C++的CppUnit很相似。
unittest中最核心的四个概念是:test case, test suite, test runner, test fixture。通过继承unittest.TestCase来创建一个测试用例。
unittest的使用有一些潜规则
pytest是一个功能丰富、灵活的测试框架,但是它的语法很简单。创建一个单元测试就像编写一个模块一样。相比unittest,实现相同的测试功能,py.test做的事情更少。
pytest有一些特点:
nose是对unittest的扩展,使得python的测试更加简单。nose自动发现测试代码并执行,nose提供了大量的插件,比如测试输出的xUnitcompatible,覆盖报表等等。
nose 不使用特定的格式、不需要一个类容器,甚至不需要 import nose ~(这也就意味着它在写测试用例时不需要使用额外的 api)。
nose的使用非常简单,自带光环:
粉一下nose的宣言
nose extends unittest to make testing easier.
先简单看一下我Flask的服务是
app里面是主框架,main里面分别有api和agent,schedule,agent是封装了邮件发送的异步操作,schedule是封装了定时执行的后台运行操作,app下面的models是数据库相关的表结构,而和app同级的tests下面,则是我新增的单测用例文件。
首先新建一个tests的包结构,然后在init.py里面,增加一个类似setup的动作,
from app import create_app, db
app = create_app(‘testing’)
app_ctx = app.app_context()
app_ctx.push()
test_app = app.test_client()
db.drop_all()
db.create_all()
def tearDown():
app_ctx.pop()
在跑单元测试的时候,需要先启动一个testing配置的mock服务,然后先删除数据库,并初始化一个新的数据库,在测试结束之后,再释放初始化push的app_context。
一般而言,Flask服务需要测试的关注点主要是models,functions,以及views。当然views就是一些页面的测试,更加类似于用selenium实现。
先简单介绍对一个api和models的测试。
def test_logs_api():
resp = tests.test_app.get('/api/test')
assert_equal(resp.status_code, 200)
def test_log():
log = models.Log(
date_created=datetime.datetime.now(),
result=111,
job_name=111,
)
db.session.add(log)
db.session.commit()
log_id =models.Log.query.filter(models.Log.result ==
111).first()
assert log_id is not None
都很简单一目了然,我的经验就是对一个方法或者实例进行测试的时候,只用一个测试。这样方便以后维护,也使得逻辑更加清晰。
然后在命令行用nose调用执行:
nosetests -v --with-coverage --cover-package=app
然后可以看到测试结果和覆盖率的结果
Name Stmts Miss Cover
app/init.py 44 0 100%
app/config.py 58 3 95%
app/main/init.py 3 0 100%
app/main/agent.py 37 16 57%
app/main/api.py 135 53 61%
app/main/schedule.py 37 25 32%
app/models.py 84 13 85%
app/util/init.py 0 0 100%
Ran 6 tests in 6.905s
OK
当真正去写单元测试的时候,才会意识到自己之前只是为了实现业务而工作,虽然基本实现了业务,但是很多function无法开展单元测试,才真正理解解耦,函数式编程。
我们在编程中应该以测试的思想去影响设计结构,就是我们常说的测试驱动开发,缺乏测试的思想会给软件项目带来巨大的潜在隐患。
当然测试不是万灵药,软件开发是艰难的工作。为了争取成功,我们必须时刻牢记真正的目标。不但要解决问题,而且要简洁,高效,优雅的去实现。
nose官网
pytest官网
监视代码复杂度
Python编写高质量代码