202 lines
6.2 KiB
Plaintext
202 lines
6.2 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Testing with [pytest](https://docs.pytest.org/en/latest/) - part 2"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Let's make sure pytest and ipytest packages are installed\n",
|
|
"# ipytest is required for running pytest inside Jupyter notebooks\n",
|
|
"import sys\n",
|
|
"!{sys.executable} -m pip install pytest\n",
|
|
"!{sys.executable} -m pip install ipytest\n",
|
|
"\n",
|
|
"import ipytest.magics\n",
|
|
"import pytest\n",
|
|
"__file__ = 'testing2.ipynb'"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## [`@pytest.fixture`](https://docs.pytest.org/en/latest/fixture.html#pytest-fixtures-explicit-modular-scalable)\n",
|
|
"Let's consider we have an implemention of `Person` class which we want to test."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# This would be e.g. in person.py\n",
|
|
"class Person:\n",
|
|
" def __init__(self, first_name, last_name, age):\n",
|
|
" self.first_name = first_name\n",
|
|
" self.last_name = last_name\n",
|
|
" self.age = age\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def full_name(self):\n",
|
|
" return '{} {}'.format(self.first_name, self.last_name)\n",
|
|
" \n",
|
|
" @property\n",
|
|
" def as_dict(self):\n",
|
|
" return {'name': self.full_name, 'age': self.age}\n",
|
|
" \n",
|
|
" def increase_age(self, years):\n",
|
|
" if years < 0:\n",
|
|
" raise ValueError('Can not make people younger :(')\n",
|
|
" self.age += years"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"You can easily create resusable testing code by using pytest fixtures. If you introduce your fixtures inside [_conftest.py_](https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions), the fixtures are available for all your test cases. In general, the location of _conftest.py_ is at the root of your _tests_ directory."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# This would be in either conftest.py or test_person.py\n",
|
|
"@pytest.fixture()\n",
|
|
"def default_person():\n",
|
|
" person = Person(first_name='John', last_name='Doe', age=82)\n",
|
|
" return person"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"Then you can utilize `default_person` fixture in the actual test cases. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%run_pytest[clean]\n",
|
|
"\n",
|
|
"# These would be in test_person.py\n",
|
|
"def test_full_name(default_person): # Note: we use fixture as an argument of the test case\n",
|
|
" result = default_person.full_name\n",
|
|
" assert result == 'John Doe'\n",
|
|
" \n",
|
|
" \n",
|
|
"def test_as_dict(default_person):\n",
|
|
" expected = {'name': 'John Doe', 'age': 82}\n",
|
|
" result = default_person.as_dict\n",
|
|
" assert result == expected\n",
|
|
" \n",
|
|
" \n",
|
|
"def test_increase_age(default_person):\n",
|
|
" default_person.increase_age(1)\n",
|
|
" assert default_person.age == 83\n",
|
|
" \n",
|
|
" default_person.increase_age(10)\n",
|
|
" assert default_person.age == 93\n",
|
|
" \n",
|
|
" \n",
|
|
"def test_increase_age_with_negative_number(default_person):\n",
|
|
" with pytest.raises(ValueError):\n",
|
|
" default_person.increase_age(-1)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"By using a fixture, we could use the same `default_person` for all our four test cases!\n",
|
|
"\n",
|
|
"In the `test_increase_age_with_negative_number` we used [`pytest.raises`](https://docs.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions) to verify that an exception is raised. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## [`@pytest.mark.parametrize`](https://docs.pytest.org/en/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions)\n",
|
|
"Sometimes you want to test the same functionality with multiple different inputs. `pytest.mark.parametrize` is your solution for defining multiple different inputs with expected outputs. Let's consider the following implementation of `replace_names` function. "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# This would be e.g. in string_manipulate.py\n",
|
|
"def replace_names(original_str, new_name):\n",
|
|
" \"\"\"Replaces names (uppercase words) of original_str by new_name\"\"\"\n",
|
|
" words = original_str.split()\n",
|
|
" manipulated_words = [new_name if w.istitle() else w for w in words]\n",
|
|
" return ' '.join(manipulated_words)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"We can test the `replace_names` function with multiple inputs by using `pytest.mark.parametrize`."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"%%run_pytest[clean]\n",
|
|
"\n",
|
|
"# This would be in your test module\n",
|
|
"@pytest.mark.parametrize(\"original,new_name,expected\", [\n",
|
|
" ('this is Lisa', 'John Doe', 'this is John Doe'),\n",
|
|
" ('how about Frank and Amy', 'John', 'how about John and John'),\n",
|
|
" ('no names here', 'John Doe', 'no names here'),\n",
|
|
" ])\n",
|
|
"def test_replace_names(original, new_name, expected):\n",
|
|
" result = replace_names(original, new_name)\n",
|
|
" assert result == expected"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.5.4"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|