自动化测试框架pytest教程2-测试函数

第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$ 

links