unittest

hkt 发布于 2025-08-19 24 次阅读


unittest原名为PyUnit,是由java的JUnit衍生而来。unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件。它受到 Java 的 JUnit 框架的启发,提供了丰富的断言方法、测试套件(Test Suite)、测试夹具(Fixture)等功能,适合对代码进行自动化测试。

Unittest的5个概念

  • test case:测试用例,一个完整的测试单元,执行该测试单元可以完成对某一个问题的验证
  • test suite:测试套件,多个测试用例的集合,测试套件或测试计划;
  • TestLoader:批量执行测试用例,加载test case到test suite中的
  • test fixure:测试夹具,一个测试用例的初始化准备及环境还原,主要是setUp() 和 setDown()方法;
  • test runner: 用来执行测试用例和测试套件,并返回测试用例的执行结果

Unittest的使用

TestCase 测试用例

在设计用例的时候,会继承 unittest 当中的 TestCase 的类和方法,可以用来创建新的测试用例,一个TestCase的实例就是一个测试用例,unittest中的测试用例都是以 “test” 开头,并且它的执行顺序是按照方法名的ASCII值进行排序。

注意:在创建TestCase时,如果有构造方法会报错,无法创建构造方法,但是可以创建析构方法。用例方法是根据ascii码的顺序执行的,pytest的用例是谁在前谁先执行。unittest只有方法用例,测试类必须集成TestCase类

举例:

import unittest


class Test_Case_01(unitest.TestCase):      # 新建测试类必须继承unittest.TestCase

  # 测试方法名称必须以 test 开头
  def test_01(self):  
    print("测试用例名称为:test_01")

  def test_02(self):
    print("测试用例名称为:test_02")


if __name__ == '__main__':
    # 执行测试用例
    unittest.main()

TestSuite测试套件

一个功能的验证往往需要多个测试用例,可以把多个测试用例集合在一起执行,这就产生了TestSuite的概念。我们可以把它理解成一个篮子,篮子里面放了很多东西(test case)。使用addTest来加载TestCase到TestSuite中。

使用:
实例化:suite = unittest.TestSuite()
添加用例:suite.addTest(ClassName(“MethodName”)) #(ClassName:类名,MethodName:方法名)
举例:

import unittest
from test_001 import Test_Case_01

# 实例化测试套件对象
suite = unittest.TestSuite()
# 单独添加测试用例
suite.addTest(Test_Case_01("test_01"))
suite.addTest(Test_Case_01("test_02"))

# 执行
runner = unittest.TextTestRunner()
runner.run(suite)

TestRunner执行测试用例和测试套件

用来执行测试用例和测试套件,并返回测试用例的执行结果。在unittest单元测试框架中,通过TextTestRunner类提供的run()方法来执行test suite/test case。

TextTestRunner使用:
实例化:runner = unittest.TextTestRunner()
执行:runner.run(suite) # suite为测试套件的名称,可以参考上文的代码

TestLoader加载TestCase

用来加载TestCase到TestSuite中,即加载满足条件的测试用例,并把测试用例封装成测试套件。使用unittest.TestLoader(),通过该类下面的discover()方法自动搜索指定目录下指定开头的 .py 文件,并将查到的测试用例组装到测试套件。

举例:

import os
import unittest

suit = unittest.TestSuite()
loader = unittest.TestLoader()  # 专门用来查找用例的实例
# test_case_path: 测试用例的所在路径。pattern:搜索以test开头的py文件,默认为‘test*.py’
suit.addTests(loader.discover(test_case_path, pattern="test*.py"))

runner = unittest.TestRunner(。。。)
runner.run(suit)

TestSuite和TestLoader的区别

共同点:都是测试套件
不同点:实现方式不同
(1) TestSuite需要手动添加测试用例(可以添加测试类,也可以添加测试类中的某个测试方法)
(2) TestLoader搜索指定目录下指定开头的py文件,并添加测试类中的所有测试方法,不能指定添加某个测试方法

unittest夹具(固件)

虽然unittestPython标准库中的测试框架,不像Pytest那样有显式的@pytest.fixture装饰器,但它通过setUp/tearDown方法setUpClass/tearDownClass等机制实现了类似夹具的功能。以下是unittest中夹具的用法和原理:

unittest的夹具机制

方法作用执行时机
setUp()在每个测试方法(test_*运行,用于初始化资源(类似 pytest 的 function 作用域夹具)。每个测试方法前调用
tearDown()在每个测试方法运行,用于清理资源。每个测试方法后调用
setUpClass()在整个测试类运行(需用 @classmethod 装饰),类似 pytest 的 class 作用域夹具。类中第一个测试方法前调用一次
tearDownClass()在整个测试类运行(需用 @classmethod 装饰),用于类级清理。类中最后一个测试方法后调用一次
setUpModule()在整个测试模块(文件)运行(模块级全局函数),类似 pytest 的 module 作用域。模块的第一个测试类前调用一次
tearDownModule()在整个测试模块运行(模块级全局函数),用于模块级清理。模块的最后一个测试类后调用一次

执行顺序

举例:

import time
import unittest
from selenium import webdriver

class Test_Tmall_Login(unittest.TestCase):

    def setUp(self) -> None:
        # 获取Chrome浏览器驱动对象
        self.driver = webdriver.Chrome()
        # 打开url
        self.driver.get("http://182.44.27.72:8090/korei/login.jsp")
        # 窗口最大化
        self.driver.maximize_window()
        # 隐式等待
        self.driver.implicitly_wait(30)

    def tearDown(self) -> None:
        # 等待3秒,关闭浏览器
        time.sleep(3)
        self.driver.quit()

    def test_login(self):
        driver = self.driver
        # 输入用户名
        driver.find_element(By.NAME, "username").send_keys("admin")
        # 输入密码
        driver.find_element(By.NAME, "password").send_keys("1")
        # 点击登录按钮
        driver.find_element(By.XPATH, '//*[text()="Log in"]').click()

unittest装饰器

Python 的 Unittest 模块提供了一些内置装饰器,用于控制测试行为(如跳过测试、预期失败等)。以下是主要装饰器及其用法:

测试跳过装饰器

@unittest.skip(reason):无条件跳过当前测试。

import unittest

class TestDemo(unittest.TestCase):
    @unittest.skip("功能暂未实现")
    def test_old_feature(self):
        self.fail("不应执行")

    def test_new_feature(self):
        self.assertEqual(1 + 1, 2)

@unittest.skipIf(condition, reason):当条件为 True 时跳过。

import sys

class TestSkipIf(unittest.TestCase):
    @unittest.skipIf(sys.version_info < (3, 8), "需要 Python 3.8+")
    def test_python3_8_feature(self):
        self.assertTrue(True)

@unittest.skipUnless(condition, reason):当条件为 False 时跳过。

import os

class TestSkipUnless(unittest.TestCase):
    @unittest.skipUnless(os.getenv("RUN_INTEGRATION_TESTS"), "需设置环境变量")
    def test_integration(self):
        self.assertTrue(True)

预期失败装饰器

@unittest.expectedFailure:标记测试为“预期失败”,如果测试通过反而报错。

class TestExpectedFailure(unittest.TestCase):
    @unittest.expectedFailure
    def test_buggy_code(self):
        self.assertEqual(1, 2)  # 预期失败,不报错

    @unittest.expectedFailure
    def test_fixed_code(self):
        self.assertEqual(1, 1)  # 意外通过,触发异常

. 表示通过 或者 pass
S 表示跳过
x 表示忽略错误
F False, 表示断言没有通过
E Error, 表示程序内部发生了错误

参数化测试装饰器

unittest 本身不支持原生参数化,但可通过以下方式实现

@parameterized.expand(可迭代类型数据)

from parameterized import parameterized

class TestMath(unittest.TestCase):
    @parameterized.expand([
        ("positive", 1, 1, 2),
        ("negative", -1, -1, -2),
    ])
    def test_add(self, name, a, b, expected):
        self.assertEqual(a + b, expected)

输出

test_add_0_positive (__main__.TestMath) ... ok
test_add_1_negative (__main__.TestMath) ... ok
  • 使用用例的形参接收可迭代类型的元素
  • 如果可迭代类型数据不是二维使用一个形参接收
  • 如果可迭代类型数据是二维的,必须使用多个形参接收数据,形参个数为容器的元素个数

@ddt.data(使用 ddt 库)

import ddt

@ddt.ddt
class TestDDT(unittest.TestCase):
    @ddt.data((1, 1), (2, 3))
    @ddt.unpack
    def test_less_than(self, a, b):
        self.assertLess(a, b)

其他实用装饰器

@mock.patch(模拟对象,来自 unittest.mock)

from unittest.mock import patch

class TestMock(unittest.TestCase):
    @patch("os.getcwd")  # 模拟 os.getcwd()
    def test_mock(self, mock_getcwd):
        mock_getcwd.return_value = "/fake/path"
        self.assertEqual(os.getcwd(), "/fake/path")

Unittest断言

unittest提供了一系列断言方法(Assertions),用于验证测试结果是否符合预期。

断言例子说明
assertEqual(a, b)a==b值是否相等
assertNotEqual(a, b)a!=b值是否不相等
assertIs(a,b)a is b值是否相同
aassertIsNot(a,b)a is not b值是否不同
assertIn(a,b)a in ba是否包含b
assertNotIn(a,b)a not in ba是否不包含b
assertTrue(a)bool(a)is true 是否为真
assertFalse(a)bool(a)is false 是否为假
assertIsNone(a)a is None是否为空
assertIsNotNone(a)a is None是否不为空
assertIsInstance(a,b)Instance(a,b)a与b的数据类型一样
assertNotIsInstance(a)not Instance(a,b)a与b的数据类型不一样

举例:

class Testdemo(unittest.TestCase)
  def test_method3(self):
      print("用例方法3")
      self.assertIn(1,[1,2,3]) 
  def test_method1(self):
      print("用例方法1")
      assert 1==1 ,"描述信息"

输出报告

使用HTMLTestRunner

pip install html-testRunner

import unittest

from unittestreport import TestRunner

suite = unittest.TestSuite()

loder = unittest.TestLoader()

suite.addTests(loder.discover(r'用例目录', pattern='用例.py'))

#runner1 = TestRunner(suite)
runner1 = TestRunner(
        suite=suite,  #指明运行的测试套
        filename="report.html",  #确定生成报告的名称
        report_dir="./reports"  #报告存放的路径
        title="测试报告"  
        tester="测试员"
        desc="xxx项目测试生成的报告"
        templates=1
)

runner1.run()

使用BeautifulReport

pip install BeautifulReport

import unittest
from BeautifulReport import BeautifulReport

class TestMath(unittest.TestCase):
    def test_add(self):
        self.assertEqual(1 + 1, 2)

if __name__ == "__main__":
    suite = unittest.TestLoader().loadTestsFromTestCase(TestMath)
    result = BeautifulReport(suite)
    result.report(
        description="单元测试报告",
        filename="unittest_report.html",
        log_path="reports"
    )
此作者没有提供个人介绍。
最后更新于 2025-08-20