Skip to content

Commit cffdf71

Browse files
committed
Improve README and bump version
1 parent 6477a61 commit cffdf71

5 files changed

Lines changed: 212 additions & 178 deletions

File tree

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
include README.rst
1+
include README.md
22
include LICENSE
33
include setup.py
44
include setup.cfg

README.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
[![PyPI version](https://badge.fury.io/py/pytest-describe.svg)](https://pypi.org/project/pytest-describe/)
2+
[![Workflow status](https://github.com/pytest-dev/pytest-describe/actions/workflows/main.yml/badge.svg)](https://github.com/pytest-dev/pytest-describe/actions)
3+
4+
# Describe-style plugin for pytest
5+
6+
**pytest-describe** is a plugin for [pytest](https://docs.pytest.org/)
7+
that allows tests to be written in arbitrary nested describe-blocks,
8+
similar to RSpec (Ruby) and Jasmine (JavaScript).
9+
10+
The main inspiration for this was
11+
a [video](https://www.youtube.com/watch?v=JJle8L8FRy0>) by Gary Bernhardt.
12+
13+
## Installation
14+
15+
You guessed it:
16+
17+
```sh
18+
pip install pytest-describe
19+
```
20+
21+
## Usage
22+
23+
Pytest will automatically find the plugin and use it when you run pytest.
24+
Running pytest will show that the plugin is loaded:
25+
26+
```sh
27+
$ pytest
28+
...
29+
plugins: describe-2.2.0
30+
...
31+
```
32+
33+
Tests can now be written in describe-blocks.
34+
Here is an example for testing a Wallet class:
35+
36+
```python
37+
import pytest
38+
39+
40+
class Wallet:
41+
42+
def __init__(self, initial_amount=0):
43+
self.balance = initial_amount
44+
45+
def spend_cash(self, amount):
46+
if self.balance < amount:
47+
raise ValueError(f'Not enough available to spend {amount}')
48+
self.balance -= amount
49+
50+
def add_cash(self, amount):
51+
self.balance += amount
52+
53+
54+
def describe_wallet():
55+
56+
def describe_start_empty():
57+
58+
@pytest.fixture
59+
def wallet():
60+
return Wallet()
61+
62+
def initial_amount(wallet):
63+
assert wallet.balance == 0
64+
65+
def add_cash(wallet):
66+
wallet.add_cash(80)
67+
assert wallet.balance == 80
68+
69+
def spend_cash(wallet):
70+
with pytest.raises(ValueError):
71+
wallet.spend_cash(10)
72+
73+
def describe_with_starting_balance():
74+
75+
@pytest.fixture
76+
def wallet():
77+
return Wallet(20)
78+
79+
def initial_amount(wallet):
80+
assert wallet.balance == 20
81+
82+
def describe_adding():
83+
84+
def add_little_cash(wallet):
85+
wallet.add_cash(5)
86+
assert wallet.balance == 25
87+
88+
def add_much_cash(wallet):
89+
wallet.add_cash(980)
90+
assert wallet.balance == 1000
91+
92+
def describe_spending():
93+
94+
def spend_cash(wallet):
95+
wallet.spend_cash(15)
96+
assert wallet.balance == 5
97+
98+
def spend_too_much_cash(wallet):
99+
with pytest.raises(ValueError):
100+
wallet.spend_cash(25)
101+
```
102+
103+
The default prefix for describe-blocks is `describe_`, but you can configure it
104+
in the pytest/python configuration file via `describe_prefixes` or
105+
via the command line option `--describe-prefixes`.
106+
107+
For example in your `pyproject.toml`:
108+
109+
```toml
110+
[tool.pytest.ini_options]
111+
describe_prefixes = ["custom_prefix_"]
112+
```
113+
114+
Functions prefixed with `_` in the describe-block are not collected as tests.
115+
This can be used to group helper functions. Otherwise, functions inside the
116+
describe-blocks need not follow any special naming convention.
117+
118+
```python
119+
def describe_function():
120+
121+
def _helper():
122+
return "something"
123+
124+
def it_does_something():
125+
value = _helper()
126+
...
127+
```
128+
129+
130+
## Why bother?
131+
132+
I've found that quite often my tests have one "dimension" more than my production
133+
code. The production code is organized into packages, modules, classes
134+
(sometimes), and functions. I like to organize my tests in the same way, but
135+
tests also have different *cases* for each function. This tends to end up with
136+
a set of tests for each module (or class), where each test has to name both a
137+
function and a *case*. For instance:
138+
139+
```python
140+
def test_my_function_with_default_arguments():
141+
def test_my_function_with_some_other_arguments():
142+
def test_my_function_throws_exception():
143+
def test_my_function_handles_exception():
144+
def test_some_other_function_returns_true():
145+
def test_some_other_function_returns_false():
146+
```
147+
148+
It's much nicer to do this:
149+
150+
```python
151+
def describe_my_function():
152+
def with_default_arguments():
153+
def with_some_other_arguments():
154+
def it_throws_exception():
155+
def it_handles_exception():
156+
157+
def describe_some_other_function():
158+
def it_returns_true():
159+
def it_returns_false():
160+
```
161+
162+
It has the additional advantage that you can have marks and fixtures that apply
163+
locally to each group of test function.
164+
165+
With pytest, it's possible to organize tests in a similar way with classes.
166+
However, I think classes are awkward. I don't think the convention of using
167+
camel-case names for classes fit very well when testing functions in different
168+
cases. In addition, every test function must take a "self" argument that is
169+
never used.
170+
171+
The pytest-describe plugin allows organizing your tests in the nicer way shown
172+
above using describe-blocks.
173+
174+
## Shared Behaviors
175+
176+
If you've used rspec's shared examples or test class inheritance, then you may
177+
be familiar with the benefit of having the same tests apply to
178+
multiple "subjects" or "suts" (system under test).
179+
180+
```python
181+
from pytest import fixture
182+
from pytest_describe import behaves_like
183+
184+
def a_duck():
185+
def it_quacks(sound):
186+
assert sound == "quack"
187+
188+
@behaves_like(a_duck)
189+
def describe_something_that_quacks():
190+
@fixture
191+
def sound():
192+
return "quack"
193+
194+
# the it_quacks test in this describe will pass
195+
196+
@behaves_like(a_duck)
197+
def describe_something_that_barks():
198+
@fixture
199+
def sound():
200+
return "bark"
201+
202+
# the it_quacks test in this describe will fail (as expected)
203+
```
204+
205+
Fixtures defined in the block that includes the shared behavior take precedence
206+
over fixtures defined in the shared behavior. This rule only applies to
207+
fixtures, not to other functions (nested describe blocks and tests). Instead,
208+
they are all collected as separate tests.

README.rst

Lines changed: 0 additions & 174 deletions
This file was deleted.

pytest_describe/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__all__ = ['behaves_like']
44

5-
__version__ = '2.1.0'
5+
__version__ = '2.2.0'

0 commit comments

Comments
 (0)