• 10 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 8/30/22

Organize Your Testing Into Classes

As you’ve seen when setting up various test scenarios, sometimes you need to duplicate code to create a test context or part of your scenario. However, duplicating code for each test that you create is pretty time-consuming. Not only do you have to keep repeating yourself, it also makes it harder to maintain your tests afterwards.

I’d really like to be able to declare variables and reuse them in my functions to test class methods, or use identical lines of code for several tests without having to copy all of the lines.

Can you think of a way of doing this?

I’ll leave you to think about it…

Have you had any bright ideas?

What if we created a class for our tests? Then we could declare class attributes and methods, using them whenever we need to. This will also enable us to differentiate between our tests.

Why use a test class?

To:

  • group test classes together.

  • define a fixed environment for running test methods.

  • eliminate code duplication when code is common to all test cases.

  • make it easier to refactor tests. 

Create a Test Class

It might sound complicated, but it’s actually quite simple. First of all, you need to create a new class in a test file. This test class will be given the name of the class we’re testing, with a prefix of  Test . This prefix allows us to identify it as a test class. You can then create each test as a method of this class.

Here’s an example of a test class with two tests:

class TestClass:
    def test_one(self):
        assert 1 == 1

    def test_two(self):
        assert 2 == 2

Now let’s run the  pytest  command to execute our tests. We get the following result on the terminal:

Once the pytest command is run on the terminal, the two tests from the test class are executed.
Run tests from a test class

You can use option  -v  after the  pytest  command to find out which file and class the test belongs to.

To have more details about the tests, add the -v option to the command.
Even more details!

You can see in the screenshot above that there are indeed two tests. And in the details, we can also see that the tests belong to the  test_class.py  file and the  TestClass  test class. It’s a really useful command!

I’m now going to deliberately raise an assertion in the second test to show you how to debug a test when using a test class. It’s almost the same as what we saw in the Go Further With Pytest chapter. Here’s the same class with an error in the second test:

class TestClass:
    def test_one(self):
        assert 1 == 1

    def test_two(self):
        assert 2 == 1

Let’s see what the terminal will display after running the tests:

How do we debug an error?
How do we debug an error?

Surprise surprise, there’s red everywhere! Obviously, we introduced an error into the second test, and we can see from the terminal display that the test called  test_two  from the  TestClass  class has failed. However, it does indicate the assertion that was raised. You can now easily correct the error and re-run the tests to check that the correction has worked.

Execute Actions Before or After Tests

I’ve saved the best till last! You now know that one of the most long-winded aspects when testing is setting up the test context and environment. This is why I’m going to show you in this part of the chapter how you can make your life easier when creating tests.

May I present the  setup()  and  teardown()  methods!

Pytest enables you to execute instructions before or after each unit test. This feature in our test library is elegantly named setup and teardown.

On the one hand, we have a test method called  setup() , which is invoked before a test is run. You can also configure your test context and environment within this method, to populate a database or to register a user and log them in, for example.

On the other hand, we have a second method called  teardown() , which is invoked at the end of a test. You can use this method to clean up the objects you used for your testing.

There are a number of invocation levels: before each unit test, when creating a class or when importing the Pytest module. We’re going to see the  setup()  and  teardown()  functions being applied at several levels:

  • Module level

  • Class level

  • Method level

  • Function level

Setup/Teardown at Module Level

At module level, there are two functions:

  • setup_module() , called at the start of a module.

  • teardown_module() , called at the end of a module. 

These two functions enable us to configure the environment at a global level, applying to all tests and classes within the module. You can also add your configuration within the two function blocks. Note that these two functions will be called once for all tests.

def setup_module(module):
    """ function called when module is imported """
    
def teardown_module(module):
    """ function called after execution of all tests within a module """

Setup/Teardown at Class Level

This time, the  setup_class()  method is called when the class is instantiated and the  teardown_class()  method is called when the class is destroyed. The context configuration only affects the scenarios within this particular test class. It’s important to note that these methods are only invoked once for all the tests within a class.

@classmethod
def setup_class(cls):
    """ function called when class is created """
    
@classmethod
def teardown_class(cls):
    """ function called when class is destroyed """

Setup/Teardown at Method Level

Now, here we have the most useful methods:  setup_method()  and  teardown_method()  .  The methods described previously enabled us to configure an environment to apply to several tests. However, this practice can be dangerous

I imagine you want to know why. Quite simply, when using the previous methods, our tests become interdependent. As a result, a test might change the test environment, because every test uses the same configuration and potentially the same database. Any change in the sequence of tests could result in a global error.

It is good practice to create a  setup()  and  teardown()  method each time a scenario is run. For this reason, we highly recommend that you use setup and teardown at the method level.

def setup_method(self, method):
    """ function called when running a unit test that is part of a class """

def teardown_method(self, method):
    """ function called at the end of a unit test that is part of a class """

Setup/Teardown at Function Level

Methods for functions aren’t particularly useful for test classes, but it’s good to know about them if you’re not using a test class in your scenarios. The  setup_function()  and  teardown_function()  methods perform the same role as the previous two methods for class methods, but this time they apply to functions.

def setup_function(function):
    """ function called when running a unit test in a function """

def teardown_function(function):
    """ function called at the end of a unit test in a function """

Let’s now have a look at some code to illustrate how various setup and teardown methods are called using some print instructions. Let’s take the following code and look at the print output on the terminal. You’ll see the order in which each function is called.

def setup_module(module):
    print("\n--> Setup module")

def teardown_module(module):
    print("--> Teardown module")

class TestClass:
    @classmethod
    def setup_class(cls):
        print("--> Setup class")

    @classmethod
    def teardown_class(cls):
        print("--> Teardown class")

    def setup_method(self, method):
        print("--> Setup method")

    def teardown_method(self, method):
        print("\n--> Teardown method")

    def test_one(self):
        print("--> Run first test")

    def test_two(self):
        print("--> Run second test")

Here’s the terminal output we see after running the following command,  pytest -s : 

The prints show the order in which each method is run.
Different setup and teardown levels

As you can see in the above screenshot, you can confirm that the methods are working correctly and are run in a specific order. Go ahead and play around with this code to understand the different  setup()  and  teardown()  methods.

Over to You!

Go back to the OC-commerce project using the Django framework.

Your mission:

  • Organize your testing into classes.

You’ll see that managing contexts and databases becomes a lot easier when you use classes.

Find a suggested solution on GitHub

Let’s Recap!

  • Using test classes enables you to organize your project testing.

  • Test classes allow you to create methods to minimize code duplication.

  • You need to name your test with a prefix of  test_  or a suffix of  _test , otherwise Pytest won’t run it.

  • The  setup()  and  teardown()  methods  allow you to manage the context at the beginning and end of a test and these methods operate at a number of levels: 

    • Module level

    • Class level

    • Method level

    • Function level 

In this chapter, we’ve been through all of the concepts that relate to unit testing. In the next chapter, we’re going to cover one final point that will enable you to verify and assess the quality of our testing, to see whether or not we did a good job!

Example of certificate of achievement
Example of certificate of achievement