Python测试技巧
Python的相关测试工具很多。这里介绍下我的测试方案。主要涉及到:doctest / unittest / coverage / pytest
参考文章:
- http://www.cnblogs.com/liaofeifight/p/5148717.html
- https://stackoverflow.com/questions/42340772/how-can-i-integrate-doctests-with-unittests-test-discovery?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
doctest
doctest 与 pycharm 结合起来,非常好用。也是我测试用例的主力。 一边写程序,一边doctest就写好了。
有一些小技巧:
模糊匹配输出
def login(ent_id, username, tel): """ >>> login(ent_id, username, '01062149131') # doctest: +ELLIPSIS {...成功... """ pass
代码中执行
doctest
import doctest doctest.testmod(<model>)
终端中执行
doctest
python -m doctest <test_file_path>
unitest
这个用的太广了。涉及到一些复杂的测试用例,或者是不好写在doctest中的,我就用unitest。
目录结构
proj
├── ccuni
│ ├── __init__.py
│ └── ccuni.py
└── tests
├── __init__.py
└── test_ccuni.py
测试代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Tests for `ccuni` package."""
import unittest
from click.testing import CliRunner
# from ccuni import ccuni
from ccuni import cli
class TestCcuni(unittest.TestCase):
"""Tests for `ccuni` package."""
def setUp(self):
"""Set up test fixtures, if any."""
def tearDown(self):
"""Tear down test fixtures, if any."""
def test_000_something(self):
"""Test something."""
pass
def test_command_line_interface(self):
"""Test the CLI."""
runner = CliRunner()
result = runner.invoke(cli.main)
assert result.exit_code == 0
assert 'ccuni.cli.main' in result.output
help_result = runner.invoke(cli.main, ['--help'])
assert help_result.exit_code == 0
assert '--help Show this message and exit.' in help_result.output
运行unitest我一般在pycharm中用鼠标操作。 命令行我很少用unitest自己的,主要用pytest来跑
pytest
命令行下我主要用pytest
,把 unittest/doctest 整合起来跑
安装
pip install pytest
配置与运行
在项目的根目录下,创建 setup.cfg
[tool:pytest]
addopts = --doctest-modules
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ELLIPSIS
然后直接
pytest
coverage
coverage是看测试代码覆盖率的。
与pytest
这么结合起来
coverage run --source <package> -m py.test
coverage report
tox
涉及到多版本的python自动测试,用tox
项目根目录下创建tox.ini
其中flake8是代码规范检查。一会再说。
[tox]
envlist = py36, flake8
[travis]
python =
3.6: py36
3.5: py35
3.4: py34
[testenv:flake8]
basepython = python
deps = flake8
commands = flake8 ccuni --ignore E501
[testenv]
setenv =
PYTHONPATH = {toxinidir}
commands = pytest
flake8
代码的PEP08规范检查。使用很简单
flake8 <package> --ignore E501
最好把规则写在配置文件中。同样还是setup.cfg
[flake8]
ignore = E501
exclude = .git,__pycache__,docs,old,build,dist
max-complexity = 10
make
把以上都结合起来,写成自动化脚本,我用make
Makefile
内容见下。这是参考pydanney的cookiecutter-pypackage
脚本
.PHONY: clean clean-test clean-pyc clean-build docs help
.DEFAULT_GOAL := help
define BROWSER_PYSCRIPT
import os, webbrowser, sys
try:
from urllib import pathname2url
except:
from urllib.request import pathname2url
webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
endef
export BROWSER_PYSCRIPT
define PRINT_HELP_PYSCRIPT
import re, sys
for line in sys.stdin:
match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line)
if match:
target, help = match.groups()
print("%-20s %s" % (target, help))
endef
export PRINT_HELP_PYSCRIPT
BROWSER := python -c "$$BROWSER_PYSCRIPT"
help:
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
lint: ## check style with flake8
flake8 ccuni tests --ignore E501
test: ## run tests quickly with the default Python
pytest
test-all: ## run tests on every Python version with tox
tox
coverage: ## check code coverage quickly with the default Python
coverage run --source ccuni setup.py test
coverage report -m
coverage html
$(BROWSER) htmlcov/index.html
docs: ## generate Sphinx HTML documentation, including API docs
rm -f docs/ccuni.rst
rm -f docs/modules.rst
sphinx-apidoc -o docs/ ccuni
$(MAKE) -C docs clean
$(MAKE) -C docs html
$(BROWSER) docs/_build/html/index.html
servedocs: docs ## compile the docs watching for changes
watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
release: dist ## package and upload a release
twine upload dist/* -r hxt
dist: clean ## builds source and wheel package
python setup.py sdist
python setup.py bdist_wheel
ls -l dist
install: clean ## install the package to the active Python's site-packages
python setup.py install
其它
代码中调用以上测试框架,可以参考下面代码。
import coverage
import doctest
import unittest
import os
# import test_module
import my_module
cov = coverage.Coverage()
cov.start()
# running doctest by explicity naming the module
doctest.testmod(my_module)
# running unittests by just specifying the folder to look into
testLoad = unittest.TestLoader()
testSuite = testLoad.discover(start_dir=os.getcwd())
runner = unittest.TextTestRunner()
runner.run(testSuite)
cov.stop()
cov.save()
cov.html_report()
print("tests completed")
性能测试
除了cProfile以外,还有一个选择:yappi。这个与pycharm集成度较高。尤其是在监控:python -m xxx.xxx 的程序的时候。
pip install yappi
pytest
django中使用
pip install pytest-django
vi pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = config.settings
# -- recommended but optional:
python_files = tests.py test_*.py *_tests.py
然后与src
目录并列建立tests
文件夹
mkdir tests
vi tests/__init__.py
import sys
import os
myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../src')
使用pytest过程中,使用当前数据库
vi tests/conftest.py
import pytest
@pytest.fixture(scope='session')
def django_db_setup():
from django.conf import settings
settings.DATABASES['default'] = {
'ENGINE': 'django.db.backends.mysql',
'HOST': 'db.example.com',
'NAME': 'external_db',
}
mock数据
auth_file = MagicMock()
auth_file.auth_file.path = os.path.join(os.path.dirname(__file__), '20190129_md5.xlsx')
xxx = People()
mocker.patch.object(xxx, 'auth')
xxx.auth.path = 'kjkj'