Unit testing is the act of testing a small unit of your code. Generally, this means you are testing a function or a class in isolation. If you are doing this unit testing manually, you simply run your function, then check and make sure it gave you the expected output. This is what you have been manually doing ever since you first started writing software.
As your software projects grow larger, this manual testing becomes less feasible. And you might start missing things that you would have caught during your manual testing on your smaller project. As you make code changes in one place, problems may arise in a different function that depends on the function that you recently changed. With manual testing, you would miss this. But with automated testing, you can catch problems that you didn’t expect.
Today we are going to talk about how to write and run unit tests using a framework called Pytest. Using a framework to handle your unit testing takes most of the work out of writing these unit tests, and allows you to focus on writing code instead of spending time figuring out how to write tests.
Installing pytest is very easy if you are using Pip. For information on how to install pip, see our python basics article.
Now that you have pip installed, you can install pytest by running:
Pip install pytest
Writing your first Unit Test
As we said earlier, you create a function, then you create a corresponding unit test. For example, if you have a function called add numbers that adds numbers together, you need to create a test where you feed in 1 and 2 and the test makes sure that the output of the function is 3.
To get started, let’s create a file called mymath.py and add the following code:
def addnumbers(x,y): answer = x+y return answer def subtractnumbers(x,y): answer = x-y return answer def multiplynumbers(x,y): answer = x*y return answer
As you can see, we have three functions. If you want to manually test these functions, you could add the following to the bottom of mymath.py and run it at the terminal:
print(addnumbers(2,3)) print(subtractnumbers(2,3)) print(multiplynumbers(2,3))
The output would then be 5, -1, and 6. But today we don’t want to manually test things anymore. We want to automate to so we always know that our code is working as we want it to.
To create your unit tests, create a new file called test_mymath.py and add the following code:
import mymath def test_addnumbers(): assert mymath.addnumbers(2,3) == 5 def test_subtractnumbers(): assert mymath.subtractnumbers(2,3) == -1 def test_multiplynumbers(): assert mymath.multiplynumbers(2,3) == 6
As you can see, we started by importing our original file, mymath.py. We then defined a new function for each of our original functions, then called them using the assert command giving sample input, and expected output for each function. To run these tests, simply open a terminal window, navigate to the folder containing these two files, and run: pytest
Your output should be similar to the following:
As you can see, all 3 tests passed successfully.
Now, let’s modify our original code to introduce a change, but not update our tests:
def addnumbers(x,y): answer = x+y+x return answer def subtractnumbers(x,y): answer = x-y return answer def multiplynumbers(x,y): answer = x*y return answer
Notice we have added an extra +x in the add numbers function. This could be a typo that we miss when building our application. Or perhaps it is a change that an engineer on your team made that changes the behavior of the function. Next time we run our unit tests, they fail:
Notice when the test ran, we had the same input of 2 and 3. So the expected output would have been 5. However, when the test was actually 7, pytest knew that something was wrong, and our test failed.
How does Pytest work?
Now that we have discussed how to write tests, let’s talk a bit more about how the framework works. Notice we have been prefixing our functions and our files with “test_”. Pytest has discovery rules defining what is and what is not a test.
By default, pytest assumes any files prefixed with “test_” are unit tests. And any function prefixed with “test_” is a unit test. When we run pytest, it scans the current folder for any test_ files and then looks for all of the test_ functions. Each of those functions is run, and the assert statement is evaluated.
By default, pytest scans the current folder and all subdirectories looking for the test_ files. However, you can specify additional arguments to scan a specific folder. For example
For more information on pytest and best practices, you can check out the pytest website.