Skip to content

Commit e0af323

Browse files
authored
added tester app (piccolo-orm#187)
* added tester app * reset PICCOLO_CONF once the tests complete * added an additional test for when an exception is raised in the context manager body * add a test for the `tester run` command * added docs * make it more obvious how to send multiple args to pytest
1 parent d1d6300 commit e0af323

File tree

10 files changed

+193
-0
lines changed

10 files changed

+193
-0
lines changed

docs/src/piccolo/engines/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Here's an example ``piccolo_conf.py`` file:
6161
.. hint:: A good place for your piccolo_conf file is at the root of your
6262
project, where the Python interpreter will be launched.
6363

64+
.. _PICCOLO_CONF:
65+
6466
PICCOLO_CONF environment variable
6567
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6668

docs/src/piccolo/projects_and_apps/included_apps.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,39 @@ need to run raw SQL queries on your database.
101101
For it to work, the underlying command needs to be on the path (i.e. ``psql``
102102
or ``sqlite3`` depending on which you're using).
103103

104+
tester
105+
~~~~~~
106+
107+
Launches `pytest <https://pytest.org/>`_ , which runs your unit test suite. The
108+
advantage of using this rather than running ``pytest`` directly, is the
109+
``PICCOLO_CONF`` environment variable will automatically be set before the
110+
testing starts, and will be restored to it's initial value once the tests
111+
finish.
112+
113+
.. code-block:: bash
114+
115+
piccolo tester run
116+
117+
Setting the :ref:`PICCOLO_CONF<PICCOLO_CONF>` environment variable means your
118+
code will use the database engine specified in that file for the duration of
119+
the testing.
120+
121+
By default ``piccolo tester run`` sets ``PICCOLO_CONF`` to
122+
``'piccolo_conf_test'``, meaning that a file called ``piccolo_conf_test.py``
123+
will be imported.
124+
125+
If you prefer, you can set a custom ``PICCOLO_CONF`` value:
126+
127+
.. code-block:: bash
128+
129+
piccolo tester run --piccolo_conf=my_custom_piccolo_conf
130+
131+
You can also pass arguments to pytest:
132+
133+
.. code-block:: bash
134+
135+
piccolo tester run --pytest_args="-s foo"
136+
104137
-------------------------------------------------------------------------------
105138

106139
Optional includes

piccolo/apps/tester/__init__.py

Whitespace-only changes.

piccolo/apps/tester/commands/__init__.py

Whitespace-only changes.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import os
2+
import sys
3+
import typing as t
4+
5+
from _pytest.config import ExitCode
6+
7+
8+
class set_env_var:
9+
def __init__(self, var_name: str, temp_value: str):
10+
"""
11+
Temporarily set an environment variable.
12+
13+
:param var_name:
14+
The name of the environment variable to temporarily change.
15+
:temp_value:
16+
The value that the environment variable will temporarily be set to,
17+
before being reset to it's pre-existing value.
18+
19+
"""
20+
self.var_name = var_name
21+
self.temp_value = temp_value
22+
23+
def set_var(self, value: str):
24+
os.environ[self.var_name] = value
25+
26+
def get_var(self) -> t.Optional[str]:
27+
return os.environ.get(self.var_name)
28+
29+
def __enter__(self):
30+
self.existing_value = self.get_var()
31+
self.set_var(self.temp_value)
32+
33+
def __exit__(self, *args):
34+
if self.existing_value is None:
35+
del os.environ[self.var_name]
36+
else:
37+
self.set_var(self.existing_value)
38+
39+
40+
def run_pytest(
41+
pytest_args: t.List[str],
42+
) -> t.Union[int, ExitCode]: # pragma: no cover
43+
try:
44+
import pytest
45+
except ImportError:
46+
sys.exit(
47+
"Couldn't find pytest. Please use `pip install 'piccolo[pytest]' "
48+
"to use this feature."
49+
)
50+
51+
return pytest.main(pytest_args)
52+
53+
54+
def run(
55+
pytest_args: str = "", piccolo_conf: str = "piccolo_conf_test"
56+
) -> None:
57+
"""
58+
Run your unit test suite using Pytest.
59+
60+
:param piccolo_conf:
61+
The piccolo_conf module to use when running your tests. This will
62+
contain the database settings you want to use. For example
63+
`my_folder.piccolo_conf_test`.
64+
:param pytest_args:
65+
Any options you want to pass to Pytest. For example
66+
`piccolo tester run --pytest_args="-s"`.
67+
68+
"""
69+
with set_env_var(var_name="PICCOLO_CONF", temp_value=piccolo_conf):
70+
args = pytest_args.split(" ")
71+
sys.exit(run_pytest(args))

piccolo/apps/tester/piccolo_app.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from piccolo.conf.apps import AppConfig
2+
3+
from .commands.run import run
4+
5+
APP_CONFIG = AppConfig(
6+
app_name="tester",
7+
migrations_folder_path="",
8+
table_classes=[],
9+
migration_dependencies=[],
10+
commands=[run],
11+
)

piccolo/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from piccolo.apps.schema.piccolo_app import APP_CONFIG as schema_config
2121
from piccolo.apps.shell.piccolo_app import APP_CONFIG as shell_config
2222
from piccolo.apps.sql_shell.piccolo_app import APP_CONFIG as sql_shell_config
23+
from piccolo.apps.tester.piccolo_app import APP_CONFIG as tester_config
2324
from piccolo.apps.user.piccolo_app import APP_CONFIG as user_config
2425
from piccolo.conf.apps import AppRegistry, Finder
2526
from piccolo.utils.sync import run_sync
@@ -66,6 +67,7 @@ def main():
6667
schema_config,
6768
shell_config,
6869
sql_shell_config,
70+
tester_config,
6971
user_config,
7072
]:
7173
for command in _app_config.commands:

requirements/extras/pytest.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest

tests/apps/tester/__init__.py

Whitespace-only changes.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
from unittest import TestCase
3+
from unittest.mock import MagicMock, patch
4+
5+
from piccolo.apps.tester.commands.run import run, set_env_var
6+
7+
8+
class TestSetEnvVar(TestCase):
9+
def test_no_existing_value(self):
10+
"""
11+
Make sure the environment variable is set correctly, when there is
12+
no existing value.
13+
"""
14+
var_name = "PICCOLO_TEST_1"
15+
16+
# Make sure it definitely doesn't exist already
17+
if os.environ.get(var_name) is not None:
18+
del os.environ[var_name]
19+
20+
new_value = "hello world"
21+
22+
with set_env_var(var_name=var_name, temp_value=new_value):
23+
self.assertEqual(os.environ.get(var_name), new_value)
24+
25+
self.assertEqual(os.environ.get(var_name), None)
26+
27+
def test_existing_value(self):
28+
"""
29+
Make sure the environment variable is set correctly, when there is
30+
an existing value.
31+
"""
32+
var_name = "PICCOLO_TEST_2"
33+
initial_value = "hello"
34+
new_value = "goodbye"
35+
36+
os.environ[var_name] = initial_value
37+
38+
with set_env_var(var_name=var_name, temp_value=new_value):
39+
self.assertEqual(os.environ.get(var_name), new_value)
40+
41+
self.assertEqual(os.environ.get(var_name), initial_value)
42+
43+
def test_raise_exception(self):
44+
"""
45+
Make sure the environment variable is still reset, even if an exception
46+
is raised within the context manager body.
47+
"""
48+
var_name = "PICCOLO_TEST_3"
49+
initial_value = "hello"
50+
new_value = "goodbye"
51+
52+
os.environ[var_name] = initial_value
53+
54+
class FakeException(Exception):
55+
pass
56+
57+
try:
58+
with set_env_var(var_name=var_name, temp_value=new_value):
59+
self.assertEqual(os.environ.get(var_name), new_value)
60+
raise FakeException("Something went wrong ...")
61+
except FakeException:
62+
pass
63+
64+
self.assertEqual(os.environ.get(var_name), initial_value)
65+
66+
67+
class TestRun(TestCase):
68+
@patch("piccolo.apps.tester.commands.run.run_pytest")
69+
def test_success(self, pytest: MagicMock):
70+
with self.assertRaises(SystemExit):
71+
run(pytest_args="-s foo", piccolo_conf="my_piccolo_conf")
72+
73+
pytest.assert_called_once_with(["-s", "foo"])

0 commit comments

Comments
 (0)