Testing with pytest - part 1¶

Why to write tests?¶

  • Who wants to perform manual testing?
  • When you fix a bug or add a new feature, tests are a way to verify that you did not break anything on the way
  • If you have clear requirements, you can have matching test(s) for each requirement
  • You don't have to be afraid of refactoring
  • Tests document your implementation - they show other people use cases of your implementation
  • This list is endless...

Test-driven development aka TDD¶

In short, the basic idea of TDD is to write tests before writing the actual implementation. Maybe the most significant benefit of the approach is that the developer focuses on writing tests which match with what the program should do. Whereas if the tests are written after the actual implementation, there is a high risk for rushing tests which just show green light for the already written logic.

Tests are first class citizens in modern, agile software development, which is why it's important to start thinking TDD early during your Python learning path.

The workflow of TDD can be summarized as follows:

  1. Add a test case(s) for the change / feature / bug fix you are going to implement
  2. Run all tests and check that the new one fails
  3. Implement required changes
  4. Run tests and verify that all pass
  5. Refactor

Running pytest inside notebooks¶

These are the steps required to run pytest inside Jupyter cells. You can copy the content of this cell to the top of your notebook which contains tests.

In [1]:
# Let's make sure pytest and ipytest packages are installed
# ipytest is required for running pytest inside Jupyter notebooks
import sys

!{sys.executable} -m pip install pytest
!{sys.executable} -m pip install ipytest

# These are needed for running pytest inside Jupyter notebooks
import ipytest

ipytest.autoconfig()
Requirement already satisfied: pytest in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (7.3.1)
Requirement already satisfied: iniconfig in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest) (2.0.0)
Requirement already satisfied: pluggy<2.0,>=0.12 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest) (1.0.0)
Requirement already satisfied: packaging in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest) (23.1)
Requirement already satisfied: tomli>=1.0.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest) (2.0.1)
Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest) (1.1.1)

[notice] A new release of pip available: 22.3.1 -> 23.1.1
[notice] To update, run: pip install --upgrade pip
Requirement already satisfied: ipytest in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (0.13.1)
Requirement already satisfied: packaging in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipytest) (23.1)
Requirement already satisfied: ipython in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipytest) (8.12.0)
Requirement already satisfied: pytest>=5.4 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipytest) (7.3.1)
Requirement already satisfied: pluggy<2.0,>=0.12 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest>=5.4->ipytest) (1.0.0)
Requirement already satisfied: iniconfig in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest>=5.4->ipytest) (2.0.0)
Requirement already satisfied: exceptiongroup>=1.0.0rc8 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest>=5.4->ipytest) (1.1.1)
Requirement already satisfied: tomli>=1.0.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pytest>=5.4->ipytest) (2.0.1)
Requirement already satisfied: pexpect>4.3 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (4.8.0)
Requirement already satisfied: prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (3.0.38)
Requirement already satisfied: pygments>=2.4.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (2.15.0)
Requirement already satisfied: traitlets>=5 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (5.9.0)
Requirement already satisfied: jedi>=0.16 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.18.2)
Requirement already satisfied: stack-data in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.6.2)
Requirement already satisfied: matplotlib-inline in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.1.6)
Requirement already satisfied: pickleshare in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.7.5)
Requirement already satisfied: decorator in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (5.1.1)
Requirement already satisfied: appnope in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.1.3)
Requirement already satisfied: backcall in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from ipython->ipytest) (0.2.0)
Requirement already satisfied: parso<0.9.0,>=0.8.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from jedi>=0.16->ipython->ipytest) (0.8.3)
Requirement already satisfied: ptyprocess>=0.5 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from pexpect>4.3->ipython->ipytest) (0.7.0)
Requirement already satisfied: wcwidth in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from prompt-toolkit!=3.0.37,<3.1.0,>=3.0.30->ipython->ipytest) (0.2.6)
Requirement already satisfied: executing>=1.2.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from stack-data->ipython->ipytest) (1.2.0)
Requirement already satisfied: asttokens>=2.1.0 in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from stack-data->ipython->ipytest) (2.2.1)
Requirement already satisfied: pure-eval in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from stack-data->ipython->ipytest) (0.2.2)
Requirement already satisfied: six in /Users/jerrypussinen/.virtualenvs/learn-python/lib/python3.10/site-packages (from asttokens>=2.1.0->stack-data->ipython->ipytest) (1.16.0)

[notice] A new release of pip available: 22.3.1 -> 23.1.1
[notice] To update, run: pip install --upgrade pip

pytest test cases¶

Let's consider we have a function called sum_of_three_numbers for which we want to write a test.

In [2]:
def sum_of_three_numbers(num1, num2, num3):
    return num1 + num2 + num3

Pytest test cases are actually quite similar as you have already seen in the exercises. Most of the exercises are structured like pytest test cases by dividing each exercise into three cells:

  1. Setup the variables used in the test
  2. Your implementation
  3. Verify that your implementation does what is wanted by using assertions

See the example test case below to see the similarities between the exercises and common structure of test cases.

In [3]:
%%ipytest

def test_sum_of_three_numbers():
    # 1. Setup the variables used in the test
    num1 = 2
    num2 = 3
    num3 = 5
    
    # 2. Call the functionality you want to test
    result = sum_of_three_numbers(num1, num2, num3)
    
    # 3. Verify that the outcome is expected
    assert result == 10
.                                                                                            [100%]
1 passed in 0.01s

Now go ahead and change the line assert result == 10 such that the assertion fails to see the output of a failed test.