不拼花哨,只拼实用:unittest指南,干货为王!

Python为开发者提供了内置的单元测试框架 unittest,它是一种强大的工具,能够有效地编写和执行单元测试。unittest 提供了完整的测试结构,支持自动化测试的执行,能够对测试用例进行组织,并且提供了丰富的断言方法。最终,unittest 会生成详细的测试报告,这个框架非常简单且易于使用。

unittest核心概念

unittest 中,有四个核心概念:

  1. TestCase(测试用例):每个测试用例实例用于封装一个或多个测试函数。
  2. TestSuite(测试套件):这是多个测试用例的集合,用于组织和执行多个测试用例。
  3. TestLoader(测试加载器):这是一个用于将测试用例加载到测试套件中的工具。
  4. TextTestRunner(测试运行器):这是用于执行测试用例的运行器,负责运行测试并生成结果报告。
  5. Fixture(环境管理机制):这是测试用例的环境搭建和销毁部分,包括前置条件和后置条件。

unittest的工作流程

  1. 编写继承自 unittest.TestCase 的测试用例类,其中每个测试函数都是一个独立的测试用例。
  2. 使用 TestLoader 加载测试用例,并将它们组织成 TestSuite 对象。
  3. 使用 TestRunner 运行 TestSuite 中的测试用例,并输出测试结果。

使用unittest初级指南

  1. 导入 unittest 模块以及被测试的文件或类。
  2. 创建一个测试类,并继承 unittest.TestCase,所有自定义的单元测试类都要继承它,作为基类。
  3. 重写 setUptearDown 方法,用于初始化和清理测试环境(如果有必要)。
  4. 定义测试函数,函数名以 test_ 开头,这样才能被识别并执行。
  5. 在测试函数中使用断言来判断测试结果是否符合预期。
  6. 调用 unittest.main() 方法运行测试用例,按照函数名的排序执行测试。

以下是一个简单的例子:

import unittest

def login(username, password):
    if username == 'kira' and password == '123':
        res = {"code": 200, "msg": "登录成功"}
        return res
    return {"code": 400, "msg": "登录失败"}

class TestLogin(unittest.TestCase):

    def test_login_success(self):
        """测试登录成功"""
        test_data = {"username": "kira", "password": "test"}
        expect_data = {"code": 200, "msg": "登录成功"}
        res = login(**test_data)
        self.assertEqual(res, expect_data)

    def test_login_error_with_error_password(self):
        """账号正确,密码错误,登录失败"""
        test_data = {"username": "kira", "password": "12345"}
        expect_data = {"code": 400, "msg": "登录失败"}
        res = login(**test_data)
        self.assertEqual(res, expect_data)

    # 更多测试函数类似...

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

以上是一个简单的测试用例,包含了两个测试函数。运行脚本将输出测试结果。

unittest核心概念

测试脚手架

测试脚手架 是测试用例的前置条件和后置条件,确保测试环境的初始化和清理,从而保证测试的准确性和可靠性。

import unittest

class MyTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # 类级别的前置条件设置,整个类运行最先只执行一次
        print("setUpClass")

    @classmethod
    def tearDownClass(cls):
        # 类级别的后置条件清理,整个类运行最后结束执行一次
        print("tearDownClass")

    def setUp(self):
        # 测试方法级别的前置条件设置,所有测试方法运行前都执行一次
        print("setUp")

    def tearDown(self):
        # 测试方法级别的后置条件清理,所有测试方法运行结束都执行一次
        print("tearDown")

    def test_example(self):
        # 测试用例
        print("test_example")

if __name__ == "__main__":
    unittest.main()
  1. setUp():每个测试方法运行前执行,用于测试前置的初始化工作。
  2. tearDown():每个测试方法结束后执行,用于测试后的清理工作。
  3. setUpClass():所有的测试方法运行前执行,用于单元测试类运行前的准备工作。使用 @classmethod 装饰器装饰,整个测试类运行过程中只会执行一次。
  4. tearDownClass():所有的测试方法结束后执行,用于单元测试类运行后的清理工作。使用 @classmethod 装饰器装饰,整个测试类运行过程中只会执行一次。

测试用例

测试用例 是最小的测试单元,用于检测特定的输入集合的特定的返回值。unittest 提供了 TestCase 基类,所有的测试类都需要继承该基类,而在该类下的函数如果以 test_ 开头,则被标识为测试函数:

class MyTestCase(unittest.TestCase

):

    def test_addition(self):
        result = 2 + 3
        self.assertEqual(result, 5)  # 使用断言方法验证结果是否相等

    def test_subtraction(self):
        result = 5 - 3
        self.assertTrue(result == 2)  # 使用断言方法验证结果是否为True

    # 更多测试用例函数...

断言方法

以下是常用的断言方法:

  • assertEqual(a, b, msg=None):验证 a 等于 b。
  • assertNotEqual(a, b):验证 a 不等于 b。
  • assertTrue(x):验证 x 是否为 True。
  • assertFalse(x):验证 x 是否为 False。
  • assertIs(a, b):验证 a 是否是 b。
  • assertIsNot(a, b):验证 a 是否不是 b。
  • assertIsNone(x):验证 x 是否为 None。
  • assertIsNotNone(x):验证 x 是否不为 None。
  • assertIn(a, b):验证 a 是否在 b 中。
  • assertNotIn(a, b):验证 a 是否不在 b 中。
  • assertIsInstance(a, b):验证 a 是否是 b 类型的实例。
  • assertNotIsInstance(a, b):验证 a 是否不是 b 类型的实例。

可以使用这些方法进行断言,也可以直接使用原生的assert来断言,如果断言失败,测试用例会被定义为执行失败。

忽略特定测试方法

unittest 提供了一些方法来跳过特定的测试用例:

  • @unittest.skip(reason):强制跳过,reason 是跳过的原因。
  • @unittest.skipIf(condition, reason):当 condition 为 True 时跳过。
  • @unittest.skipUnless(condition, reason):当 condition 为 False 时跳过。
  • @unittest.expectedFailure:如果测试失败,这个测试用例不会计入失败的统计。
  • 使用实例方法:self.skipTest() 使用和上述类似。
import sys
import unittest

class Test1(unittest.TestCase):
    @unittest.expectedFailure  # 即使失败也会被计为成功的用例
    def test_1(self):
        assert 1 + 1 == 3

    @unittest.skip('无条件跳过')  # 不管什么情况都会进行跳过
    def test_2(self):
        print("2+2...", 4)

    @unittest.skipIf(sys.platform == "win32", "跳过")  # 如果系统平台为 Windows 则跳过
    def test_3(self):
        print("3+3...", 6)

    @unittest.skipUnless(sys.platform == "win32", "跳过")  # 除非系统平台为 Windows,否则跳过
    def test_4(self):
        print("4+4...", 8)

    def test_5(self):
        self.skipTest("跳过")
        print("5+5...", 10)

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

测试套件

测试套件用于收集和组织多个测试用例,便于集中执行。

  1. 通过 unittest.main() 方法直接加载单元测试的测试模块,这是一种简单的加载方式。所有测试用例的执行顺序按照方法名的字符串表示的 ASCII 码升序排序,通过命名时使用 test_01_xxx 来指定执行顺序。
  2. 将所有的单元测试用例 TestCase 加载到测试套件 Test Suite 集合中,然后一次性加载所有测试对象。

通过 TestSuite 对象收集

此方式适用于需要自定义组合特定测试用例的情况。

import unittest

class MyTestCase(unittest.TestCase):
    def test_addition(self):
        result = 2 + 3
        self.assertEqual(result, 5)

def suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('test_addition'))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(suite())

通过 TestLoader 对象收集

TestLoaderunittest 框架提供的加载测试用例的类。

import unittest

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    
    # 自动加载当前模块中所有以 'test_' 开头的测试用例函数
    suite = loader.loadTestsFromModule(__name__)

    runner = unittest.TextTestRunner()
    runner.run(suite)

import unittest

class MyTestCase(unittest.TestCase):
    def test_addition(self):
        result = 2 + 3
        self.assertEqual(result, 5)

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    
    # 自动加载 MyTestCase 类中的所有测试用例
    suite = loader.loadTestsFromTestCase(MyTestCase)

    runner = unittest.TextTestRunner()
    runner.run(suite)

import unittest

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    
    # 自动加载指定名称的测试用例
    suite = loader.loadTestsFromName('module.MyTestCase.test_addition')

    runner = unittest.TextTestRunner()
    runner.run(suite)

import unittest

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    
    # 自动发现并加载指定目录中的测试用例模块
    suite = loader.discover(start_dir='test_directory', pattern='test_*.py', top_level_dir=None)

    runner = unittest.TextTestRunner()
    runner.run(suite)

测试运行器

测试运行器是用于执行和输出测试结果的组件。常用的运行器有:

  • unittest.TextTestRunner:这是 unittest 框架中默认的测试运行器,会在命令行输出测试结果。通过调用 run() 方法运行测试套件,并将测试结果打印到控制台。
import unittest

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    suite = loader.discover(start_dir='tests', pattern='test_*.py')
    
    runner = unittest.TextTestRunner()
    result = runner.run(suite)
  • HTMLTestRunner:这是一个第三方库,能够生成漂亮的 HTML 测试报告,需要进行安装。你可以通过搜索获取相关文件进行安装。
import unittest
from HTMLTestRunner import HTMLTestRunner



if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    suite = loader.discover(start_dir='tests', pattern='test_*.py')
    
    with open('test_report.html', 'wb') as report_file:
        runner = HTMLTestRunner(stream=report_file, title='Test Report', description='Test Results')
        result = runner.run(suite)
  • XMLTestRunner:这是另一个第三方库,用于生成 XML 格式的测试报告。
import unittest
from xmlrunner import XMLTestRunner

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    suite = loader.discover(start_dir='tests', pattern='test_*.py')
    
    with open('test_report.xml', 'wb') as report_file:
        runner = XMLTestRunner(output=report_file)
        result = runner.run(suite)

你也可以自定义测试运行器。继承 unittest.TestRunner 类并实现 run() 方法,以创建自己的测试运行器。

import unittest

class MyTestRunner(unittest.TextTestRunner):
    def run(self, test):
        print("Running tests with MyTestRunner")
        result = super().run(test)
        return result

if __name__ == '__main__':
    loader = unittest.defaultTestLoader
    suite = loader.discover(start_dir='tests', pattern='test_*.py')
    
    runner = MyTestRunner()
    result = runner.run(suite)

通常使用 HTMLTestRunner 即可满足需求,它非常易用。

实战一个测试案例

假设有一个测试函数 login

# login.py
def login(username, password):
    """模拟登录校验"""
    if username == 'kira' and password == '123456':
        return {"code": 0, "msg": "登录成功"}
    else:
        return {"code": 1, "msg": "账号或密码不正确"}

设计用例

根据函数的参数和逻辑,设计如下用例:

序号标题测试数据预期结果实际结果
1 账号密码正确 {"username": "kira", "password": "123456"} {"code": 0, "msg": "登录成功"}  
2 账号正确密码不正确 {"username": "kira", "password": "123"} {"code": 1, "msg": "账号或密码不正确"}  
3 账号错误密码正确 {"username": "kir", "password": "123456"} {"code": 1, "msg": "账号或密码不正确"}  

编写测试用例并运行

import unittest
from login import login

class TestLogin(unittest.TestCase):
    def test_login_correct(self):
        """测试账号密码正确"""
        test_data = {"username": "kira", "password": "123456"}
        expect_data = {"code": 0, "msg": "登录成功"}
        res = login(**test_data)
        self.assertEqual(res, expect_data)

    def test_login_wrong_password(self):
        """测试账号正确密码不正确"""
        test_data = {"username": "kira", "password": "123"}
        expect_data = {"code": 1, "msg": "账号或密码不正确"}
        res = login(**test_data)
        self.assertEqual(res, expect_data)

    def test_login_wrong_username(self):
        """测试账号错误密码正确"""
        test_data = {"username": "kir", "password": "123456"}
        expect_data = {"code": 1, "msg": "账号或密码不正确"}
        res = login(**test_data)
        self.assertEqual(res, expect_data)

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

这是一个简单的测试用例,包含了三个测试函数。运行测试用例后,会输出测试结果,看完是否觉得unittest非常简单易用。ner.run(suite)

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

如果你想学习软件测试和需要软件测试资料,欢迎加入扣扣交流群:731789136,里面可以免费领取软件测试+自动化测试资料+软件测试面试宝典+简历模版+实战项目+面试刷题工具和大佬答疑解惑,我们一起交流一起学习!

热门相关:剑道邪尊   帝少夜宠:小甜妻,乖!   至尊医道   至尊医道   亡国公主穿成王府寡妇:二嫁王妃