Unittest automated testing framework explanation and actual combat

Why learn unittest

According to the test phase, the test can be divided into unit test, integration test, system test and acceptance test. Unit testing refers to the work of checking and verifying the smallest testable unit in the software in isolation from other parts of the program, usually referring to functions or classes, which are usually developed.

Unit testing can move the testing work forward, find problems early, and reduce the cost of solving problems. At the same time, the unit test can also ensure that there is no problem with the function of a single module, provide preparation for the subsequent integration test, and reduce the problems after synthesis.

For testing, unit testing is the execution of use cases. In order to better manage use cases, we need to learn the unit test framework unittest that comes with Python.

Unittest framework and principle

unittest is a set of testing framework that comes with python, and it is relatively easy to learn. There are four core concepts of the unittest framework:

  • test case: test case. A basic class TestCase is provided in unittest, which can be used to create new test cases. A TestCase example is a test case; the methods of test cases in unittest all start with ,  testand the execution order will be sorted according to the ASCII value of the method name .
  • test fixure:Test Fixture. It is used for the construction and destruction of test case switching, that is, the large part of the environment before the use case test ( SetUppre-condition), and the restoration of the environment after the test ( TearDownpost-condition). For example, you need to log in to obtain tokens before the test, which is the environment required by the test case. After the operation is completed, you need to restore the environment before executing the next use case, so as not to affect the test results of the next use case.
  • test suit: test suite. It is used to execute several test cases that need to be executed together, which is equivalent to a basket. We generally use  TestLoaderto load test cases into the test suite.
  • test runner: Test run. It is used to execute the test case and return the execution result of the test case. It can be combined with graphic or text interfaces to display the returned test results more vividly, such as HTMLTestRunner.

unittest affirmations

Assertions are an important part of a test case and can be used to check that the operation is correct. For example, in the login process, the successful page must have elements similar to the user name. At this time, we can use assertions to determine whether the expected results are consistent with the actual results. If they match, the test case can be considered passed.

In the Python base, there is an  assertassert method, which basically uses the format  assert 表达式,基本信息. In the unittest framework, a built-in assertion method is also provided. If the assertion fails or fails, an  AssertionErrorassertion error will be thrown; if it succeeds, it will be marked as passed.

The following assertion methods all have one  msg=Noneparameter (only the first one is listed in the table, but they all have one), and return by default  None. But if the value of the msg parameter is specified, the message will be returned as the error message of the failure .

method examine
assertEqual(a, b ,msg=None) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(x, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

TestCase test case

Before writing test cases, we need to create a class that inherits the TestCase class in unittest, and then we can actually use the unittest framework to write test cases.

Proceed as follows:

  • import unittestmodule
  • Create a test class that inherits unittest.TestCasethe class
  • Define the test method, the method name must test_start with
  • Call the unittest.main() method to run the test case. unittest.main()The method will search for all test cases and methods starting with test under this module and execute them automatically.

code example

# 注册功能代码

# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}]  

def register(username, password1, password2):
  
  if not all([username, password1, password2]):
    return {'code': 0, 'msg': '所有参数不能为空.'}
  
  # 注册
  for user in users:
    if username == user['username']:
      return {'code': 0, 'msg': '用户名已存在!'}
  
  else:
    if password1 != password2:
      return {'code': 0, 'msg': '两次密码输入不一致!'}
    else:
      if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:
        # 追加到users列表
        users.append({'username': username, 'password': password2})
        return {'code': 0, 'msg': '注册成功.'}
      else:
        return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
  
  


import unittest
from demo import register # 导入被测试代码

class RegisterTest(unittest.TestCase):
    '''注册接口测试类'''
  
    def test_register_success(self):
        '''注册成功'''
        data = ('palien', 'palien', 'palien') # 测试数据
        result = register(*data) # 测试结果
        expected = {'code': 0, 'msg': '注册成功.'} # 预期结果
        self.assertEqual(result, expected) # 断言测试结果与预期结果一致
        # pass
  
    def test_username_exist(self):
        '''注册失败-用户名已存在'''
        data = ('testing', '123456', '123456')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名已存在!'}
        self.assertEqual(result, expected)
  
    def test_username_isnull(self):
        '''注册失败-用户名为空'''
        data = ('', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '所有参数不能为空.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_username_lt18(self):
        '''注册失败-用户名长度大于18位'''
        data = ('palienpalienpalienpalien', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_password1_not_password2(self):
        '''注册失败-两次输入密码不一致'''
        data = ('palien', 'palien1', 'palien2')
        result = register(*data)
        expected = {'code': 0, 'msg': '两次密码输入不一致!'}
        self.assertEqual(result, expected)
        # pass
  
# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':
    unittest.main()

# Output
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。

尝试新的跨平台 PowerShell https://aka.ms/pscore6

PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK
PS D:\d_02_study\01_git\papers\system\02automation> 

TestFixture test fixture

The test fixture of unittest has two ways to use, one is based on the method of the test case: setUp()and  tearDown(); the other is based on the test class: setUpClass()and  tearDownClass().

Code example:

# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}]  

def register(username, password1, password2):
  
  if not all([username, password1, password2]):
    return {'code': 0, 'msg': '所有参数不能为空.'}
  
  # 注册
  for user in users:
    if username == user['username']:
      return {'code': 0, 'msg': '用户名已存在!'}
  
  else:
    if password1 != password2:
      return {'code': 0, 'msg': '两次密码输入不一致!'}
    else:
      if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:
        # 追加到users列表
        users.append({'username': username, 'password': password2})
        return {'code': 0, 'msg': '注册成功.'}
      else:
        return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
  
  


import unittest
from demo import register # 导入被测试代码

class RegisterTest(unittest.TestCase):
    '''注册接口测试类'''
  
    @classmethod # 指明这是个类方法,以类为维度去执行的
    def setUpClass(cls) -> None:
          '''整个测试用例类中的用例执行之前,会先执行此方法'''
          print('-----setup---class-----')
  
    @classmethod
    def tearDownClass(cls) -> None:
          '''整个测试用例类中的用例执行完成后,会执行此方法'''
          print('-----teardown---class-----')
  
    def setUp(self):
        '''每条测试用例执行前都会执行'''
        print('用例{}开始执行...'.format(self))
  
    def tearDown(self):
        '''每条测试用例执行结束后都会执行'''
        print('用例{}执行结束...'.format(self))
  
    def test_register_success(self):
        '''注册成功'''
        data = ('palien', 'palien', 'palien') # 测试数据
        result = register(*data) # 测试结果
        expected = {'code': 0, 'msg': '注册成功.'} # 预期结果
        self.assertEqual(result, expected) # 断言测试结果与预期结果一致
        # pass
  
    def test_username_exist(self):
        '''注册失败-用户名已存在'''
        data = ('testing', '123456', '123456')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名已存在!'}
        self.assertEqual(result, expected)
  
    def test_username_isnull(self):
        '''注册失败-用户名为空'''
        data = ('', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '所有参数不能为空.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_username_lt18(self):
        '''注册失败-用户名长度大于18位'''
        data = ('palienpalienpalienpalien', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_password1_not_password2(self):
        '''注册失败-两次输入密码不一致'''
        data = ('palien', 'palien1', 'palien2')
        result = register(*data)
        expected = {'code': 0, 'msg': '两次密码输入不一致!'}
        self.assertEqual(result, expected)
        # pass
  
# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':
    unittest.main()


### Output
PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
-----setup---class-----
用例test_password1_not_password2 (__main__.RegisterTest)开始执行...   
用例test_password1_not_password2 (__main__.RegisterTest)执行结束...   
.用例test_register_success (__main__.RegisterTest)开始执行...
用例test_register_success (__main__.RegisterTest)执行结束...
.用例test_username_exist (__main__.RegisterTest)开始执行...
用例test_username_exist (__main__.RegisterTest)执行结束...
.用例test_username_isnull (__main__.RegisterTest)开始执行...
用例test_username_isnull (__main__.RegisterTest)执行结束...
.用例test_username_lt18 (__main__.RegisterTest)开始执行...
用例test_username_lt18 (__main__.RegisterTest)执行结束...
.-----teardown---class-----

----------------------------------------------------------------------
Ran 5 tests in 0.004s

OK
PS D:\d_02_study\01_git\papers\system\02automation> 

TestSuit test suite

unittest.TestSuit()A class is used to represent a set of test cases, a collection of test case classes or modules that need to be executed.

Commonly used methods:

  • unittest.TestSuit()
    • addTest(): add a single test case method
    • addTest([...]): Add multiple test case methods, there is a list of method names
  • unittest.TestLoader()
    • loadTestsFromTestCase(测试类名): add a test class
    • loadTestsFromMdule(模块名): add a module
    • discover(测试用例所在的目录): Specify a directory to load, and it will automatically find all test cases that meet the naming rules in this directory

Code example:

'''
    以下三个文件必须在同一文件夹下:
    demo.py
    test_demo.py
    run_test.py
'''

import os
import unittest
import test_demo

# 第一步,创建一个测试套件
suit = unittest.TestSuite()

# 第二步,将测试用例加载到测试套件中

# # 方式一,添加单条测试用例
# case = test_demo.RegisterTest('test_register_success')
# '''
# 创建一个用例对象。
# 注意:通过用例类去创建测试用例对象的时候,需要传入用例的方法名(字符串类型)
# 这里不是像调用普通类中的方法那样通过类名.方法名调用,可以理解为unittest框架的特殊之处
# '''
# suit.addTest(case) # 添加用例到测试套件中

# # 方式二:添加多条用例
# case1 = test_demo.RegisterTest('test_username_exist')
# case2 = test_demo.RegisterTest('test_username_isnull')
# suit.addTest([case1, case2]) # 添加用例到测试套件中。注意这里使用的是suit.addTest()方法而不是suit.addTests()方法

# # 方式三:添加一个测试用例集
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadFromTestCase(test_demo.RegisterTest)) # 通过加载对象从测试类中加载用例到测试套件中

# '''
#     通产我们使用方式4、5比较多,可以根据实际情况来运用。
#     其中方式5还可以自定义匹配规则,默认是会寻找目录下的test*.py文件,即所有以test开头命名的py文件。
# '''
# # 方式四:添加一个模块(其实就是一个后缀名为.py文件,这里就是test_demo.py文件)
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadTestsFromModule(test_demo))  # 通过加载对象从模块中加载测试用例到测试套件中

# 方式五:指定测试用例的所在目录路径,进行加载
loader = unittest.TestLoader() # 创建一个加载对象
case_path = os.path.dirname(os.path.abspath(__file__)) # 文件路径
# print('用例所在的目录路径为:', case_path)
# suit.addTest(loader.discover(case_path))  # 通过加载对象从用例所在目录加载测试用例到测试套件
suit.addTest(loader.discover(start_dir=case_path, pattern='test_demo*.py')) # 两个参数:路径和匹配规则

TestRunner execution use case

testRunnerUsed to execute use cases and generate corresponding test reports. There are two forms of test reports: one is  text文本; one is  html格式.

The html format is  HTMLTestRunnergenerated with the help of a plug-in, which is an extension of the unittest framework of the Python standard library, and can generate a clear and intuitive html test report. The prerequisite for use is that it needs to be downloaded  HTMLTestRunner.py. After the download is complete, it can be placed in the scripts directory under the python installation directory.

The text text is too crude compared to html, it is no different from the output console, and it is almost inapplicable.

Code example:

# demo.py,与test_demo.py和run_test.py在同一目录下

# 导入模块
import unittest
import os
import test_demo
from HTMLTestReportCN import HTMLTestRunner

# 用例文件所在目录
base_path = os.path.dirname(os.path.abspath(__file__))
# report_path = base_path + 'report.html'

# 打开报告文件


# 创建测试套件
suit = unittest.TestSuite()

# 通过模块加载测试用例
loader = unittest.TestLoader()
suit.addTest(loader.discover(start_dir=base_path, pattern='test_demo*.py'))

# 创建测试运行程序启动器
runner = HTMLTestRunner(
    stream=open('report.html', 'w', encoding='utf-8'),   # 打开一个报告文件,并将句柄传给stream
    tester='palien',                    # 报告中显示的测试人员  
    description='注册接口测试报告',      # 报告中显示的描述信息
    title='自动化测试报告'               # 测试报告标题
)

# 使用启动器去执行测试套件里面的测试用例
runner.run(suit)

Related parameter description:

  • stream: Specify the output method
  • tester: The name of the tester to display in the report
  • description: Descriptive information to be displayed in the report
  • title: The title of the test report
  • verbosity: Indicates the detail level of the test report information, a total of three values, the default is 2
    • 0(Silent mode): Only the total test case book and total results can be obtained, such as: a total of 100, failure 90
    • 1(default mode): Similar to silent mode, except that there is an F in front of each successful use case. There is an F in front of each failed use case
    • 2(verbose mode): Test results will show all relevant information for each test case

After running, a file will be generated under the project directory report.html, open it in the browser, and you can see the test report.

problem record

In the process of learning, I encountered some problems and recorded them.

  • HTMLTestRunnerdownload

It has been verified that the following two files support the generation of the report in the screenshot above.

  • Error  TypeError: a bytes-like object is required, not 'str'resolution

This is caused by the wrong way to open the file. Attach the problem solving blog: https://blog.csdn.net/Teresa_lqs/article/details/126250505?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task- blog-2~default~CTRLIST~Rate-1-126250505-blog-116235034.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-126250505-blog-116 235034.pc_relevant_default&utm_relevant_index= 1

  • Chinese report garbled problem

The problem is as shown in the figure:

Guess you like

Origin blog.csdn.net/xiao1542/article/details/130622535