第2章 测试函数
测试Tasks程序
被测程序Tasks的结构如下
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 | tasks_proj/
├── CHANGELOG.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── setup.py
├── src
│ └── tasks
│ ├── __init__.py
│ ├── api.py
│ ├── cli.py
│ ├── config.py
│ ├── tasksdb_pymongo.py
│ └── tasksdb_tinydb.py
└── tests
├── conftest.py
├── pytest.ini
├── func
│ ├── __init__.py
│ ├── test_add.py
│ └── ...
└── unit
├── __init__.py
├── test_task.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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | # python3 setup.py install
running install
Checking .pth file support in /usr/local/lib/python3.5/dist-packages/
/usr/bin/python3 -E -c pass
TEST PASSED: /usr/local/lib/python3.5/dist-packages/ appears to support .pth files
running bdist_egg
running egg_info
creating src/tasks.egg-info
writing src/tasks.egg-info/PKG-INFO
writing dependency_links to src/tasks.egg-info/dependency_links.txt
writing top-level names to src/tasks.egg-info/top_level.txt
writing requirements to src/tasks.egg-info/requires.txt
writing entry points to src/tasks.egg-info/entry_points.txt
writing manifest file 'src/tasks.egg-info/SOURCES.txt'
reading manifest file 'src/tasks.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'src/tasks.egg-info/SOURCES.txt'
installing library code to build/bdist.linux-x86_64/egg
running install_lib
running build_py
creating build
creating build/lib
creating build/lib/tasks
copying src/tasks/api.py -> build/lib/tasks
copying src/tasks/tasksdb_pymongo.py -> build/lib/tasks
copying src/tasks/tasksdb_tinydb.py -> build/lib/tasks
copying src/tasks/config.py -> build/lib/tasks
copying src/tasks/__init__.py -> build/lib/tasks
copying src/tasks/cli.py -> build/lib/tasks
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/egg
creating build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/api.py -> build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/tasksdb_pymongo.py -> build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/tasksdb_tinydb.py -> build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/config.py -> build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/__init__.py -> build/bdist.linux-x86_64/egg/tasks
copying build/lib/tasks/cli.py -> build/bdist.linux-x86_64/egg/tasks
byte-compiling build/bdist.linux-x86_64/egg/tasks/api.py to api.cpython-35.pyc
byte-compiling build/bdist.linux-x86_64/egg/tasks/tasksdb_pymongo.py to tasksdb_pymongo.cpython-35.pyc
byte-compiling build/bdist.linux-x86_64/egg/tasks/tasksdb_tinydb.py to tasksdb_tinydb.cpython-35.pyc
byte-compiling build/bdist.linux-x86_64/egg/tasks/config.py to config.cpython-35.pyc
byte-compiling build/bdist.linux-x86_64/egg/tasks/__init__.py to __init__.cpython-35.pyc
byte-compiling build/bdist.linux-x86_64/egg/tasks/cli.py to cli.cpython-35.pyc
creating build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/PKG-INFO -> build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/SOURCES.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/dependency_links.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/entry_points.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/requires.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
copying src/tasks.egg-info/top_level.txt -> build/bdist.linux-x86_64/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist/tasks-0.1.0-py3.5.egg' and adding 'build/bdist.linux-x86_64/egg' to it
removing 'build/bdist.linux-x86_64/egg' (and everything under it)
Processing tasks-0.1.0-py3.5.egg
Copying tasks-0.1.0-py3.5.egg to /usr/local/lib/python3.5/dist-packages
Adding tasks 0.1.0 to easy-install.pth file
Installing tasks script to /usr/local/bin
Installed /usr/local/lib/python3.5/dist-packages/tasks-0.1.0-py3.5.egg
Processing dependencies for tasks==0.1.0
Searching for tinydb
Reading https://pypi.python.org/simple/tinydb/
Best match: tinydb 3.9.0.post1
Downloading https://files.pythonhosted.org/packages/82/f2/c26a5c4b8486349dacc8abba33c495b77a0b67052b5c79c5d5c90da49b95/tinydb-3.9.0.post1.tar.gz#sha256=88793016ba94267e3606372e1564d4e797eae855acd5a48352011bfdcf8ee7ad
Processing tinydb-3.9.0.post1.tar.gz
Writing /tmp/easy_install-_cvtvm1f/tinydb-3.9.0.post1/setup.cfg
Running tinydb-3.9.0.post1/setup.py -q bdist_egg --dist-dir /tmp/easy_install-_cvtvm1f/tinydb-3.9.0.post1/egg-dist-tmp-xpn6fitk
/usr/lib/python3.5/distutils/dist.py:261: UserWarning: Unknown distribution option: 'python_requires'
warnings.warn(msg)
zip_safe flag not set; analyzing archive contents...
Installed /tmp/easy_install-_cvtvm1f/tinydb-3.9.0.post1/.eggs/pytest_runner-4.2-py3.5.egg
/usr/lib/python3.5/distutils/dist.py:261: UserWarning: Unknown distribution option: 'project_urls'
warnings.warn(msg)
Moving tinydb-3.9.0.post1-py3.5.egg to /usr/local/lib/python3.5/dist-packages
Adding tinydb 3.9.0.post1 to easy-install.pth file
Installed /usr/local/lib/python3.5/dist-packages/tinydb-3.9.0.post1-py3.5.egg
Searching for six==1.10.0
Best match: six 1.10.0
Adding six 1.10.0 to easy-install.pth file
Using /usr/lib/python3/dist-packages
Searching for click==6.7
Best match: click 6.7
Adding click 6.7 to easy-install.pth file
Using /usr/local/lib/python3.5/dist-packages
Finished processing dependencies for tasks==0.1.0
|
其他安装方式: 在tasks_proj执行:“pip install . ” 或者 “pip install -e .”, 或者在上级目录执行“pip install -e tasks_proj”。 -e 为editor,可以编辑源码。
被测程序演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $ tasks add "reading pytest!" --owner Andrew
$ tasks add "do something else"
$ tasks list
ID owner done summary
-- ----- ---- -------
1 Andrew False reading pytest!
2 False do something else
$ tasks update 2 --owner Brian
$ tasks list
ID owner done summary
-- ----- ---- -------
1 Andrew False reading pytest!
2 Brian False do something else
$ tasks update 1 --done True
$ tasks list
ID owner done summary
-- ----- ---- -------
1 Andrew True reading pytest!
2 Brian False do something else
$ tasks delete 1
$ tasks list
ID owner done summary
-- ----- ---- -------
2 Brian False do something else
|
把前面的单元测试进行汇总:
/ch2/tasks_proj/tests/unit/test_task.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 27 28 29 30 31 32 33 34 35 36 | """Test the Task data type."""
from tasks import Task
def test_asdict():
"""_asdict() should return a dictionary."""
t_task = Task('do something', 'okken', True, 21)
t_dict = t_task._asdict()
expected = {'summary': 'do something',
'owner': 'okken',
'done': True,
'id': 21}
assert t_dict == expected
def test_replace():
"""replace() should change passed in fields."""
t_before = Task('finish book', 'brian', False)
t_after = t_before._replace(id=10, done=True)
t_expected = Task('finish book', 'brian', True, 10)
assert t_after == t_expected
def test_defaults():
"""Using no parameters should invoke defaults."""
t1 = Task()
t2 = Task(None, None, False, None)
assert t1 == t2
def test_member_access():
"""Check .field functionality of namedtuple."""
t = Task('buy milk', 'brian')
assert t.summary == 'buy milk'
assert t.owner == 'brian'
assert (t.done, t.id) == (False, None)
|
执行
1 2 3 4 5 6 7 8 9 | andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/unit$ pytest test_task.py
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 4 items
test_task.py .... [100%]
======================================== 4 passed in 0.02 seconds =========================================
|
断言
test_task_fail.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from tasks import Task
def test_task_equality():
"""Different tasks should not be equal."""
t1 = Task('sit there', 'brian')
t2 = Task('do something', 'okken')
assert t1 == t2
def test_dict_equality():
"""Different tasks compared as dicts should not be equal."""
t1_dict = Task('make sandwich', 'okken')._asdict()
t2_dict = Task('make sandwich', 'okkem')._asdict()
assert t1_dict == t2_dict
|
执行:
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 27 28 29 30 31 32 33 34 35 36 | ~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/unit$ pytest test_task_fail.py
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 2 items
test_task_fail.py FF [100%]
================================================ FAILURES =================================================
___________________________________________ test_task_equality ____________________________________________
def test_task_equality():
"""Different tasks should not be equal."""
t1 = Task('sit there', 'brian')
t2 = Task('do something', 'okken')
> assert t1 == t2
E AssertionError: assert Task(summary=...alse, id=None) == Task(summary='...alse, id=None)
E At index 0 diff: 'sit there' != 'do something'
E Use -v to get the full diff
test_task_fail.py:9: AssertionError
___________________________________________ test_dict_equality ____________________________________________
def test_dict_equality():
"""Different tasks compared as dicts should not be equal."""
t1_dict = Task('make sandwich', 'okken')._asdict()
t2_dict = Task('make sandwich', 'okkem')._asdict()
> assert t1_dict == t2_dict
E AssertionError: assert OrderedDict([...('id', None)]) == OrderedDict([(...('id', None)])
E Omitting 3 identical items, use -vv to show
E Differing items:
E {'owner': 'okken'} != {'owner': 'okkem'}
E Use -v to get the full diff
test_task_fail.py:16: AssertionError
======================================== 2 failed in 0.04 seconds =========================================
|
预期异常
tasks/api.py的函数如下:
1 2 3 4 5 6 7 8 9 10 | def add(task): # type: (Task) -> int
def get(task_id): # type: (int) -> Task
def list_tasks(owner=None): # type: (str|None) -> list of Task
def count(): # type: (None) -> int
def update(task_id, task): # type: (int, Task) -> None
def delete(task_id): # type: (int) -> None
def delete_all(): # type: () -> None
def unique_id(): # type: () -> int
def start_tasks_db(db_path, db_type): # type: (str, str) -> None
def stop_tasks_db(): # type: () -> None
|
test_api_exceptions.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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | """Test for expected exceptions from using the API wrong."""
import pytest
import tasks
def test_add_raises():
"""add() should raise an exception with wrong type param."""
with pytest.raises(TypeError):
tasks.add(task='not a Task object')
@pytest.mark.smoke
def test_list_raises():
"""list() should raise an exception with wrong type param."""
with pytest.raises(TypeError):
tasks.list_tasks(owner=123)
@pytest.mark.get
@pytest.mark.smoke
def test_get_raises():
"""get() should raise an exception with wrong type param."""
with pytest.raises(TypeError):
tasks.get(task_id='123')
class TestUpdate():
"""Test expected exceptions with tasks.update()."""
def test_bad_id(self):
"""A non-int id should raise an excption."""
with pytest.raises(TypeError):
tasks.update(task_id={'dict instead': 1},
task=tasks.Task())
def test_bad_task(self):
"""A non-Task task should raise an excption."""
with pytest.raises(TypeError):
tasks.update(task_id=1, task='not a task')
def test_delete_raises():
"""delete() should raise an exception with wrong type param."""
with pytest.raises(TypeError):
tasks.delete(task_id=(1, 2, 3))
def test_start_tasks_db_raises():
"""Make sure unsupported db raises an exception."""
with pytest.raises(ValueError) as excinfo:
tasks.start_tasks_db('some/great/path', 'mysql')
exception_msg = excinfo.value.args[0]
assert exception_msg == "db_type must be a 'tiny' or 'mongo'"
|
标记测试函数
基于标签实现,参见上面代码。
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/func$ pytest test_api_exceptions.py
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 7 items
test_api_exceptions.py ....... [100%]
======================================== 7 passed in 0.03 seconds =========================================
andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/func$ pytest test_api_exceptions.py -v -m "smoke"
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- /usr/bin/python3
cachedir: ../.pytest_cache
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 7 items / 5 deselected
test_api_exceptions.py::test_list_raises PASSED [ 50%]
test_api_exceptions.py::test_get_raises PASSED [100%]
================================= 2 passed, 5 deselected in 0.02 seconds ==================================
andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/func$ pytest test_api_exceptions.py -v -m "smoke and get"
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- /usr/bin/python3
cachedir: ../.pytest_cache
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 7 items / 6 deselected
test_api_exceptions.py::test_get_raises PASSED [100%]
================================= 1 passed, 6 deselected in 0.01 seconds ==================================
andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/func$ pytest test_api_exceptions.py -v -m "smoke and not get"
=========================================== test session starts ===========================================
platform linux -- Python 3.5.2, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 -- /usr/bin/python3
cachedir: ../.pytest_cache
rootdir: /home/andrew/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests, inifile: pytest.ini
collected 7 items / 6 deselected
test_api_exceptions.py::test_list_raises PASSED [100%]
================================= 1 passed, 6 deselected in 0.01 seconds ==================================
andrew@andrew-PowerEdge-T630:~/code/china-testing/python3_libraries/pytest_testing/ch2/tasks_proj/tests/func$
|