不拼花哨,只拼实用: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() 方法运行测试用例,按照函数名的排序执行测试。

以下是一个简单的例子:

python 复制代码
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核心概念

测试脚手架

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

python 复制代码
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_ 开头,则被标识为测试函数:

ruby 复制代码
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() 使用和上述类似。

python 复制代码
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 对象收集

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

scss 复制代码
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 框架提供的加载测试用例的类。

ini 复制代码
import unittest

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

    runner = unittest.TextTestRunner()
    runner.run(suite)
ini 复制代码
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)
ini 复制代码
import unittest

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

    runner = unittest.TextTestRunner()
    runner.run(suite)
ini 复制代码
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() 方法运行测试套件,并将测试结果打印到控制台。
ini 复制代码
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 测试报告,需要进行安装。你可以通过搜索获取相关文件进行安装。
ini 复制代码
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 格式的测试报告。
ini 复制代码
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() 方法,以创建自己的测试运行器。

ini 复制代码
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

python 复制代码
# 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": "账号或密码不正确"}

编写测试用例并运行

python 复制代码
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)

总结

以上就是勇哥今天为各位小伙伴准备的内容,如果你想了解更多关于Python自动化测试的知识和技巧,欢迎关注我:公众号\博客\CSDN\B站:测试玩家勇哥;我会不定期地分享更多的精彩内容。感谢你的阅读和支持!


题外话,勇哥打算把新建的技术交流群,打造成一个活跃的高质量技术群。工作中遇到的技术问题,都可以在里面咨询大家,还有工作内推的机会。有兴趣的小伙伴,欢迎加我(记得备注是进群还是报名学习)

勇哥,10年落魄测试老司机,技术栈偏python,目前在一家超大型房产公司担任自动化测试主管,日常工作比较繁杂,主要负责自动化测试,性能测试、软件质量管理及人员管理。工作之余专注于为粉丝进行简历修改、面试辅导、模拟面试、资料分享、一对一自动化测试教学辅导等副业发展。目前已服务十多位小伙伴,取得高薪offer。

关注公众号,测试干货及时送达

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 接口自动化测试项目2.0,让你像Postman一样编写测试用例,支持多环境切换、多业务依赖、数据库断言等 |
| 揭秘抓包利器:Python和Mitmproxy让您轻松实现接口请求抓取与分析! |
| 构建高效的接口自动化测试框架思路 |
| Pytest 快速入门 |
| 接口自动化之测试数据动态生成并替换 |
| requests模块该如何封装? |
| 接口自动化如何封装mysql操作 |
| 一文看懂python如何执行cmd命令 |
| 最通俗易懂python操作数据库 |
| python-Threading多线程之线程锁 |
| python正则一篇搞掂 |
| 性能测试之必备知识 |
| 性能分析思 |
| Python + ChatGPT来实现一个智能对话的钉钉机器人 |
| 一文看懂python如何执行cmd命令 |
[往期精选文章:]

相关推荐
这个男人是小帅9 分钟前
【GAT】 代码详解 (1) 运行方法【pytorch】可运行版本
人工智能·pytorch·python·深度学习·分类
小白学大数据3 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
Shy9604184 小时前
Doc2Vec句子向量
python·语言模型
秀儿还能再秀7 小时前
机器学习——简单线性回归、逻辑回归
笔记·python·学习·机器学习
阿_旭8 小时前
如何使用OpenCV和Python进行相机校准
python·opencv·相机校准·畸变校准
幸运的星竹8 小时前
使用pytest+openpyxl做接口自动化遇到的问题
python·自动化·pytest
kali-Myon9 小时前
ctfshow-web入门-SSTI(web361-web368)上
前端·python·学习·安全·web安全·web
B站计算机毕业设计超人9 小时前
计算机毕业设计Python+大模型农产品价格预测 ARIMA自回归模型 农产品可视化 农产品爬虫 机器学习 深度学习 大数据毕业设计 Django Flask
大数据·爬虫·python·深度学习·机器学习·课程设计·数据可视化