Skip to content

Commit e2f537a

Browse files
committed
📝 Update testing
* Rearrange testing section * pytest vs unittest * pytest monkeypatching * Mocking with Typer * TDD glossary entry
1 parent 8bbd42a commit e2f537a

File tree

7 files changed

+129
-47
lines changed

7 files changed

+129
-47
lines changed

docs/appendix/glossary.rst

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -712,8 +712,27 @@ Glossary
712712

713713
Test-driven development
714714
TDD
715-
A software development strategy in which the tests are written before the
716-
code.
715+
A technique for creating software that guides software development by
716+
writing tests. It was developed in the late 1990s by Kent Beck as part of
717+
Extreme Programming. Essentially, it involves repeating three simple
718+
steps:
719+
720+
* Write a test for the next feature to be added.
721+
* Write the function code until the test passes.
722+
* Refactor both the new and old code to make it well structured.
723+
724+
Although these three steps, often summarised as *‘red – green –
725+
refactor’*, form the core of the process, there is also an important
726+
first step, in which a list of test cases is created. One of these tests
727+
is then selected, *‘Red – Green – Refactor’* is applied to it, and the
728+
next test is selected. During the process, further tests are added to
729+
this list.
730+
731+
.. seealso::
732+
* `Canon TDD <https://tidyfirst.substack.com/p/canon-tdd>`_ by Kent
733+
Beck
734+
* `Test-driven development by example
735+
<https://archive.org/details/est-driven-development-by-example/test-driven-development-by-example/>`_ by Kent Beck
717736

718737
``try``
719738
A keyword that protects a part of the code that can throw an

docs/test/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ Basically, a distinction is made between static and dynamic test procedures.
1919
:titlesonly:
2020
:hidden:
2121

22-
pytest/index
2322
unittest
23+
pytest/index
2424
mock
2525
hypothesis
2626
tox

docs/test/mock.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ We can then simply use this fixture to test the version in
123123
def test_version(items_cli):
124124
assert items_cli("version") == items.__version__
125125
126+
.. seealso::
127+
`Typer Learn Testing <https://typer.tiangolo.com/tutorial/testing/>`_
128+
126129
Mocking of attributes
127130
---------------------
128131

docs/test/pytest/builtin-fixtures.rst

Lines changed: 78 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -270,38 +270,53 @@ code is restored and everything that was changed by the patch is undone.
270270

271271
The ``monkeypatch`` fixture offers the following functions:
272272

273-
+-------------------------------------------------------+-----------------------+
274-
| Function | Description |
275-
+=======================================================+=======================+
276-
| :samp:`setattr(TARGET, NAME, VALUE, raising=True)` | sets an attribute |
277-
| [1]_ | |
278-
+-------------------------------------------------------+-----------------------+
279-
| :samp:`delattr(TARGET, NAME, raising=True)` [1]_ | deletes an attribute |
280-
+-------------------------------------------------------+-----------------------+
281-
| :samp:`setitem(DICT, NAME, VALUE)` | sets a dict entry |
282-
| | |
283-
+-------------------------------------------------------+-----------------------+
284-
| :samp:`delitem(DICT, NAME, raising=True)` [1]_ | deletes a dict entry |
285-
+-------------------------------------------------------+-----------------------+
286-
| :samp:`setenv(NAME, VALUE, prepend=None)` [2]_ | sets an environment |
287-
| | variable |
288-
+-------------------------------------------------------+-----------------------+
289-
| :samp:`delenv(NAME, raising=True)` [1]_ | deletes an environment|
290-
| | variable |
291-
+-------------------------------------------------------+-----------------------+
292-
| :samp:`syspath_prepend(PATH)` | expands the path |
293-
| | ``sys.path`` |
294-
+-------------------------------------------------------+-----------------------+
295-
| :samp:`chdir(PATH)` | changes the current |
296-
| | working directory |
297-
+-------------------------------------------------------+-----------------------+
273+
+-----------------------------------------------+-----------------------+
274+
| Function | Description |
275+
+===============================================+=======================+
276+
| :meth:`monkeypatch.setattr(obj, name, value, | sets an attribute |
277+
| raising=True) | |
278+
| <pytest.MonkeyPatch.setattr>` | |
279+
| [1]_ | |
280+
+-----------------------------------------------+-----------------------+
281+
| :meth:`monkeypatch.delattr(obj, name, | deletes an attribute |
282+
| raising=True) | |
283+
| <pytest.MonkeyPatch.delattr>` | |
284+
| [1]_ | |
285+
+-----------------------------------------------+-----------------------+
286+
| :meth:`monkeypatch.setitem(mapping, name, | sets a dict entry |
287+
| value) <pytest.MonkeyPatch.setitem>` | |
288+
+-----------------------------------------------+-----------------------+
289+
| :meth:`monkeypatch.delitem(obj, name, | deletes a dict entry |
290+
| raising=True) <pytest.MonkeyPatch.delitem>` | |
291+
| [1]_ | |
292+
+-----------------------------------------------+-----------------------+
293+
| :meth:`monkeypatch.setenv(name, value, | sets an environment |
294+
| prepend=None) <pytest.MonkeyPatch.setenv>` | variable |
295+
| [2]_ | |
296+
+-----------------------------------------------+-----------------------+
297+
| :meth:`monkeypatch.delenv(name, raising=True) | deletes an environment|
298+
| <pytest.MonkeyPatch.delenv>` | variable |
299+
| [1]_ | |
300+
+-----------------------------------------------+-----------------------+
301+
| :meth:`monkeypatch.syspath_prepend(path) | expands the path |
302+
| <pytest.MonkeyPatch.syspath_prepend>` | :py:data:`sys.path` |
303+
+-----------------------------------------------+-----------------------+
304+
| :meth:`monkeypatch.chdir(path) | changes the current |
305+
| <pytest.MonkeyPatch.chdir>` | working directory |
306+
+-----------------------------------------------+-----------------------+
307+
| :meth:`monkeypatch.context() | changes the current |
308+
| <pytest.MonkeyPatch.context>` | context |
309+
+-----------------------------------------------+-----------------------+
298310

299311
.. [1] The ``raising`` :term:`parameter` tells pytest whether an exception
300312
should be thrown if the element is not (yet) present.
301313
.. [2] The ``prepend`` :term:`parameter` of ``setenv()`` can be a character. If
302314
it is set, the value of the environment variable is changed to
303315
:samp:`{VALUE} + prepend + {OLD_VALUE}`
304316
317+
RMonkey patching of environment variables
318+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
319+
305320
We can use ``monkeypatch`` to redirect the :abbr:`CLI (Command Line Interface)`
306321
to a temporary directory for the database in two ways. Both methods require
307322
knowledge of the application code. Let’s take a look at the method
@@ -392,6 +407,44 @@ environment variable :envvar:`ITEMS_DB_DIR` that can be easily patched:
392407
monkeypatch.setenv("ITEMS_DB_DIR", str(tmp_path))
393408
assert run_items_cli("config") == str(tmp_path)
394409
410+
Monkey patching dictionaries
411+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
412+
413+
The path could also have been specified in a dictionary, for example:
414+
415+
.. code-block:: python
416+
:caption: conf.py
417+
418+
DEFAULT_CONFIG = {"database": "items_db"}
419+
420+
421+
def create_connection(config=None):
422+
"""Create a connection string from input or defaults."""
423+
config = config or DEFAULT_CONFIG
424+
return f"Location={config['database']};"
425+
426+
For testing purposes, we can change the values in the ``DEFAULT_CONFIG``
427+
dictionary:
428+
429+
.. code-block:: python
430+
:caption: tests/test_conf.py
431+
432+
from items import conf
433+
434+
435+
def test_connection(monkeypatch):
436+
monkeypatch.setitem(conf.DEFAULT_CONFIG, "database", "test_db")
437+
438+
Alternatively, you could have defined a fixture with:
439+
440+
.. code-block:: python
441+
:caption: tests/conftest.py
442+
443+
@pytest.fixture
444+
def mock_test_database(monkeypatch):
445+
"""Set the DEFAULT_CONFIG database to test_db."""
446+
monkeypatch.setitem(app.DEFAULT_CONFIG, "database", "test_db")
447+
395448
Remaining built-in fixtures
396449
---------------------------
397450

docs/test/pytest/fixtures.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ But before you familiarise yourself with fixtures and use them to test Items,
2020
let’s take a look at a small example fixture and learn how fixtures and test
2121
functions are connected.
2222

23+
.. seealso::
24+
* `pytest fixtures <https://docs.pytest.org/en/latest/explanation/fixtures.html>`_
25+
* `pytest fixtures reference
26+
<https://docs.pytest.org/en/latest/reference/fixtures.html>`_
27+
* `How to use fixtures
28+
<https://docs.pytest.org/en/latest/how-to/fixtures.html#how-to-fixtures>`_
29+
2330
First steps with fixtures
2431
-------------------------
2532

docs/test/pytest/index.rst

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ pytest
44
:doc:`pytest <pytest:index>` is an alternative to Python’s :doc:`../unittest`
55
module that simplifies testing even further.
66

7-
Features
8-
--------
9-
10-
* More detailed information about failed ``assert`` statements
11-
* Automatic detection of test modules and functions
12-
* Modular fixtures for the management of small or :term:`parameterised
13-
<Parameter>`, long-lived test resources
14-
* Can also execute unit tests without presets
15-
* Extensive plug-in architecture, with over 800 external plug-ins
7+
* pytest automatically recognises tests based on filenames and functions that
8+
start with ``test_``, while unittest derives test classes and methods from
9+
:class:`unittest.TestCase`. This results in simpler, more readable syntax with
10+
less boilerplate code.
11+
* unittest provides a set of assertion methods (for example,
12+
:func:`assertEqual`, :func:`assertTrue`, :func:`assertRaises`). With pytest,
13+
the same assertions can be defined, but using Python’s standard :func:`assert`
14+
statement. This often results in more meaningful error messages and better
15+
introspection.
16+
* unittest only provides :func:`setUp` and :func:`tearDown` methods for
17+
fixtures. In pytest, on the other hand, :doc:`fixtures <fixtures>` are defined
18+
as functions, which promotes reusability and simplifies the management of
19+
test dependencies.
20+
* Parametrised tests are possible in unittest, but require additional effort.
21+
pytest, however, includes the :doc:`decorator <../../ functions/decorators>`
22+
``@pytest.mark.parametrize``, which makes it easy to run test functions with
23+
different inputs and expected outputs.
24+
* pytest has an extensive ecosystem with over 800 :doc:`plugins` for advanced
25+
testing requirements; unittest is more limited in its extensibility.
1626

1727
Installation
1828
------------

docs/test/unittest.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ It provides the following test concepts:
1515
Test Fixture
1616
is a consistent test environment.
1717

18-
.. seealso::
19-
* `pytest fixtures
20-
<https://docs.pytest.org/en/latest/explanation/fixtures.html>`_
21-
* `About fixtures
22-
<https://docs.pytest.org/en/latest/explanation/fixtures.html#about-fixtures>`_
23-
* `Fixtures reference
24-
<https://docs.pytest.org/en/latest/reference/fixtures.html>`_
25-
* `How to use fixtures
26-
<https://docs.pytest.org/en/latest/how-to/fixtures.html#how-to-fixtures>`_
27-
2818
Test Suite
2919
is a collection of several :term:`test cases <Test Case>`.
3020

0 commit comments

Comments
 (0)