杰瑞科技汇

unittest python脚本

目录

  1. 核心概念:理解 unittest 的四大核心组件。
  2. 第一个测试脚本:从零开始,创建一个简单的测试。
  3. 断言方法:介绍常用的断言,用于验证代码行为。
  4. 测试夹具setUptearDown,用于管理测试前后的环境。
  5. 测试套件:如何组织和运行多个测试文件。
  6. 高级特性:跳过测试、预期异常等。
  7. 最佳实践:编写可维护测试的建议。

核心概念

unittest 框架基于四个核心概念:

unittest python脚本-图1
(图片来源网络,侵删)
  • Test Case (测试用例):最小的测试单元,它检查你的代码对特定输入是否产生预期的输出,在 unittest 中,我们通过继承 unittest.TestCase 类来创建测试用例。
  • Test Fixture (测试夹具):执行一个或多个测试前需要准备的环境,以及在测试完成后需要清理的环境,这通常通过 setUp()tearDown() 方法来实现。
  • Test Suite (测试套件):一组测试用例、测试套件或它们的组合,你可以将多个测试用例打包在一起,然后一次性运行它们。
  • Test Runner (测试运行器):执行测试套件,并将结果报告给用户,可以是文本、图形界面等,最常用的是 unittest.TextTestRunner

第一个测试脚本

让我们从一个简单的例子开始,假设我们有一个简单的计算器模块 calculator.py,我们要对它进行测试。

步骤 1: 创建被测试的代码 (calculator.py)

# calculator.py
def add(a, b):
    """返回两个数的和"""
    return a + b
def subtract(a, b):
    """返回两个数的差"""
    return a - b
def multiply(a, b):
    """返回两个数的积"""
    return a * b
def divide(a, b):
    """返回两个数的商"""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

步骤 2: 创建测试脚本 (test_calculator.py)

测试脚本需要遵循一定的结构:

  1. 导入 unittest 模块和你的被测试代码。
  2. 创建一个类,继承自 unittest.TestCase
  3. 在类中编写以 test_ 开头的方法,这些方法就是你的测试用例。
  4. 在测试方法中,使用 self.assertEqual(), self.assertTrue() 等断言方法来验证结果。
# test_calculator.py
import unittest
from calculator import add, subtract, multiply, divide
class TestCalculator(unittest.TestCase):
    """测试 Calculator 模块中的所有函数"""
    def test_add(self):
        """测试加法函数"""
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 5), 4)
        self.assertEqual(add(0, 0), 0)
    def test_subtract(self):
        """测试减法函数"""
        self.assertEqual(subtract(5, 3), 2)
        self.assertEqual(subtract(2, 5), -3)
    def test_multiply(self):
        """测试乘法函数"""
        self.assertEqual(multiply(3, 4), 12)
        self.assertEqual(multiply(-2, 5), -10)
    def test_divide(self):
        """测试除法函数"""
        self.assertEqual(divide(10, 2), 5)
        self.assertEqual(divide(9, 2), 4.5)
        # 测试除以零的情况
        with self.assertRaises(ValueError):
            divide(5, 0)
if __name__ == '__main__':
    unittest.main()

代码解释:

  • import unittest: 导入测试框架。
  • from calculator import ...: 导入我们要测试的函数。
  • class TestCalculator(unittest.TestCase):: 定义一个测试类,它必须继承 unittest.TestCase
  • def test_add(self):: 这是一个测试用例,所有测试方法都必须以 test_ 开头,这样测试运行器才能自动发现它们。
  • self.assertEqual(actual, expected): 这是核心断言方法,它会比较 actual(实际结果)和 expected(期望结果),如果两者不相等,测试就会失败。
  • with self.assertRaises(ValueError):: 这是一个特殊的断言,用于验证代码是否按预期抛出了特定的异常。divide(5, 0) 没有抛出 ValueError,测试就会失败。
  • if __name__ == '__main__':: 这行代码确保只有当脚本直接运行时(而不是被其他模块导入时),才会执行测试。
  • unittest.main(): 启动测试运行器,它会自动查找当前文件中以 test_ 开头的方法并执行它们。

步骤 3: 运行测试

在终端中,导航到 test_calculator.py 所在的目录,然后运行脚本:

unittest python脚本-图2
(图片来源网络,侵删)
python test_calculator.py

预期输出:

....
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
  • 代表 4 个测试点都通过了。
  • OK 表示所有测试都成功。

如果某个测试失败,你会看到类似这样的详细输出,指明是哪个测试用例、哪个断言失败了。


常用断言方法

unittest.TestCase 类提供了许多断言方法,用于验证不同条件:

断言方法 描述
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, 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)
assertRaises(exception, callable, *args, **kwargs) 验证 callable 调用是否抛出 exception
assertAlmostEqual(a, b, places=7) round(a-b, places) == 0 (用于浮点数比较)
assertNotAlmostEqual(a, b, places=7) round(a-b, places) != 0

测试夹具

如果你的多个测试用例需要共享一些准备和清理工作,可以使用 setUp()tearDown()

unittest python脚本-图3
(图片来源网络,侵删)
  • setUp(): 在每个测试用例运行之前被调用,用于创建测试所需的对象、建立数据库连接等。
  • tearDown(): 在每个测试用例运行之后被调用,用于关闭文件、断开数据库连接、删除临时文件等。

示例

# test_example_with_fixture.py
import unittest
class TestStringMethods(unittest.TestCase):
    def setUp(self):
        """在每个测试前运行"""
        print("\n[+] Setting up for a new test...")
        self.sample_list = [1, 2, 3]
    def tearDown(self):
        """在每个测试后运行"""
        print("[-] Tearing down after the test.")
        # 通常在这里清理资源
    def test_append(self):
        """测试列表的 append 方法"""
        self.sample_list.append(4)
        self.assertEqual(self.sample_list, [1, 2, 3, 4])
    def test_pop(self):
        """测试列表的 pop 方法"""
        self.sample_list.pop()
        self.assertEqual(self.sample_list, [1, 2])
if __name__ == '__main__':
    unittest.main()

运行这个脚本,你会看到 setUptearDown 的打印信息在每次测试前后被调用。


测试套件

当你的项目变大,会有很多测试文件。unittest.TestSuite 可以让你将它们组合起来一起运行。

假设我们有两个测试文件:test_calculator.pytest_string_methods.py

创建一个主测试运行器 (run_all_tests.py)

# run_all_tests.py
import unittest
from test_calculator import TestCalculator
from test_string_methods import TestStringMethods # 假设这个文件存在
if __name__ == '__main__':
    # 1. 创建一个测试套件
    suite = unittest.TestSuite()
    # 2. 将测试用例添加到套件中
    # 方法一:添加一个测试类中的所有测试
    suite.addTest(unittest.makeSuite(TestCalculator))
    suite.addTest(unittest.makeSuite(TestStringMethods))
    # 方法二:添加特定的测试方法
    # suite.addTest(TestCalculator('test_add'))
    # suite.addTest(TestStringMethods('test_append'))
    # 3. 创建一个测试运行器
    runner = unittest.TextTestRunner(verbosity=2) # verbosity=2 提供更详细的输出
    # 4. 运行测试套件
    print("Running all tests...")
    runner.run(suite)

你只需要运行 python run_all_tests.py 就能执行所有测试。


高级特性

跳过测试

有时你可能想跳过某个测试,因为它还不稳定、依赖于外部环境或者与当前版本不相关。

import unittest
import sys
class MyTests(unittest.TestCase):
    @unittest.skip("暂时跳过这个测试")
    def test_skipped(self):
        self.fail("这个测试不应该被执行")
    @unittest.skipIf(sys.version_info < (3, 6), "需要 Python 3.6 或更高版本")
    def test_skipped_on_old_python(self):
        self.assertEqual("hello".format(), "hello")
    @unittest.skipUnless(sys.platform == "win32", "只在 Windows 上运行")
    def test_windows_only(self):
        self.assertTrue(True)
if __name__ == '__main__':
    unittest.main()

预期失败

你知道一个测试会失败,但你希望它被标记为“预期失败”,而不是“错误”。

import unittest
class MyTests(unittest.TestCase):
    @unittest.expectedFailure
    def test_expected_to_fail(self):
        self.assertEqual(1 + 1, 3) # 这个测试肯定会失败

最佳实践

  1. 命名清晰

    • 测试文件名:test_ 开头,如 test_user_model.py
    • 测试类名:Test 开头,描述被测试的类或模块,如 TestUserModel
    • 测试方法名:test_ 开头,清晰描述测试场景,如 test_user_can_be_createdtest_user_login_with_invalid_password
  2. 一个测试,一个断言:理想情况下,一个测试方法只验证一件事,这使得定位失败原因更容易。

  3. 测试独立的:测试之间不应该相互依赖,每个测试都应该能独立运行,并产生可预测的结果,这就是 setUptearDown 的意义所在。

  4. 测试边界条件和异常:不仅要测试“正常情况”,还要测试空值、零、最大/最小值、非法输入等边界条件。

  5. 使用 Mock:当你的代码依赖于外部系统(如数据库、网络API、文件系统)时,使用 unittest.mock 模块来创建这些依赖的“模拟”对象,这能让你的测试运行得更快、更可靠,并且不依赖外部环境,这是一个更高级的话题,但对于大型项目至关重要。

unittest 是 Python 测试的基石,掌握了它,你就为编写健壮、可靠的 Python 代码打下了坚实的基础,随着项目复杂度的增加,你还可以探索 pytest 等更现代、更灵活的测试框架,但 unittest 的原理和许多概念是相通的。

分享:
扫描分享到社交APP
上一篇
下一篇