Why test your code?
In a previous article, we discussed why unit tests are fundamental for ensuring the quality and proper functioning of code.
As a reminder, unit tests are designed to independently test different parts of code, unlike integration tests that verify the functioning of the code as a whole. They also differ from functional tests, which are used to ensure the functionality of a specific feature.
Why use the Pytest framework to test your code?
To facilitate the automation of unit tests, you can use testing frameworks that standardize their writing, quickly identify failing tests, and make maintenance easier.
In Python, there are different testing frameworks, with the most well-known ones being Unittest and Pytest. Today, we will focus on implementing unit tests with Pytest, which has a slightly simpler learning curve.
How do I use Pytest?
First, you will need to install the Pytest library using the command `pip install pytest`.
In a directory, create a file named `student.py`. Inside this file, you can create a `Student` class that allows you to create instances of students, record their grades, and calculate their average.
In the same directory, create a test file named `student_test.py`. The naming convention for test files is to use the format “test_filename.py” or “filename_test.py”. In the test file, each test should be defined as a function prefixed with “test”. This naming convention helps the testing framework (Pytest) identify and run the tests correctly.
The assert statements are used to verify that a function returns a correct result for given arguments and raise an exception if the condition is false. In your example, the first two functions verify the properties (grades, academic average) of a `Student` object during its instantiation and then the value of the average after adding a grade of 12.
If your test files are correctly prefixed, you can simply run the command `python -m pytest` in your terminal to run them. To run a specific test, execute the command `python -m pytest filename.py`.
After execution, Pytest provides a detailed report of the tests, indicating which tests passed and which ones failed, along with any relevant error messages. This makes it easy to identify and address issues in your code.
The “collected items” correspond to the number of tests that are executed. In this case, all 3 tests have passed.
If you temporarily modify your `student.py` file so that, upon instantiation of a student, they have a default grade of 10, you can rerun your tests to see if they still pass with this change. It’s a good practice to test different scenarios and edge cases to ensure your code behaves correctly in various situations.
In our `student_test.py` file, we hide the first 2 test functions to keep only the `test_add_grades` function, then we execute the test file.
We observe that the test no longer passes because the changes made in our `student.py` file make the assertion false.
Pytest allows us to see if code modifications affect the expected result or not.
Note: For the next steps, it will be necessary to modify the `__init__` function in `student.py` to return an empty list during instantiation.
Avoid repetition using pytest fixtures
In the previous example, we instantiated an object of type Student in each test. To avoid rewriting code used in multiple tests, Pytest provides a specific decorator called a fixture, which allows easy access to the elements required for test execution, such as data.
To specify that a function is a fixture, you will need to use the @pytest.fixture decorator before defining it. You will also need to pass the fixture as an argument to the test functions where you want to use it.
In this example, the fixture prevents us from re-creating the student object in each test.
The `@pytest.mark.parametrize` decorator is used to test a sequence of instructions with different input values.
Parameterization allows you to run the same test with different values. To do this, we use the `@pytest.mark.parametrize` decorator, specifying the names of the arguments to test and their respective values.
Conclusion
In this article, we’ve started to explore the use of Pytest for testing code. As a Data Engineer or Data Scientist, implementing unit tests is a necessary step to ensure code quality and reduce issues during deployment. However, it’s important to note that this step alone may not be sufficient.
On one hand, when developing a Machine Learning application, some aspects may slip through the cracks. Additionally, by definition, unit tests cannot test the interaction between units.
To discover the role of a Data Engineer and the training path we offer, feel free to check out our dedicated page.