Skip to content

Latest commit

 

History

History
141 lines (105 loc) · 5.9 KB

README.md

File metadata and controls

141 lines (105 loc) · 5.9 KB

Unit Testing in Python

This tutorial will cover the basics of unit testing in Python using the Pytest library. By the end of this tutorial, you will have a good understanding of how to write and run unit tests using Pytest.

Installation

Before we begin, you will need to have Python and Pytest installed on your computer. You can install Python by following the instructions on the Python website and you can install Pytest by running the following command in your terminal:

pip install pytest

It is also recommended to set up a virtual environment to keep your dependencies isolated from the system-wide packages. You can use the package venv to create a virtual environment as follows:

python3 -m venv robomasters-env

This code creates a virtual environment called robomasters-env which contains its own python packages. You can activate the virtual enviroinment by writing:

source robomasters-env/bin/activate

Writing Simple Functions

We will start by writing a simple function that we will use for testing later. This class is a basic arithmetic function that performs different operations on two numbers.

class Arithmetic:
    def add(self, a, b):
        return a + b
        
    def subtract(self, a, b):
        return a - b
        
    def multiply(self, a, b):
        return a * b
        
    def divide(self, a, b):
        if b == 0:
            return 'cannot divide by zero'
        else:
            return a / b

Brainstorming Inputs and Outputs

Next, we will brainstorm different inputs and outputs for the functions we wrote. For the arithmetic function, some inputs we could test include:

  • Two positive integers
  • Two negative integers
  • One positive and one negative integer
  • Zero as one of the inputs
  • Zero as the divisor
  • Invalid operation

For the outputs, we could test for:

  • The correct result for each operation
  • The correct error message for invalid operation
  • The correct error message for division by zero

Writing Tests

Now that we have a clear idea of what we want to test, we can write test cases using the Pytest library.

Here is an example of test cases for the arithmetic function:

import pytest

def test_addition(arithmetic_fixture):
    assert arithmetic_fixture.add(3, 4) == 7
    assert arithmetic_fixture.add(-3, 4) == 1
    assert arithmetic_fixture.add(3, -4) == -1
    assert arithmetic_fixture.add(-3, -4) == -7

def test_subtraction(arithmetic_fixture):
    assert arithmetic_fixture.subtract(3, 4) == -1
    assert arithmetic_fixture.subtract(-3, 4) == -7
    assert arithmetic_fixture.subtract(3, -4) == 7
    assert arithmetic_fixture.subtract(-3, -4) == 1

def test_multiplication(arithmetic_fixture):
    assert arithmetic_fixture.multiply(3, 4) == 12
    assert arithmetic_fixture.multiply(-3, 4) == -12
    assert arithmetic_fixture.multiply(3, -4) == -12
    assert arithmetic_fixture.multiply(-3, -4) == 12
    
@pytest.mark.parametrize("a, b, expected", [
    (3, 4, 0.75),
    (-3, 4, -0.75),
    (3, -4, -0.75),
    (-3, -4, 0.75),
    (0, 3, 0),
    (3, 0, 'cannot divide by zero')
])
def test_division(arithmetic_fixture, a, b, expected):
    result = arithmetic_fixture.divide(a, b)
    assert result == expected

In this example, we used the pytest.mark.parametrize decorator to test the divide method with multiple sets of inputs. This allows us to test the function with different scenarios such as dividing two positive numbers, two negative numbers, zero as the divisor, and so on.

Fixtures

Fixtures are functions that are run before and after each test method. They are used to set up and tear down any test resources that are needed. For example, you could create a fixture that creates an instance of the arithmetic class before each test method, and then deletes it after the test method has completed.

Now you can use the fixture by passing it as an input parameter to the test method.

def test_addition(arithmetic_fixture):
    assert arithmetic_fixture.add(3, 4) == 7
    assert arithmetic_fixture.add(-3, 4) == 1
    assert arithmetic_fixture.add(3, -4) == -1
    assert arithmetic_fixture.add(-3, -4) == -7

Fixtures are useful for setting up and cleaning up resources that are needed for multiple test methods, such as a database connection or a file. This makes your tests more efficient and less prone to errors.

Decorators

Decorators are a way to modify the behavior of a function or method. For example, you could use a decorator to skip a test method if certain conditions are not met, or to mark a test method as slow and run it less frequently. For example, you can use the decorator pytest.mark.skip to skip a test method if a certain condition is not met.

import sys

@pytest.mark.skipif(sys.version_info < (3,6),
                    reason="requires python3.6 or higher")
def test_division(arithmetic_fixture):
    assert arithmetic_fixture.divide(3, 4) == 0.75
    assert arithmetic_fixture.divide(-3, 4) == -0.75
    assert arithmetic_fixture.divide(3, -4) == -0.75
    assert arithmetic_fixture.divide(-3, -4) == 0.75
    assert arithmetic_fixture.divide(3,0) == 'cannot divide by zero'
    

This test case will only run if the version of python is 3.6 or higher, otherwise pytest will skip it and won't run it.

You can also use other decorators like @pytest.mark.xfail to mark a test case that is expected to fail under certain conditions and @pytest.mark.parametrize to test the function with multiple sets of inputs.

Conclusion

Unit testing is an important part of software development, and Pytest is a powerful library for writing and running unit tests in Python. By following the techniques outlined in this tutorial, you will be able to write efficient and maintainable tests for your Python code.

It's important to note that fixtures and decorators are powerful tools but they can make the code more complex, so it's important to use them judiciously and only when they add value to your tests.