testing
Shivan

Shivan

A quick introduction to Pytest

Print Friendly, PDF & Email

Pytest is a python package where we write automated tests for our coded project. The reason we write tests for our code is to ensure the code runs the way we want. Writing automated tests can also make the process of testing our code easier, faster and more consistent.

We can develop tests first, in the case of test-driven development, and then build our code while using these automated tests to ensure we are heading in the correct direction.

We can also use the tests to ensure the changes we make to the code don’t cause untended errors later in the pathway of our development.

Most modern packages come with automated testing code, so it is a great way to make your code look professional.

Here is a video I also made as quick demonstration:

A video introducing pytest by example

Here is a git repository with all the code.

Installation

Let’s create a directory and environment:

# setting up directory
mkdir intro_pytesting
cd mkdir intro_pytesting

# creating a git repo
git init
git add .
git commit -m 'init'

# creating Pipfile and installing pytest as a dev package
pipenv lock
pipenv install pytest --dev

# creating out files
touch function.py
mkdir tests
touch tests/test_function.py
touch tests/__init__.py # tells python tests is a module

We should have something looking like this

intro_pytesting/
  ├── function.py
  ├── Pipfile
  ├── Pipfile.lock
  └── tests
       ├── __init__.py
       └── test_function.py

function.py will contain code for our interesting application.

On the other hand, test_function.py will contain the code used to test the features in the function.py file.

How to use – the basics

Let’s construct our interesting function. This function will be a be a class that performs mathematics functions

class Maths:
    """
    Takes input and does maths
    """

    def __init__(self, n):
        self.n = n

    def __repr__(self):
        return f"n is {self.n}"

The __init__() is a constructor that we can use to take input for objects created from this class. __repr__() is our representation of the object. Let’s test the existence of our function.

$ pipenv run python
>>> from function import Maths
>>> maths = Maths(12)
>>> maths
n is 12 # output thanks to the __repr__ method

We can use pytest to ensure this class works. In tests/test_function.py:

import pytest

from function import Maths

def test_exists():
    """
    Test that the Maths class works
    """
    maths = Maths(12)
    assert isinstance(maths, Maths)

Breaking down the function, there is an assert statement that verifies isinstance(), we can be more explicitly assert isinstance(maths, Maths) == True. isinstance(*object*, *type*) takes an object, which is maths and a type, which can be int, str, bool and so on, but we pass the class, Maths instead.

Now it’s time to run a test. When we run pytest. Pytest will look for programs starting with test_ and run these test cases. We will run pytest with -vv tags in order to provide a verbose and detailed response:


$ pipenv run pytest -vv

# we get output more or less simiar to this
tests/test_function.py::test_exists PASSED

Yay! We have run our first test and it works.

How it works — less basic

Now let’s give our function some, well… functionality. We will use test-driven development (TDD). We will write the tests first, then the functioning code.

We are doing to add two methods to the class. One the add numbers together and one to subtract numbers.

import pytest

from function import Maths

def test_exists():
    """
    Test that the Maths class works
    """
    maths = Maths(12)
    assert isinstance(maths, Maths)

def test_add():
    """
    Test the add function
    """
    maths = Math(12)
    addition = maths.add(10)
    assert addition == 22

def test_subtract():
    """
    Test subtract
    """
    maths = Math(12)
    subtraction = maths.subtract(2)
    assert subtraction == 10

This will be a good time to introduce the @pytest.fixtures decorator. Decorators, well… decorate functions. Not related to decorators, we can see that we repeat the instantiation of the maths object. We can pass this fixture to our test functions. This saves us from having to set up our tests again and again.

import pytest

from function import Maths

@pytest.fixture()
def maths():
    return Maths(12)

def test_exists(maths):
    """
    Test that the Maths class works
    """
    assert isinstance(maths, Maths)

def test_add(maths):
    """
    Test the add function
    """
    assert maths.add(10) == 22

def test_subtract(maths):
    """
    Test subtract
    """
    assert maths.subtract(2) == 10

We create a fixture maths(). We pass this function without calling it through our test functions. In addition to this, we can squash code down and make it look a bit neater.

Let’s write our code and then test if it works.

class Maths:
    """
    Takes input and does maths
    """

    def __init__(self, n):
        self.n = n

		# our new functions
		def add(self, x):
				self.n = self.n + x
				return self.n
		
		# 100% sure this below function is right
		def substract(self, x):
				self.n = self.n + x 
				return self.n

    def __repr__(self):
        return f"n is {self.n}"

We run pytest in the root of our project directory again:

$ pipenv run pytest

tests/test_function.py::test_exists PASSED                                                                                                                                                     [ 33%]
tests/test_function.py::test_add PASSED                                                                                                                                                    [ 66%]
tests/test_function.py::test_subtract FAILED  

We can see we get two out of three tests that passed.

When looking at the code from function.py. We see our mistake. We add instead of subtracting in the subtract() method. If we ammend this:

class Maths:
    """
    Takes input and does maths
    """

    def __init__(self, n):
        self.n = n

		# our new functions
		def add(self, x):
				self.n = self.n + x
				return self.n
		
		# !!! FIXED !!!
		def substract(self, x):
				self.n = self.n - x 
				return self.n

    def __repr__(self):
        return f"n is {self.n}"

Fixing our code will show all tests passing.

Conclusion

Pytest is a powerful tool to add to the tool belt in order to make your code more robust and professional.

A package that allows you to write automated tests, saving you time and mistakes as your proejcts grow.

I hope you find this useful and please send any feedback. Stay pythonic!

Related posts

python

The Zero to Hundred with Python

Reading Time: 6 minutes I’ve been embarking on the journey of learning Python. Python is a general-purpose programming language, excellent if you want to begin learning how to program

Read More »
ali abdaal

My TubeStats App (ft. Ali Abdaal)

Reading Time: 7 minutes With over 100 million views and one million subscribers, Ali Abdaal is definitely one to advise on building a successful YouTube channel. Besides his advice

Read More »

Share this post

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on email
Print Friendly, PDF & Email

Leave a Comment

Your email address will not be published. Required fields are marked *