Unit testing is a software testing technique in which individual components or units of a software application are tested independently from the rest of the application.
In software development, it's beneficial to break your application into small, isolated units. This approach allows you to write independent tests to check all parts of your application, ensuring that they behave as expected. Also, if a test fails, you can easily isolate and troubleshoot the area of the code that has the bug without tampering with the rest of your application.
Python provides built-in support for unit testing through the unittest testing framework. There are also other third-party testing frameworks that you can use for your unit testing, such as pytest.
This article focuses on how to use the unittest
framework to write tests for your Python applications and why developers often prefer it.
To get the best out of this article, you should have a basic understanding of the Python programming language.
Why Do Developers Prefer to Use unittest?
While there are many frameworks for unit testing in the Python ecosystem, many developers still prefer the built-in unittest
due to its compelling advantages.
First, the unittest
module is part of Python's standard library. This ensures immediate availability and compatibility across various environments without extra dependencies. The seamless integration with various environments makes it convenient for developers to use unittest
without installing additional packages.
Second, as a long-standing framework within the Python ecosystem, unittest
benefits from familiarity and longevity. Many developers are already used to its API and structure, making it a reliable choice for testing.
Third, most integrated development environments (IDEs) such as PyCharm offer built-in support for unittest
. This improves developer productivity and streamlines the testing process, allowing for easier test management and execution.
Fourth, the framework has comprehensive and well-maintained documentation. This provides detailed guidance and examples, aiding developers in effectively using unittest
for their testing needs.
Finally, many existing Python projects use unittest
for testing, ensuring compatibility with legacy codebases. This widespread adoption allows developers to maintain and extend older projects without needing to introduce and adapt to a new testing framework.
How to Write Unit Tests with unittest
Unit testing with unittest
involves creating test cases to verify the functionality of individual units of your code. Each test case is defined by subclassing unittest.TestCase
. This allows you to inherit the several methods provided by the TestCase class.
Some of the methods provided by the TestCase
class are assert methods. These assert methods allow you to check whether the actual result of a function or operation matches the expected result, or whether certain conditions are met. If an assertion fails, the test is marked as failed, and you will receive an error message.
See Classes and functions for detailed information about the different methods provided by the TestCase
class.
Now, let's use two of the assert methods to write tests for a simple calculator program. First, create a new folder (directory), named unit-testing
. Then, create a file named calculator.py
within your unit-testing
folder. Now, copy the following code into your calculator.py
file:
def add(x, y): """add numbers""" return x + ydef subtract(x, y): """subtract numbers""" return x - ydef divide(x, y): """divide numbers""" return x / ydef multiply(x, y): """multiply numbers""" return x * y
Notice that instead of having your calculator program within a single function, we broke it into four independent functions (units). This is to ensure that each part of the program is independently tested. So if any of the units gives an error during testing, you can easily identify that unit and troubleshoot it without tampering with the other parts of your program.
As earlier mentioned, testing with unittest
involves creating a subclass of the unittest.TestCase
class and then defining methods within the subclass to test individual units of your program.
To show how this works, let's write a test for the add
function in your calculator program. In your unit-testing
folder, create a new file named test_calculator.py
and then copy the following code into it:
import unittestimport calculatorclass TestCalculator(unittest.TestCase): def test_add(self): self.assertEqual(calculator.add(1, 2), 3) self.assertEqual(calculator.add(-1, 1), 0) self.assertEqual(calculator.add(-1, -1), -2) self.assertEqual(calculator.add(0, 0), 0)
In lines one and two of your code, you imported the unittest
and your calculator
modules. You then created a TestCalculator
class, which inherits from the TestCase
class.
In line five of your code, you defined a test_add
method within your class. The method just like every instance methods in Python takes self
as its first argument. Since self
is a reference of the TestCalculator
class, it can access the assertEqual
method provided by the TestCase
class, which TestCalculator
inherits from.
The assertEqual
method checks if two values are equal. It has the following syntax:
self.assertEqual(first, second, msg=None)
In the preceding syntax, first
represents the value you want to test against the second
value. msg
is optional and it represents a custom message that you will receive if the assertion fails. If you don't provide a value for msg
, you will receive a default message.
Now, let's use the explanation of the syntax to explain the use of assertEqual
in your test_add
method. In your first assertion, self.assertEqual(add(1, 2), 3)
checks if the result of add(1, 2)
is equal to 3. If the function returns 3, the test passes. Otherwise, it fails and outputs a message indicating the mismatch. This explanation is the same for the rest of your assertions.
Also, notice that you tested just representative values in your test_add
method. This ensures that your test covers a wide range of possible inputs without redundant code. Here is a breakdown of the representative values in your test_add
method:
- The addition of two positive numbers (
self.assertEqual(calculator.add(1, 2), 3)
). - The addition of a negative number and a positive number (
self.assertEqual(calculator.add(-1, 1), 0)
). - The addition of two negative numbers (
self.assertEqual(calculator.add(-1, -1), -2)
). - The addition of two zeros (
self.assertEqual(calculator.add(0, 0), 0)
).
Now, to run your test, navigate to the unit-testing
directory in your terminal and run the following command:
python -m unittest test_calculator.py
Running the preceding command will give you the following message in your terminal:
.----------------------------------------------------------------------Ran 1 test in 0.000sOK
The output indicates that you ran a single test and it was successful.
To ensure that your test is working as expected, you go back to your calculator.py
file and change the addition (+
) operator in your add
function to subtraction (-
), like this:
def add(x, y): """add numbers""" return x - y
Once you've made the changes, running your test again will raise an AssertionError
showing that your test failed:
Traceback (most recent call last): File ".../test_calculator.py", line 6, in test_add self.assertEqual(calculator.add(1, 2), 3)AssertionError: -1 != 3----------------------------------------------------------------------Ran 1 test in 0.000s
You may be wondering why you have to include the unittest
module in your command instead of running python test_calculator.py
. That's because you are yet to make your test_calculator.py
file a standalone script. So running python test_calculator.py
won't give you any output.
To make your test_calculator.py
executable as a standalone script, you need to add the following to the bottom of your test_calculator.py
file:
if __name__ == "__main__": unittest.main()
Also, the unittest
module requires that you start the name of your test methods with the word test
, otherwise, your test won't run as expected.
To try this, change the name of your test_add
method to add_test
like this:
class TestCalculator(unittest.TestCase): def add_test(self): self.assertEqual(calculator.add(1, 2), 3) self.assertEqual(calculator.add(-1, 1), 0) self.assertEqual(calculator.add(-1, -1), -2) self.assertEqual(calculator.add(0, 0), 0)
Now, if you run the command python test_calculator.py
, you will get a message similar to this:
----------------------------------------------------------------------Ran 0 tests in 0.000sOK
Notice that the preceding output shows that zero tests ran. Now, change your method's name back to test_add
. Also, change the operator in the add
function of your calculator.py
back to addition (+
). Now, rerun the test with the command python test_calculator.py
and compare your output to the preceding output.
It's a common practice for developers to handle invalid input by raising exceptions. So, it's important to write tests that check for these exceptions.
For example, Python will raise a ZeroDivisionError
if you try to divide any number by zero. You can use the unittest
module to test for such errors.
Now, modify your test_calculator.py
file to include a test method for your divide
function:
import unittestimport calculatorclass TestMathOperations(unittest.TestCase): def test_add(self): self.assertEqual(calculator.add(1, 2), 3) self.assertEqual(calculator.add(-1, 1), 0) self.assertEqual(calculator.add(-1, -1), -2) self.assertEqual(calculator.add(0, 0), 0) def test_divide(self): self.assertEqual(calculator.divide(10, 2), 5) self.assertEqual(calculator.divide(9, 3), 3) self.assertEqual(calculator.divide(-6, 2), -3) self.assertEqual(calculator.divide(0, 1), 0) with self.assertRaises(ZeroDivisionError): calculator.divide(10, 0)if __name__ == "__main__": unittest.main()
In the preceding code, your new test_divide
method tests representative values just like your test_add
method. But there is new code at the end that uses assertRaises
. assertRaises
is another assert method provided by unittest
to check if your code raises an exception. Here, you used the method to check for ZeroDivisionError
.
So, if you run the tests now, you will get a message showing two dots (..
) and indicating that you ran two successful tests:
..----------------------------------------------------------------------Ran 2 tests in 0.000sOK
Conclusion
This article has taught you the basics of unit testing in Python using the unittest
testing framework.
You've learned the importance of independently testing individual units of your application and the reasons unittest
is still a popular choice among Python developers. Also, you've learned how to write basic test cases for the add
and divide
functions in your simple calculator program.
With this knowledge, you can now confidently create tests that ensure your code behaves as expected, making it more robust and maintainable. I encourage you to apply these lessons by writing tests for the remaining subtract
and multiply
functions in your calculator program.
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
ADVERTIsem*nT
Damilola Oladele is a software developer and technical writer. He is passionate about community and learning new technologies.
If you read this far, thank the author to show them you care.
Learn to code for free. freeCodeCamp's open source curriculum has helped more than 40,000 people get jobs as developers. Get started
ADVERTIsem*nT