A few days ago, I listened to some big man of the company’s experience about programming, which talked about “test-driven development”, I feel my testing skills are weak, so I write this article, hoping to have an introduction to testing. During this time, I also realized the value of testing. In a word, learning to test can make your development more efficient. \
This article will introduce the following two aspects:
- Test with Coverage
- Mock
Test with Coverage
Test coverage is often used to measure the adequacy and completeness of tests. Broadly speaking, there are two main categories: project-oriented requirements coverage and more technology-oriented code coverage. For developers, we focus more on code coverage.
Code coverage is the percentage of the total number of items that are executed at least once. If the number of items is a statement, it corresponds to line coverage; If the number of items is a function, the corresponding is the function coverage; If the number of items is a path, the corresponding path coverage, and so on. The fundamental purpose of statistics on code coverage is to identify potentially missing test cases and target them, as well as to identify code that is obsolete due to requirements changes, etc. In general, we want as much code coverage as possible. The higher the code coverage, the better the proof that your test case design is adequate and complete, but the cost of testing increases as the code coverage increases.
In Python, the Coverage module helps us implement line-of-code coverage, and we can easily use it to fully test line-of-code coverage.
We use an example to illustrate the use of the Coverage module.
First, we have the script func_add.py, which implements the add function as follows:
# -*- coding: utf- 8 - -*-
def add(a, b):
if isinstance(a, str) and isinstance(b, str):
return a + '+' + b
elif isinstance(a, list) and isinstance(b, list):
return a + b
elif isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a + b
else:
return None
Copy the code
In the add function, there are four cases of addition: strings, lists, attribute values, and other cases.
Next, we use the unittest module for unit testing, with the script (test_func_add.py) as follows:
import unittest
from func_add import add
class Test_Add(unittest.TestCase):
def setUp(self):
pass
def test_add_case1(self):
a = "Hello"
b = "World"
res = add(a, b)
print(res)
self.assertEqual(res, "Hello+World")
def test_add_case2(self):
a = 1
b = 2
res = add(a, b)
print(res)
self.assertEqual(res, 3)
def test_add_case3(self):
a = [1.2]
b = [3]
res = add(a, b)
print(res)
self.assertEqual(res, [1.2.3])
def test_add_case4(self):
a = 2
b = "3"
res = add(a, b)
print(None)
self.assertEqual(res, None)
if __name__ == '__main__'Unittest.testsuite () = unittest.testSuite ()'test_add_case1'))
suite.addTest(Test_Add('test_add_case2'))
# suite.addTest(Test_Add('test_add_case3'))
# suite.addTest(Test_Add('test_add_case4'))
run = unittest.TextTestRunner()
run.run(suite)
Copy the code
In this test, we only tested the first two use cases, which tested string and numeric addition.
Run the test script on the command line by typing coverage run test_func_add.py. The output is as follows:
Hello+World
3.
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Copy the code
To generate a report of line coverage, type the command coverage HTML. The htmlcov folder is generated. Open the index.html file in the folder and you can see the coverage for this execution as shown below: \
An overview of test coverage results
Let’s click func_add.py to see what the add function looks like:
Test coverage of the func_add.py script
As you can see, the first two test cases of the unit test script test_func_add.py only cover the left-hand green portion of the add function, not the red portion, with 75% line coverage.
Therefore, there are still two cases that are not covered, indicating that the test cases in our unit tests are not sufficient.
In test_func_add.py, we removed the comments from the main function and added the latter two test cases, and then ran the coverage module above to regenerate htmlCov. Func_add. py’s line coverage looks like this:
Test coverage of the func_add.py script after adding test cases
As you can see, after adding the test case, we call the Add function with 100% line coverage, covering all the code.
Mock
The word Mock means to simulate in English, so we can guess that the main function of this library is to simulate things. Mock is a Python library designed to support unit testing. It uses Mock objects to simulate the behavior of specified Python objects. In Python3, a mock is a module that AIDS in unit testing. It allows you to replace parts of your system with mock objects and assert the way they have been used.
In actual production, projects are very complex, and the following problems will be encountered when unit testing them:
- Interface dependencies
- External interface call
- The test environment is very complex
Unit tests should only be tested against the current unit, and all internal or external dependencies should be stable and have been tested elsewhere. Using mocks allows you to emulate and replace external dependent component implementations, allowing unit tests to focus only on the current unit functionality.
Let’s use a simple example to illustrate the use of mock modules.
First, we have mock_multipy.py, which implements the multipy function in the Operator class. Here we can assume that the multipy function is not fully implemented, but there is a function like this:
# -*- coding: utf- 8 - -*-
# mock_multipy.py
class Operator():
def multipy(self, a, b):
pass
Copy the code
Although we didn’t implement the multipy function, we wanted to test its functionality by using the Mock class in the Mock module. The mock_example.py script for the test looks like this:
# -*- coding: utf- 8 - -*-
from unittest import mock
import unittest
from mock_multipy importOperator # test Operator class class TestCount(unittest.TestCase): def test_add(self): Op = Operator() # Using the Mock class, we assume that the result returned is15
op.multipy = mock.Mock(return_value=15) # call multipy with input arguments4.5Result = op.multipy()4.5) # declare whether the return result is15
self.assertEqual(result, 15)
if __name__ == '__main__':
unittest.main()
Copy the code
Let’s make a few remarks about the above code.
op.multipy = mock.Mock(return_value=15)
Copy the code
A Mock class is used to simulate calling the multipy() function of the Operator class. Return_value defines the return value of the multipy() method.
result = op.multipy(4.5)
Copy the code
The result value calls multipy() with input arguments of 4,5, but is not actually called, and finally assertEqual() asserts whether the result returned is the expected result is 15. The output is as follows:
Ran 1 test in 0.002s
OK
Copy the code
Mock classes allow us to test by imagining the results of function execution even when multipy is not implemented, so that subsequent functions that rely on multipy do not affect subsequent code testing.
Using the patch function in the Mock module, we can simplify the script code for the above tests as follows:
# -*- coding: utf- 8 -- * -import unittest
from unittest.mock import patch
from mock_multipy import Operator
# test Operator class
class TestCount(unittest.TestCase):
@patch("mock_multipy.Operator.multipy")
def test_case1(self, tmp):
tmp.return_value = 15
result = Operator().multipy(4.5)
self.assertEqual(15, result)
if __name__ == '__main__':
unittest.main()
Copy the code
The Patch () decorator makes it easy to emulate classes or objects in module tests. During the test, the object you specify will be replaced with a mock (or other object) and restored at the end of the test.
If we implement multipy later, can we still test it?
Modify the mock_multipy.py script as follows:
# -*- coding: utf- 8 - -*-
# mock_multipy.py
class Operator():
def multipy(self, a, b):
return a * b
Copy the code
At this point, we run the mock_example.py script again, and the test still passes because the multipy function returns the value that we mock instead of calling the multipy function in the real Operator class.
We modify the mock_example.py script as follows:
# -*- coding: utf- 8 - -*-
from unittest import mock
import unittest
from mock_multipy importOperator # test Operator class class TestCount(unittest.TestCase): def test_add(self): Op = Operator() # Add side_effect op.multipy = mock.Mock(return_value=)15, side_effect=op.multipy4.5Result = op.multipy(4.5) # declare whether the return result is15
self.assertEqual(result, 15)
if __name__ == '__main__':
unittest.main()
Copy the code
The side_effect argument is the opposite of the return_value argument. It assigns replaceable results to the mock, overwriting return_value. Simply put, a mock factory call will return side_effect instead of return_value. Therefore, if you set side_effect to multipy in the Operator class, return_value is invalid.
Run the modified test script. The test result is as follows:
Ran 1 test in 0.004s
FAILED (failures=1)
15! =20
Expected :20
Actual :15
Copy the code
The multipy function returns 20, which is not the expected value of 15. This is due to side_effect, which calls the multipy function of the Operator class, so it returns 20.
Change 15 to 20 in self.assertequal (result, 15) and run the test as follows:
Ran 1 test in 0.002s
OK
Copy the code
This is the end of sharing, thank you for reading ~
Author: Jclian, like algorithms, love to share, hope to make more like-minded friends, together in the path of learning Python further!
Recommended reading
Complete PyPy mastery in 5 minutes
Make your Python code run faster with PyPy! \
Someone poisoned the code! Use the PIP install command with caution
Click below to read the article and join the community
Give it a thumbs up