测试
按照我们平时的开发习惯,是不是接下来就应该开发业务代码了?当然这样是可以的,但是我们这里不这样做,我们让测试先行,让测试来驱动开发,这样有什么好处呢?TDD
最重要的功能就是保障代码的正确性,能够迅速发现、定位bug
。关于TDD
更多的知识可以自行Google
。提供一个篇IBM关于TDD
介绍的文章:浅谈测试驱动开发(TDD)
首先新增测试用的依赖包:Flask-Testing
到requirements.txt
文件下面(记得用pip
命令安装哦~):
Flask-Testing==0.7.1
在project
目录下面新建一个tests
的目录,然后在该目录使用下面新增几个文件:
(tdd3)$ touch __init__.py base.py test_config.py test_users.py
如果你对Flask-Testing
还不太熟悉,在编写测试文件之前,可以先简单查看下文档。
更新base.py
文件:
# project/tests/base.py from flask_testing import TestCase from project import app, db class BaseTestCase(TestCase): def create_app(self): app.config.from_object('project.config.TestingConfig') return app def setUp(self): db.create_all() db.session.commit() def tearDown(self): db.session.remove() db.drop_all()
首先,我们确保测试使用的数据库URI
不是生产环境的,避免对线上环境产生任何影响。每次测试的时候创建数据表、完成后删除表,确保能够清理测试数据。
必须指定一个create_app 方法,并且返回flask app 实例,如果没有定义将会抛出NotImplementedError异常。
另外在tearDown
方法中增加db.session.remove()
方法,这样能够确保每次测试完成的时候能够将SQLAlchemy
的session属性移除掉,在下一次测试的时候始终是一个新的session。
更新test_config.py
文件:
from flask import current_app from flask_testing import TestCase from project import app class TestDevelopmentConfig(TestCase): def create_app(self): app.config.from_object('project.config.DevelopmentConfig') return app def test_app_is_development(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret') self.assertTrue(app.config['DEBUG'] is True) self.assertFalse(current_app is None) self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == 'mysql+pymysql://root:root321@users-db:3306/users_dev' ) class TestTestingConfig(TestCase): def create_app(self): app.config.from_object('project.config.TestingConfig') return app def test_app_is_testing(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret') self.assertTrue(app.config['DEBUG']) self.assertTrue(app.config['TESTING']) self.assertFalse(app.config['PRESERVE_CONTEXT_ON_EXCEPTION']) self.assertTrue( app.config['SQLALCHEMY_DATABASE_URI'] == 'mysql+pymysql://root:root321@users-db:3306/users_test' ) class TestProductionConfig(TestCase): def create_app(self): app.config.from_object('project.config.ProductionConfig') return app def test_app_is_production(self): self.assertTrue(app.config['SECRET_KEY'] == 'secret') self.assertFalse(app.config['DEBUG']) self.assertFalse(app.config['TESTING'])
更新test_users.py
文件:
import json from project.tests.base import BaseTestCase class TestUserService(BaseTestCase): def test_users(self): """确保ping的服务正常.""" response = self.client.get('/ping') data = json.loads(response.data.decode()) self.assertEqual(response.status_code, 200) self.assertIn('pong', data['message']) self.assertIn('success', data['status'])
然后我们在manage.py
中新增一个测试的命令行支持:
import unittest from flask_script import Manager from project import app, db manager = Manager(app) @manager.command def recreate_db(): """重新创建数据表.""" db.drop_all() db.create_all() db.session.commit() @manager.command def test(): """运行测试.""" tests = unittest.TestLoader().discover('project/tests', pattern='test_*.py') result = unittest.TextTestRunner(verbosity=2).run(tests) if result.wasSuccessful(): return 0 return 1 if __name__ == '__main__': manager.run()
unittest
支持非常简单的测试发现,为了兼容测试发现,所有的测试文件(test_xxx.py)必须是可以从项目的顶级目录导入的包或者模块,我们这里就是利用TestLoader.discover()
方法去发现project/tests包下面的所有的test_xxx.py测试文件。然后使用TextTestRunner用来执行测试用例,对测试进行编排并把结果返回给用户。
如果是单个的测试文件,推荐你将所有的测试方法放在同一个文件中,这样能够方便的使用unittest.main()
方法执行:
import unittest import flask_testing # TODO your test cases if __name__ == '__main__': unittest.main()
然后可以执行命令python test_xxx.py进行测试。
由于我们这里有test_config.py
和test_users.py
两个业务不一样的测试用例,所以我们使用test runner
来收集所有的测试结果并生成最后的测试报告。
由于我们更新了依赖包,所以需要重新构建镜像:
(tdd3)$ docker-compose up -d --build
然后运行测试命令:
(tdd3)$ docker-compose run users-service python manage.py test
然后我们可以看到类似于下面的测试没有通过的提示信息:
====================================================================== FAIL: test_app_is_development (test_config.TestDevelopmentConfig) ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/src/app/project/tests/test_config.py", line 13, in test_app_is_development self.assertTrue(app.config['SECRET_KEY'] == 'secret') AssertionError: False is not true
这是因为我们的app.config
中还没有SECRET_KEY这个key,所以assertTrue
肯定是不会通过的,然后我们在config.py
的BaseConfig类中新增SECRET_KEY属性:
class BaseConfig: """基础配置""" DEBUG = False TESTING = False SQLALCHEMY_TRACK_MODIFICATIONS = False SECRET_KEY = 'secret'
重新执行测试命令:
(tdd3)$ docker-compose run users-service python manage.py test Starting users-db ... done test_app_is_development (test_config.TestDevelopmentConfig) ... ok test_app_is_production (test_config.TestProductionConfig) ... ok test_app_is_testing (test_config.TestTestingConfig) ... ok test_users (test_users.TestUserService) 确保ping的服务正常. ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.124s OK
测试通过~~~