commit | author | age
|
b731b5
|
1 |
.. _qtut_unit_testing: |
PE |
2 |
|
86e187
|
3 |
============================= |
SP |
4 |
05: Unit Tests and ``pytest`` |
|
5 |
============================= |
b1b922
|
6 |
|
PE |
7 |
Provide unit testing for our project's Python code. |
86e187
|
8 |
|
b1b922
|
9 |
|
PE |
10 |
Background |
|
11 |
========== |
|
12 |
|
86e187
|
13 |
As the mantra says, "Untested code is broken code." The Python community has |
SP |
14 |
had a long culture of writing test scripts which ensure that your code works |
|
15 |
correctly as you write it and maintain it in the future. Pyramid has always had |
|
16 |
a deep commitment to testing, with 100% test coverage from the earliest |
|
17 |
pre-releases. |
b1b922
|
18 |
|
86e187
|
19 |
Python includes a :ref:`unit testing framework |
SP |
20 |
<python:unittest-minimal-example>` in its standard library. Over the years a |
|
21 |
number of Python projects, such as :ref:`pytest <pytest:features>`, have |
|
22 |
extended this framework with alternative test runners that provide more |
|
23 |
convenience and functionality. The Pyramid developers use ``pytest``, which |
|
24 |
we'll use in this tutorial. |
b1b922
|
25 |
|
86e187
|
26 |
Don't worry, this tutorial won't be pedantic about "test-driven development" |
SP |
27 |
(TDD). We'll do just enough to ensure that, in each step, we haven't majorly |
|
28 |
broken the code. As you're writing your code, you might find this more |
|
29 |
convenient than changing to your browser constantly and clicking reload. |
b1b922
|
30 |
|
86e187
|
31 |
We'll also leave discussion of `pytest-cov |
9ce94f
|
32 |
<https://pytest-cov.readthedocs.io/en/latest/>`_ for another section. |
86e187
|
33 |
|
b1b922
|
34 |
|
PE |
35 |
Objectives |
|
36 |
========== |
|
37 |
|
86e187
|
38 |
- Write unit tests that ensure the quality of our code. |
b1b922
|
39 |
|
86e187
|
40 |
- Install a Python package (``pytest``) which helps in our testing. |
SP |
41 |
|
b1b922
|
42 |
|
PE |
43 |
Steps |
|
44 |
===== |
|
45 |
|
34a7a8
|
46 |
#. First we copy the results of the previous step. |
b1b922
|
47 |
|
34a7a8
|
48 |
.. code-block:: bash |
b1b922
|
49 |
|
34a7a8
|
50 |
cd ..; cp -r debugtoolbar unit_testing; cd unit_testing |
b1b922
|
51 |
|
34a7a8
|
52 |
#. Add ``pytest`` to our project's dependencies in ``setup.py`` as a :term:`Setuptools` "extra": |
b1b922
|
53 |
|
34a7a8
|
54 |
.. literalinclude:: unit_testing/setup.py |
SP |
55 |
:language: python |
|
56 |
:linenos: |
beeb8e
|
57 |
:emphasize-lines: 15 |
b1b922
|
58 |
|
34a7a8
|
59 |
#. Install our project and its newly added dependency. |
b15a06
|
60 |
Note that we use the extra specifier ``[dev]`` to install testing requirements for development and surround it and the period with double quote marks. |
b1b922
|
61 |
|
34a7a8
|
62 |
.. code-block:: bash |
SP |
63 |
|
23fbca
|
64 |
$VENV/bin/pip install -e ".[dev]" |
34a7a8
|
65 |
|
SP |
66 |
#. Now we write a simple unit test in ``unit_testing/tutorial/tests.py``: |
|
67 |
|
|
68 |
.. literalinclude:: unit_testing/tutorial/tests.py |
|
69 |
:linenos: |
|
70 |
|
|
71 |
#. Now run the tests: |
|
72 |
|
|
73 |
.. code-block:: bash |
b1b922
|
74 |
|
PE |
75 |
|
34a7a8
|
76 |
$VENV/bin/pytest tutorial/tests.py -q |
SP |
77 |
. |
|
78 |
1 passed in 0.14 seconds |
b1b922
|
79 |
|
PE |
80 |
|
|
81 |
Analysis |
|
82 |
======== |
|
83 |
|
86e187
|
84 |
Our ``tests.py`` imports the Python standard unit testing framework. To make |
SP |
85 |
writing Pyramid-oriented tests more convenient, Pyramid supplies some |
|
86 |
``pyramid.testing`` helpers which we use in the test setup and teardown. Our |
|
87 |
one test imports the view, makes a dummy request, and sees if the view returns |
|
88 |
what we expect. |
b1b922
|
89 |
|
86e187
|
90 |
The ``tests.TutorialViewTests.test_hello_world`` test is a small example of a |
SP |
91 |
unit test. First, we import the view inside each test. Why not import at the |
|
92 |
top, like in normal Python code? Because imports can cause effects that break a |
|
93 |
test. We'd like our tests to be in *units*, hence the name *unit* testing. Each |
|
94 |
test should isolate itself to the correct degree. |
b1b922
|
95 |
|
86e187
|
96 |
Our test then makes a fake incoming web request, then calls our Pyramid view. |
SP |
97 |
We test the HTTP status code on the response to make sure it matches our |
|
98 |
expectations. |
b1b922
|
99 |
|
PE |
100 |
Note that our use of ``pyramid.testing.setUp()`` and |
|
101 |
``pyramid.testing.tearDown()`` aren't actually necessary here; they are only |
|
102 |
necessary when your test needs to make use of the ``config`` object (it's a |
|
103 |
Configurator) to add stuff to the configuration state before calling the view. |
34a7a8
|
104 |
|
86e187
|
105 |
|
65687f
|
106 |
Extra credit |
b1b922
|
107 |
============ |
PE |
108 |
|
86e187
|
109 |
#. Change the test to assert that the response status code should be ``404`` |
f63332
|
110 |
(meaning, not found). Run ``pytest`` again. Read the error report and see |
86e187
|
111 |
if you can decipher what it is telling you. |
b1b922
|
112 |
|
86e187
|
113 |
#. As a more realistic example, put the ``tests.py`` back as you found it, and |
SP |
114 |
put an error in your view, such as a reference to a non-existing variable. |
|
115 |
Run the tests and see how this is more convenient than reloading your |
|
116 |
browser and going back to your code. |
b1b922
|
117 |
|
PE |
118 |
#. Finally, for the most realistic test, read about Pyramid ``Response`` |
86e187
|
119 |
objects and see how to change the response code. Run the tests and see how |
SP |
120 |
testing confirms the "contract" that your code claims to support. |
b1b922
|
121 |
|
PE |
122 |
#. How could we add a unit test assertion to test the HTML value of the |
|
123 |
response body? |
|
124 |
|
|
125 |
#. Why do we import the ``hello_world`` view function *inside* the |
|
126 |
``test_hello_world`` method instead of at the top of the module? |
|
127 |
|
34a7a8
|
128 |
.. seealso:: See also :ref:`testing_chapter` and `Setuptools Declaring "Extras" (optional features with their own dependencies) <https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies>`_. |