This paper introduces the unit test framework unitest which is commonly used in python

  • 2021-08-28 20:32:03
  • OfStack

1. Introduction of main functional modules of unitest

unitest mainly includes TestCase, TestSuite, TestLoader, TextTestRunner and TextTestResult.

TestCase: An TestCase instance is a test case, and a test case is a complete test process, including the construction of the pre-test environment, the execution of the test code, and the restoration or destruction of the post-test environment. The essence of meta-testing is here. A test case is a complete test unit, which can check and verify a specific problem. TestSuite: Multiple test case sets in one are TestSuite, and TestSuite can also be nested with TestSuite. TestLoader: The role of TestLoader is to load Testcase into TestSuite. TextTestRunner: TextTestRunner is used to execute test cases, where run (test) executes the run (result) method in TestSuite/TestCase. TextTestResult: TextTestResult is used to store test results, including how many test cases were run, how many succeeded, how many failed, and so on.

The whole process is as follows: write TestCase, then load TestCase to TestSuite by TestLoader, and then run TestSuite by TextTestRunner, and the running results are saved in TextTestResult.

2. Example introduction

First, prepare several methods to be tested and write them in test_func. py.


def add(a, b):
  return a + b


def multi(a, b):
  return a * b


def lower_str(string):
  return string.lower()


def square(x):
  return x ** 2

After preparing several methods to be tested, write a test case for these methods to our_testcase. py.


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) #  Deliberately designed here 1 Use cases that will go wrong, test 4 The square of is equal to the square of 17 In fact, it doesn't mean. 
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()

After writing here, enter the command line terminal and execute python our_testcase. py. The execution result is as follows.


...F
======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

Here, analyze this execution result. First of all, we can see that 1 ran four test cases and failed one, and gave the reason for the failure, AssertionError: 17! = 16, this is an error vulnerability that we deliberately left behind, which was tested by the test case.

Line 1... in F, 1 dot. For test success, F for failure. In our test results, the first three succeeded, the fourth failed, a total of 4 tests. In the rest of the symbols, E for error and S for skipping.

One special point is that the order of execution of tests has nothing to do with the order of methods, and the four tests are executed randomly.

When each test method is written, it should start with test, such as test_square, otherwise it will not be recognized by unitest.

Adding verbosity parameter to unitest. main () can control the detailed program of output error report. The default is 1. If it is set to 0, the execution result of each 1 use case will not be output, that is, the execution result content of the first line above. If set to 2, the detailed execution result is output.

Modify the main function in our_testcase. py.


if __name__ == '__main__':
  unittest.main(verbosity=2)

The implementation results are as follows.


test_add (__main__.TestFunc)
Test func add ... ok
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

It can be seen that the detailed execution of each use case, as well as the use case name and use case description, are output. Add "" "Doc String" "in the code example under the test method. When the use case is executed, the string will be used as the description of the use case, and adding appropriate comments can make the output test report easier to read.

3. Organize TestSuite

According to the above test method, we can't control the order of use cases execution, which is obviously unreasonable, because in the first test process, we definitely need to control the test of some use cases first and then some use cases, and these use cases have a causal relationship. Here, we need to use TestSuite. The case that we added to the TestSuite is executed in the order in which it was added.

Another problem is that we only have one test file now, so we can execute it directly. However, if there are multiple test files, how to organize them, we can't execute them one by one. The answer is also in TestSuite.

Create a new file, test_suite. py.


import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)
  runner = unittest.TextTestRunner(verbosity=2)
  runner.run(suite)

The implementation results are as follows.


test_square (our_testcase.TestFunc)
Test func square ... FAIL
test_lower_str (our_testcase.TestFunc)
Test func lower_str ... ok
test_multi (our_testcase.TestFunc)
Test func multi ... ok

======================================================================
FAIL: test_square (our_testcase.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "/Users/luyuze/projects/test/our_testcase.py", line 27, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)

In this way, the order in which use cases are executed is the order in which we added them.

The addTests () method of TestSuite was used above and passed directly into the TestCase list, and there are 1 other ways to add use cases to TestSuite.


#  Direct use addTest Method to add a single TestCase
suite.addTest(TestMathFunc("test_multi"))


#  Use loadTestFromName, Incoming module name .TestCase Name, the following two methods have the same effect 
suite.addTests(unittest.TestLoader().loadTestsFromName('our_testcase.TestFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['our_testcase.TestFunc']))


# loadTestsFromTestCase() , incoming TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFunc))

It is impossible to sort case by the method of TestLoader, and suite can also be set in suite.

4. Output file

The use cases are organized, but the results can only be output to the console, so there is no way to view the previous execution records. We want to output the results to a file.

Modify test_suite. py.


import unittest
from our_testcase import TestFunc

if __name__ == '__main__':
  suite = unittest.TestSuite()
  tests = [TestFunc("test_square"), TestFunc("test_lower_str"), TestFunc("test_multi")]
  suite.addTests(tests)

  with open('UnitestTextReport.txt', 'a') as f:
    runner = unittest.TextTestRunner(stream=f, verbosity=2)
    runner.run(suite)

5. Treatment before and after the test

In previous tests, there may be such a problem: What if you want to prepare the environment before the test and do some cleaning after the test is finished? What you need here are setUp () and tearDown ().

Modify our_testcase. py.


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""
  
  def setUp(self):
    print("do something before testcase")

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4))
    self.assertNotEqual(35, square(6))
    
  def tearDownClass(self):
    print("do something after testcase")


if __name__ == '__main__':
  unittest.main(verbosity=2)

Implementation results:


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) #  Deliberately designed here 1 Use cases that will go wrong, test 4 The square of is equal to the square of 17 In fact, it doesn't mean. 
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()
0

You can see that setUp () and tearDown () are executed once before and after each case. If you want to prepare and clean up your environment before all case executions and after all case executions, you can use setUpClass () and tearDownClass ().


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) #  Deliberately designed here 1 Use cases that will go wrong, test 4 The square of is equal to the square of 17 In fact, it doesn't mean. 
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()
1

6. Skip case

If we temporarily want to skip an case without execution, unitest also has a corresponding method.

1. skip decorator


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) #  Deliberately designed here 1 Use cases that will go wrong, test 4 The square of is equal to the square of 17 In fact, it doesn't mean. 
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()
2

Implementation results:


test_add (__main__.TestFunc)
Test func add ... skipped 'do not run this case'
test_lower_str (__main__.TestFunc)
Test func lower_str ... ok
test_multi (__main__.TestFunc)
Test func multi ... ok
test_square (__main__.TestFunc)
Test func square ... FAIL

======================================================================
FAIL: test_square (__main__.TestFunc)
Test func square
----------------------------------------------------------------------
Traceback (most recent call last):
 File "our_testcase.py", line 28, in test_square
  self.assertEqual(17, square(4))
AssertionError: 17 != 16

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1, skipped=1)

The result shows that a total of 4 tests were executed, 1 failed and 1 was skipped.

skip Decorator 1 has three unittest. skip (reason), unittest. skipIf (condition, reason), unittest. skipUnless (condition, reason), skip unconditionally skipped, skipIf skipped when condition is True, skipUnless skipped when condition is False.

2. TestCase. skipTest () method


import unittest
from test_func import *


class TestFunc(unittest.TestCase):
  """Test test_func.py"""

  def test_add(self):
    """Test func add"""
    self.assertEqual(3, add(1, 2))
    self.assertNotEqual(3, add(1, 3))

  def test_multi(self):
    """Test func multi"""
    self.assertEqual(6, multi(2, 3))
    self.assertNotEqual(8, multi(3, 3))

  def test_lower_str(self):
    """Test func lower_str"""
    self.assertEqual("abc", lower_str("ABC"))
    self.assertNotEqual("Dce", lower_str("DCE"))

  def test_square(self):
    """Test func square"""
    self.assertEqual(17, square(4)) #  Deliberately designed here 1 Use cases that will go wrong, test 4 The square of is equal to the square of 17 In fact, it doesn't mean. 
    self.assertNotEqual(35, square(6))


if __name__ == '__main__':
  unittest.main()
4

The effect is the same as that of the first one.

The above is a comprehensive introduction to the unit test framework unitest, which is commonly used in python. For more information about python unit test framework unitest, please pay attention to other related articles on this site!


Related articles: