This article explores the significance of software testing and why you should pay attention to it. We will learn how to design unit tests and how to write Python unit tests. In particular, we will explore two of the most commonly used unit testing frameworks in Python: unittest and pytest.
Key Points
Introduction to Software Testing
Software testing is a process of checking the behavior of software products to evaluate and verify that they comply with specifications. A software product may contain thousands of lines of code and hundreds of components that work together. If a line of code does not work properly, the error may propagate and cause other errors. Therefore, to ensure that the program runs as expected, it must be tested.
Because modern software can be quite complex, there are multiple levels of testing to evaluate different aspects of correctness. According to the ISTQB certification test basic level syllabus, there are four levels of software testing:
This article will discuss unit testing, but before going into it, I would like to introduce an important principle in software testing.
Tests can only prove the existence of defects, but cannot prove the absence of defects.
— ISTQB CTFL Syllabus 2018
In other words, even if all the tests you run do not show any failures, it will not prove that your software system is free of errors, or another test case will not find any flaws in your software behavior.
This is the first test level, also known as component testing. In this section, a single software component is tested. Depending on the programming language, a software unit may be a class, a function, or a method. For example, if you have a Java class called ArithmeticOperations that contains multiple and divide methods, unit testing of the ArithmeticOperations class needs to test the correct behavior of the multiply and divide methods.
Unit testing is usually performed by software testers. To run unit tests, software testers (or developers) need to access the source code because the source code itself is the object being tested. Therefore, this software testing method that directly tests the source code is called white box testing.
You may be wondering why you should worry about software testing and whether it is worth it. In the next section, we will analyze the motivations behind the testing software system.
The main advantage of software testing is that it improves the quality of the software. Software quality is crucial, especially in the world where software handles all kinds of things in our daily activities. Improving software quality is still an overly vague goal. Let's try to better illustrate what we call the quality of software. According to ISO/IEC standard 9126-1 ISO 9126, software quality includes the following factors:
If you own a company, then you should carefully consider software testing activities as it will affect your business. For example, in May 2022, Tesla recalled 130,000 cars due to problems with the vehicle’s infotainment system. This problem was then solved through a software update distributed "in the air". These failures have caused time and money to the company and also caused problems for customers, as they were unable to use their cars for a while. Testing software does cost money, but companies can also save millions of technical support.
Unit testing focuses on checking that the software is running correctly, which means checking that the mapping between input and output is done correctly. As a low-level testing activity, unit testing helps identify errors early so that they do not propagate them to higher levels of the software system.
Other advantages of unit testing include:
Design testing strategies
Now let's see how to design a test strategy.
Before starting to plan your testing strategy, there is an important question to answer. What parts of the software system do you want to test?
This is a key issue, because exhaustive testing is impossible. Therefore, you can't test all possible inputs and outputs, but you should prioritize the tests based on the risks involved.
A number of factors need to be considered when defining the scope of the test:
Once you have defined the test scope (specifying what you should test and what you should not test), you can discuss the features that a good unit test should have.
A step is missing before delving into unit testing in Python. How do we organize the tests to make them clean and readable? We use a pattern called Preparation, Execution, and Assertion (AAA).
The Preparation, Execution, and Assertion (AAA) pattern is a common strategy for writing and organizing unit tests. It works as follows:
This strategy provides a clean way to organize unit tests by separating all the main parts of the test (setup, execution, and validation). Additionally, unit tests are easier to read because they all follow the same structure.
Unit tests in Python: unittest or pytest?
We will now discuss two different unit testing frameworks in Python. These two frameworks are unittest and pytest.
The Python standard library contains the unittest unit testing framework. This framework is inspired by JUnit, which is a unit testing framework in Java.
As stated in the official documentation, unittest supports several important concepts we will mention in this post:
unittest has its own way of writing tests. In particular, we need:
Since unittest is already installed, we are ready to write our first unit test!
Suppose we have the BankAccount class:
import unittest class BankAccount: def __init__(self, id): self.id = id self.balance = 0 def withdraw(self, amount): if self.balance >= amount: self.balance -= amount return True return False def deposit(self, amount): self.balance += amount return True
We cannot withdraw money that exceeds the available amount of deposit, so let's test if our source code handles this situation correctly.
In the same Python file, we can add the following code:
class TestBankOperations(unittest.TestCase): def test_insufficient_deposit(self): # Arrange a = BankAccount(1) a.deposit(100) # Act outcome = a.withdraw(200) # Assert self.assertFalse(outcome)
We are creating a class called TestBankOperations which is a subclass of unittest.TestCase. In this way, we are creating a new test case.
In this class, we define a single test function whose method begins with test. This is important because every test method must start with the word test.
We expect this test method to return False, which means the operation failed. To assert the result, we use a special assertion method called assertFalse().
We are ready to perform the test. Let's run this command on the command line:
python -m unittest example.py
Here, example.py is the name of the file containing all the source code. The output should look like this:
<code>. ---------------------------------------------------------------------- Ran 1 test in 0.001s OK</code>
Very good! This means our test was successful. Now let's see what the output looks like when there is a failure. We add a new test to the previous class. Let's try to deposit negative amounts, which is certainly impossible. Will our code handle this situation?
This is our new test method:
def test_negative_deposit(self): # Arrange a = BankAccount(1) # Act outcome = a.deposit(-100) # Assert self.assertFalse(outcome)
We can use the detailed mode of unittest to perform this test by using the -v flag:
python -m unittest -v example.py
The output is different now:
<code>test_insufficient_deposit (example.TestBankOperations) ... ok test_negative_deposit (example.TestBankOperations) ... FAIL ====================================================================== FAIL: test_negative_deposit (example.TestBankOperations) ---------------------------------------------------------------------- Traceback (most recent call last): File "example.py", line 35, in test_negative_deposit self.assertFalse(outcome) AssertionError: True is not false ---------------------------------------------------------------------- Ran 2 tests in 0.002s FAILED (failures=1)</code>
In this case, the detailed logo gives us more information. We know that test_negative_deposit fails. In particular, AssertionError tells us that the expected result should be false, but True is not false, which means that the method returns True.
unittest framework provides different assertion methods according to our needs:
Now that we have a basic understanding of how to write unit tests using the unittest framework, let's take a look at another Python framework called pytest.
pytest framework is a Python unit testing framework that has some related features:
Since pytest is not installed by default, we must first install it. Note that pytest requires Python 3.7.
Installing pytest is very easy. You just need to run the following command:
import unittest class BankAccount: def __init__(self, id): self.id = id self.balance = 0 def withdraw(self, amount): if self.balance >= amount: self.balance -= amount return True return False def deposit(self, amount): self.balance += amount return True
Then check that everything is installed correctly by typing:
class TestBankOperations(unittest.TestCase): def test_insufficient_deposit(self): # Arrange a = BankAccount(1) a.deposit(100) # Act outcome = a.withdraw(200) # Assert self.assertFalse(outcome)
The output should look like this:
python -m unittest example.py
Very good! Let's write the first test using pytest.
We will use the BankAccount class we wrote earlier and we will test the same method as before. In this way, it is easier to compare the efforts required to write tests using these two frameworks.
To use pytest for testing, we need:
So we create a file named test_bank.py and put it into a folder. This is what our first test function looks like:
<code>. ---------------------------------------------------------------------- Ran 1 test in 0.001s OK</code>
As you have noticed, the only change compared to the unittest version is the assert part. Here we use the normal Python assertion method.
Now we can take a look at the test_bank.py file:
def test_negative_deposit(self): # Arrange a = BankAccount(1) # Act outcome = a.deposit(-100) # Assert self.assertFalse(outcome)
To run this test, let's open a command prompt in the folder containing the test_bank.py file. Then, run the following command:
python -m unittest -v example.py
The output will look like this:
<code>test_insufficient_deposit (example.TestBankOperations) ... ok test_negative_deposit (example.TestBankOperations) ... FAIL ====================================================================== FAIL: test_negative_deposit (example.TestBankOperations) ---------------------------------------------------------------------- Traceback (most recent call last): File "example.py", line 35, in test_negative_deposit self.assertFalse(outcome) AssertionError: True is not false ---------------------------------------------------------------------- Ran 2 tests in 0.002s FAILED (failures=1)</code>
In this case, we can see how easy it is to write and execute tests. Furthermore, we can see that we write less code than unittest. The results of the test are also easy to understand.
Let's continue to look at the failed tests!
We use the second method we wrote before, which is called test_negative_deposit. We refactor the assert part and the result is as follows:
pip install -U pytest
We run the tests as before, which should be the output:
pytest --version
By parsing the output we can read collected 2 items, which means that two tests have been performed. Scroll down and we can see an error occurred while testing the test_negative_deposit method. In particular, errors occur when evaluating assertions. In addition, the report also states that the value of the outcome variable is True, which means that the deposit method contains errors.
Since pytest uses the default Python assertion keyword, we can compare any output we get to with another variable that stores the expected result. All of this requires no special assertion methods.
Conclusion
All in this article, we introduce the basics of software testing. We discovered why software testing is crucial and why everyone should test their code. We discuss unit testing and how to design and implement simple unit testing in Python.
We use two Python frameworks called unittest and pytest. Both have useful features, and they are two of the most commonly used frameworks in Python unit testing.
Finally, we see two basic test cases to give you an idea of how to write tests in preparation, execution, and assertion patterns.
I hope I have convinced you of the importance of software testing. Choose a framework, such as unittest or pytest, and start testing – because the extra effort is worth it!
If you like this article, you may also find the following useful:
FAQs about Python unit testing
What are unit tests in Python? Unit testing in Python is a software testing technique in which a single unit or component of a program is tested isolated to ensure that each unit works as expected.
Why is unit testing important in Python development? Unit testing helps ensure the correctness of individual components in a Python program. It helps to detect errors early, provides a safe net for code changes, and supports maintainability of code.
How to write unit tests in Python? Unit tests in Python are often written using the built-in unittest module. You create test classes inherited from unittest.TestCase and write test methods in these classes. Test methods usually start with "test".
What other frameworks can I use for Python unit testing besides unittest? Yes, besides unittest there are other popular Python testing frameworks such as pytest and nose2. These frameworks provide different features and syntaxes, allowing developers to choose the one that best suits their needs.
What is the role of fixture in Python unit testing? Fixture is a way to set preconditions in Python and clean up after testing. They help ensure that the tests are independent and can run independently.
What is test coverage and why it is important? Test coverage measures the percentage of the code base your tests execute. It helps identify untested code and ensures that your tests are comprehensive, thus reducing the possibility of errors being discovered.
What are the best practices for writing effective unit tests in Python? Yes, some best practices include writing independent and isolated tests, using descriptive test method names, and testing boundary situations. In addition, try to get good test coverage and run tests frequently.
The above is the detailed content of A Guide to Python Unit Testing with unittest and pytest. For more information, please follow other related articles on the PHP Chinese website!