自动化测试框架 - Unittest 学习笔记速查

5 篇文章 0 订阅
1 篇文章 0 订阅


UnitTest 简介

在Python的标准库中,unittest框架占据了举足轻重的地位,它是开发者们进行单元测试时不可或缺的利器。
集结了丰富的断言方法与全面的测试工具集,从测试固件、测试套件到测试加载器,无一不为编写清晰、可维护的测试用例提供了强大的后盾。

通过unittest,开发者们可以构建出一套坚实可靠的测试体系,确保代码的正确性和可靠性。
其断言方法的多样性和灵活性,使得测试用例的编写变得游刃有余;
其全面的测试工具集则帮助开发者们更高效地管理、执行和分析测试。

不仅如此,unittest还能与各种第三方工具无缝对接,为开发者们提供更丰富的测试报告和覆盖度分析。
在unittest的支持下,软件开发的整体效率得到了显著提升,代码的质量和稳定性也得到了有力保障。


UnitTest 核心

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

UnitTest 原理

1.设置测试环境(TestFixture)

  • 通过setUp()方法设置测试用例执行前需要的环境或条件;
  • 如初始化对象、打开文件、建立数据库连接等。
  • 通过tearDown()方法设置测试用例执行后需要的环境清理;
  • 如关闭文件、断开数据库连接、删除数据库记录等。

2.编写测试用例(TestCase)

  • 定义继承自unittest.TestCase的类,并在其中编写具体的测试方法;
  • 每个测试方法通常以test开头,并且不接受任何参数。

3.收集测试用例(TestSuite)

  • 使用TestLoader来加载和收集测试用例,形成一个TestSuite
  • TestSuite是一个测试用例的集合,可以包含多个TestCase实例。

4.执行测试用例(TestRunner)

  • TestRunner负责执行TestSuite中的测试用例;
  • 它运行每个测试用例,并收集测试结果。

5.生成测试报告

  • TestRunner根据测试结果生成报告,通常包括测试通过的用例数、测试失败的用例数、错误信息等内容。

使用TestLoader无需再使用TestSuite,直接收集用例后去送给TestRunner去执行

在这里插入图片描述


UnitTest 断言函数

断言方法断言描述
assertEqual(arg1, arg2, msg=None)验证arg1=arg2,不等则fail
assertNotEqual(arg1, arg2, msg=None)验证arg1 != arg2, 相等则fail
assertTrue(expr, msg=None)验证expr是true,如果为false,则fail
assertFalse(expr,msg=None)验证expr是false,如果为true,则fail
assertIs(arg1, arg2, msg=None)验证arg1、arg2是同一个对象,不是则fail
assertIsNot(arg1, arg2, msg=None)验证arg1、arg2不是同一个对象,是则fail
assertIsNone(expr, msg=None)验证expr是None,不是则fail
assertIsNotNone(expr, msg=None)验证expr不是None,是则fail
assertIn(arg1, arg2, msg=None)验证arg1是arg2的子串,不是则fail
assertNotIn(arg1, arg2, msg=None)验证arg1不是arg2的子串,是则fail
assertIsInstance(obj, cls, msg=None)验证obj是cls的实例,不是则fail
assertNotIsInstance(obj, cls, msg=None)验证obj不是cls的实例,是则fail
assertRaises(expr)验证expr异常类型,结合with使用例子:with self.assertRaises(Exception):  # 测试内容

TestCase(用例)

unittest框架中,TestCase 是一个基础的抽象类,用于定义单个的测试用例。

每个TestCase实例代表一个具体的测试,它会检查某个特定的功能或行为是否符合预期。

使用规则:

  1. 测试用例必须在
  2. 测试用例所在的类,必须继承自unittest.TestCase
  3. 测试用例的方法名称,必须以test开头
  4. 测试用例的方法参数,通常除了self外不接受其他参数
  5. 测试用例的执行顺序是按照ASCILL编码表的排列(0-9,A-Z,a-z…)

基本用法
# 第一步:导入unittest模块
import unittest


# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):

    # 第三步:定义测试函数,函数名以test_开头。测试用例
    def test_1(self):
        print("这是第一条测试案例...")

    def test_2(self):
        print("这是第二条测试案例...")

    def test_3(self):
        print("这是第三条测试案例...")

    def test_4(self):
        print("这是第四条测试案例...")


# 第四步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
    unittest.main()


执行结果

在这里插入图片描述


TestFixture(夹具)


方法级夹具
# 第一步:导入unittest模块
import unittest


# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):

    # 第三步:重写父类的setUp和tearDown方法
    # 这是每个test case执行前的初始化
    def setUp(self):
        print("setUp方法执行了...")

    # 这是每个test case执行后的测试回收
    def tearDown(self):
        print("tearDown方法执行了...")

    # 第四步:定义测试函数,函数名以test_开头。测试用例
    def test_1(self):
        print("这是第一条测试案例...")

    def test_2(self):
        print("这是第二条测试案例...")

    def test_3(self):
        print("这是第三条测试案例...")

    def test_4(self):
        print("这是第四条测试案例...")


# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
    unittest.main()

在这里插入图片描述


类级夹具
# 第一步:导入unittest模块
import unittest


# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo(unittest.TestCase):

    # 第三步:重写父类的setUp和tearDown方法

    # 这是TestCases执行前的初始化
    @classmethod
    def setUpClass(cls):
        print("测试类 Begin...")

    # 这是TestCases执行后的回收操作
    @classmethod
    def tearDownClass(cls):
        print("测试类 End...")

    # 这是每个test case执行前的初始化
    def setUp(self):
        print("setUp方法执行了...")

    # 这是每个test case执行后的测试回收
    def tearDown(self):
        print("tearDown方法执行了...")

    # 第四步:定义测试函数,函数名以test_开头。测试用例
    def test_1(self):
        print("这是第一条测试案例...")

    def test_2(self):
        print("这是第二条测试案例...")

    def test_3(self):
        print("这是第三条测试案例...")

    def test_4(self):
        print("这是第四条测试案例...")


# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
    unittest.main()

在这里插入图片描述


模块级夹具
# 第一步:导入unittest模块
import unittest


# 模块级别的需要写在 类的外边 直接 定义函数 即可

def setUpModule():
    print("模块前置:", __name__)


def tearDownModule():
    print("模块后置:", __name__)


# 第二步: 创建一个测试类,被继承unittest.TestCase
class unittest_demo1(unittest.TestCase):

    # 第三步:重写父类的setUp和tearDown方法

    # 这是TestCases执行前的初始化
    @classmethod
    def setUpClass(cls):
        print("测试类 Begin...")

    # 这是TestCases执行后的回收操作
    @classmethod
    def tearDownClass(cls):
        print("测试类 End...")

    # 这是每个test case执行前的初始化
    def setUp(self):
        print("setUp方法执行了...")

    # 这是每个test case执行后的测试回收
    def tearDown(self):
        print("tearDown方法执行了...")

    # 第四步:定义测试函数,函数名以test_开头。测试用例
    def test_1(self):
        print("这是第一条测试案例...")

    def test_2(self):
        print("这是第二条测试案例...")

    def test_3(self):
        print("这是第三条测试案例...")

    def test_4(self):
        print("这是第四条测试案例...")


# 将unittest_demo1拷贝一份
class unittest_demo2(unittest.TestCase):

    # 第三步:重写父类的setUp和tearDown方法

    # 这是TestCases执行前的初始化
    @classmethod
    def setUpClass(cls):
        print("测试类 Begin...")

    # 这是TestCases执行后的回收操作
    @classmethod
    def tearDownClass(cls):
        print("测试类 End...")

    # 这是每个test case执行前的初始化
    def setUp(self):
        print("setUp方法执行了...")

    # 这是每个test case执行后的测试回收
    def tearDown(self):
        print("tearDown方法执行了...")

    # 第四步:定义测试函数,函数名以test_开头。测试用例
    def test_1(self):
        print("这是第一条测试案例...")

    def test_2(self):
        print("这是第二条测试案例...")

    def test_3(self):
        print("这是第三条测试案例...")

    def test_4(self):
        print("这是第四条测试案例...")


# 第五步:调用unittset.main()方法运行测试用例
if __name__ == 'main':
    unittest.main()

在这里插入图片描述


TestSuite(套件)

unittest框架中,测试套件(Test Suite)是一个重要的概念;

测试套件允许将多个测试用例、测试套件或测试函数组合到一起,然后一次性运行它们。

用途:

  • 将多个测试用例组合在一起,形成一个逻辑上的测试组。
  • 分层组织测试,例如先运行快速的基础测试,再运行更复杂的集成测试。
  • 动态地添加或移除测试用例。
  • 重复运行同一组测试。

优点:

  • **组织性:**测试套件允许以结构化的方式组织测试用例,使得测试代码更加清晰和易于维护。
  • **灵活性:**可以根据需要动态地添加、删除或修改测试套件中的测试用例。
  • **复用性:**测试套件可以重复使用,例如在不同的测试环境或不同的测试阶段中。
  • **批量执行:**通过测试套件,可以一次性执行多个测试用例,而无需单独运行每个测试用例。

函数:

  • **addTest(test):**将一个测试用例(unittest.TestCase的实例)或测试套件(TestSuite的实例)添加到测试套件中。
  • **addTests(tests):**将多个测试用例或测试套件添加到测试套件中。tests参数通常是一个测试用例或测试套件的列表或元组。
  • **countTestCases():**返回测试套件中包含的测试用例数量。
  • **run(result):**运行测试套件中的所有测试用例,并将结果存储在result参数中(通常是一个unittest.TestResult的实例)。
import unittest


# 定义第一个测试用例类
class TestClass1(unittest.TestCase):
    def test_1_01(self):
        print("执行 test_1_01 测试用例...")
        # 使用断言函数,判断两个参数相等
        self.assertEqual(1 + 1, 2)

    def test_1_02(self):
        print("执行 test_1_02 测试用例...")
        # 使用断言函数,判断一个参数是不是True
        self.assertTrue(True)


# 定义第二个测试用例类
class TestClass2(unittest.TestCase):
    def test_2_01(self):
        print("执行 test_2_01 测试用例...")
        # 使用断言函数,判断参数1是不是参数2的子串
        self.assertIn("hello", "hello world")

    def test_2_02(self):
        print("执行 test_2_02 测试用例...")
        # 使用断言函数,判断参数是不是None
        self.assertIsNone(None)


if __name__ == '__main__':
    # 创建测试套件
    suite = unittest.TestSuite()

    # 将第一个测试用例类的所有测试方法添加到测试套件中
    suite.addTest(unittest.makeSuite(TestClass1))

    # 将第二个测试用例类的一个特定测试方法添加到测试套件中
    suite.addTest(TestClass2("test_2_02"))

    # 运行测试套件
    runner = unittest.TextTestRunner()
    runner.run(suite)

在这里插入图片描述


TestLoader(加载器)

unittest框架中的TestLoader是一个测试用例加载器;
TestLoader的主要任务是发现和加载测试用例,以便之后执行。

TestLoader提供了几个方法用于加载测试用例,并可以根据不同的需求选择使用。


loadTestsFromTestCase(testCaseClass)

用于加载一个特定的测试用例类中的所有测试方法
并将它们作为TestSuite返回

import unittest

class MyTestCase(unittest.TestCase):
    def test_method1(self):
        self.assertEqual(1 + 1, 2)
    
    def test_method2(self):
        self.assertEqual(2 * 2, 4)

# 创建TestLoader实例
loader = unittest.TestLoader()

# 使用loadTestsFromTestCase加载测试用例类中的所有测试方法
suite = loader.loadTestsFromTestCase(MyTestCase)

# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)

loadTestsFromModule(module, pattern=None)

用于加载一个模块中所有符合指定模式的测试用例。
如果不提供pattern,则加载模块中的所有测试用例

import unittest

# 假设有一个名为my_module的模块,其中包含测试用例
# my_module.py
class TestMyModule(unittest.TestCase):
    def test_something(self):
        self.assertEqual(1, 1)

# 在主脚本中加载模块中的测试用例
loader = unittest.TestLoader()

# 加载my_module模块中的所有测试用例
suite = loader.loadTestsFromModule(my_module)

# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)

loadTestsFromNames(names, module=None)

根据提供的名称列表加载测试用例;

这些名称可以是测试用例类名、模块名或可调用对象(例如函数)的名称。

import unittest

# 假设有两个测试用例类
class TestA(unittest.TestCase):
    def test_a(self):
        self.assertEqual(1, 1)

class TestB(unittest.TestCase):
    def test_b(self):
        self.assertEqual(2, 2)

# 创建TestLoader实例
loader = unittest.TestLoader()

# 使用loadTestsFromNames加载特定的测试用例类
suite = loader.loadTestsFromNames(['__main__.TestA', '__main__.TestB'])

# 运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)

suiteClass

这是一个类属性,它指定了当TestLoader创建新的TestSuite实例时所使用的类。

通常不需要直接设置这个属性,除非你想要自定义TestSuite的行为。

class CustomSuite(unittest.suite.TestSuite):
    # 自定义TestSuite的行为
    pass

class CustomTestLoader(unittest.TestLoader):
    suiteClass = CustomSuite  # 指定使用自定义的TestSuite类

# 使用自定义的TestLoader加载测试用例
loader = CustomTestLoader()
suite = loader.loadTestsFromTestCase(MyTestCase)

sortTestMethodsUsing(method)

这个方法允许指定一个排序函数;
用于对TestCase类中的测试方法进行排序。

def sort_test_methods(a, b):
    # 自定义排序函数
    return a[3:] < b[3:]  # 按方法名字母顺序排序,忽略'test_'前缀

loader = unittest.TestLoader()
loader.sortTestMethodsUsing(sort_test_methods)

suite = loader.loadTestsFromTestCase(MyTestCase)

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

getTestCaseNames(testCaseClass)

这个方法返回一个列表,其中包含testCaseClass中所有以test开头的方法的名称。

这个方法通常不直接调用,而是由TestLoader在内部使用来确定哪些方法应该作为测试用例加载。

loader = unittest.TestLoader()

Discover (加载器)

discover()是unittest框架中的一个功能强大的函数,用于自动发现和运行测试。
该函数会根据给定的开始目录(起始路径),递归地查找该目录及其子目录下的所有测试模块,并加载它们执行测试。

discover() 的用途:

  • 自动发现测试模块:在大型项目中,测试可能分布在多个文件或目录中。discover() 函数可以自动找到这些测试模块,而无需手动导入每个模块。
  • 灵活性和可扩展性:通过指定不同的参数,如模式匹配、顶层目录等,discover() 可以非常灵活地控制哪些测试应该被执行。

discover()的参数:

  • **start_dir:**要搜索测试模块的起始目录
  • **pattern:**匹配测试模块名的模式(默认为 test*.py
  • **top_level_dir:**用于确定要运行的测试的顶层目录(默认为 None,表示使用 start_dir 作为顶层目录)
  • **verbosity:**输出详细信息的级别(默认为 1,表示输出简要结果)
  • **buffer:**控制输出的缓冲(默认为 False
  • **suiteClass:**用于创建测试套件的类(默认为 unittest.TestSuite
  • **loaderClass:**用于加载测试模块的类(默认为 unittest.TestLoader

示例代码

假设有以下目录结构:

project_root/

├── tests/
│ ├── test_module1.py
│ └── test_module2.py

└── main.py

test_module1.pytest_module2.py中分别定义了测试用例。

可以使用以下代码来运行所有测试:

import unittest

# 使用 discover() 函数自动发现并运行测试
unittest.discover(start_dir='./tests', pattern='test*.py')

# 这将运行 tests 目录下所有以 test 开头且扩展名为 .py 的文件中的测试

TextTestRunner(运行器)

TextTestRunner是unittest框架中的一个类,它提供了一个文本模式的测试执行器,用于运行测试用例并输出结果;

TextTestRunner 可以配置以显示详细的测试输出,包括测试进度、测试成功或失败的信息等。

TextTestRunner 的构造函数接受几个参数来定制其行为:

  • **stream:**一个输出流对象,用于写入测试结果。默认为sys.stdout,表示标准输出。
  • **descriptions:**一个布尔值,指定是否打印每个测试用例的描述。默认为 True
  • **verbosity:**一个整数,指定测试输出的详细程度。0表示最少输出,1表示正常输出,2表示详细输出。默认为1。
  • **failfast:**一个布尔值,如果为True,则一旦有测试失败,就立即停止运行后面的测试。默认为False
  • **buffer:**一个布尔值,如果为True,则输出会被缓冲,直到测试运行结束。默认为False
  • **resultclass:**一个TestResult类的子类,用于处理测试结果。默认为 unittest.TextTestResult

基本用法

TextTestRunner 的主要方法是 run(test)
它接受一个TestSuiteTestCase实例作为参数;
运行测试并返回测试结果。

import unittest

# 定义测试用例类
class MyTestCase(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(1 + 1, 2)

    def test_subtraction(self):
        self.assertEqual(2 - 1, 1)

# 创建测试套件
suite = unittest.TestSuite()
suite.addTest(MyTestCase('test_addition'))
suite.addTest(MyTestCase('test_subtraction'))

# 创建TextTestRunner实例
runner = unittest.TextTestRunner()

# 运行测试套件并输出结果
result = runner.run(suite)

# 输出测试结果
print("Test result:", result.wasSuccessful())
自定义输出格式

可以通过修改TextTestRunnerverbosity参数来定制输出信息的详细程度。

runner = unittest.TextTestRunner(verbosity=2)
# verbosity=0: 最简洁的输出
# verbosity=1: 标准输出
# verbosity=2: 详细输出

使用缓冲区

buffer参数可以决定输出是否缓冲;
如果设置为True,则所有输出都会被缓冲,直到测试运行完成

runner = unittest.TextTestRunner(buffer=True)

自定义结果类

resultclass 参数允许你使用自定义的结果类来处理测试结果

class CustomResult(unittest.TextTestResult):
    # 在这里可以添加自定义逻辑
    pass

runner = unittest.TextTestRunner(resultclass=CustomResult)

跳过特定测试用例

使用 @unittest.skip(reason) 强制跳过测试用例,并提供一个跳过的原因

import unittest

class MyTestCases(unittest.TestCase):
    @unittest.skip("这个测试用例暂时不被需要或尚未完成")
    def test_skipped_due_to_reason(self):
        self.assertTrue(False, "这个测试不会执行,因为它被跳过了")

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

使用@unittest.skipIf(condition, reason)条件成立时跳过测试用例,并提供一个跳过的原因

import unittest
import sys

class MyTestCases(unittest.TestCase):
    @unittest.skipIf(sys.version_info < (3, 6), "这个测试需要Python 3.6或更高版本")
    def test_python_version(self):
        self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")

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

使用@unittest.skipUnless(condition, reason)条件不成立时跳过测试用例,并提供一个跳过的原因

import unittest

class MyTestCases(unittest.TestCase):
    @unittest.skipUnless(False, "这个测试被故意设置为总是跳过")
    def test_always_skipped(self):
        self.assertTrue(True, "这个测试不会执行,因为它被跳过了")

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

使用@unittest.expectedFailure标记一个测试用例预期失败

import unittest

class MyTestCases(unittest.TestCase):
    @unittest.expectedFailure
    def test_expected_to_fail(self):
        self.assertTrue(False, "这个测试预期会失败,因为它尚未完成或存在已知问题")

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

使用self.skipTest()函数,跳过当前正在执行的测试用例

这通常在测试方法的内部逻辑中基于某些条件决定跳过测试时使用。

import unittest

class MyTestCases(unittest.TestCase):
    def test_skipping_in_method(self):
        # 基于某种条件决定跳过测试
        if not some_external_condition:  # 假设 some_external_condition 是一个外部条件或变量
            self.skipTest("由于某些外部条件,跳过此测试")
        self.assertTrue(True, "这个测试执行了")

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

示例代码
import unittest
import sys


class SkippingTestCases(unittest.TestCase):

    # 使用 @unittest.skip 强制跳过整个测试方法
    @unittest.skip("这个测试方法:test_skip_by_decoration 被强制跳过了")
    def test_skip_by_decoration(self):
        self.fail("这个测试方法被跳过了")

    # 使用条件判断在测试方法内部跳过
    def test_skip_in_method_by_condition(self):
        if sys.version_info < (3, 6):
            self.skipTest("这个测试需要Python 3.6或更高版本")
        self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")

    # 使用 @unittest.skipIf 根据条件在装饰时跳过
    @unittest.skipIf(sys.version_info < (3, 6), "这个测试需要Python 3.6或更高版本")
    def test_skip_if_condition_met(self):
        self.assertTrue(True, "这个测试在Python 3.6或更高版本下执行")

    # 使用 @unittest.skipUnless 根据条件在装饰时跳过,当条件不满足时跳过
    @unittest.skipUnless(False, "这个测试方法:test_skip_unless_condition_not_met 被故意设置为总是跳过")
    def test_skip_unless_condition_not_met(self):
        self.fail("这个测试不会执行,因为它被跳过了")

    # 使用 @unittest.expectedFailure 标记预期会失败的测试
    @unittest.expectedFailure
    def test_expected_to_fail(self):
        self.assertTrue(True, "这个测试预期会失败,因为它尚未完成或存在已知问题")


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

执行结果

在这里插入图片描述


BeautifulReport - 运行器扩展


安装

BeautifulReport 属于第三方库,需要额外下载安装,命令如下:

pip install BeautifulReport

示例代码
import unittest

from BeautifulReport import BeautifulReport


# 定义一个测试类
class MathTests(unittest.TestCase):

    # 测试加法
    def test_addition(self):
        self.assertEqual(1 + 1, 3)

    # 测试减法
    def test_subtraction(self):
        self.assertEqual(2 - 1, 1)

    # 测试乘法
    def test_multiplication(self):
        self.assertEqual(2 * 2, 5)

    # 测试除法
    def test_division(self):
        self.assertEqual(4 / 2, 2)

    # 测试除法,预期会失败
    def test_division_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            1 / 0


# 定义另一个测试类
class StringTests(unittest.TestCase):

    # 测试字符串连接
    def test_string_concatenation(self):
        self.assertEqual("Hello, " + "World", "Hello, World")

    # 测试字符串相等
    def test_string_equality(self):
        self.assertEqual("Hello", "Hello")

    # 测试字符串不相等
    def test_string_inequality(self):
        self.assertNotEqual("Hello", "Goodbye")


# 如果这个脚本被直接运行,则执行测试
if __name__ == '__main__':
    # 创建测试套件,添加测试类
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(MathTests))
    suite.addTest(unittest.makeSuite(StringTests))

    # 利用BeautifulReport生成测试报告
    result = BeautifulReport(suite)
    result.report(filename='这是测试报告的文件名称',
                  description='这是测试报告的描述',
                  log_path="./test_reports/log.log",
                  report_dir="./test_reports")


执行结果

在这里插入图片描述


unittestreport - 运行器扩展


安装

unittestreport 属于第三方库,需要额外下载安装,命令如下:

pip install unittestreport

示例代码
import unittest

from unittestreport import TestRunner


# 定义一个测试类
class MathTests(unittest.TestCase):

    # 测试加法
    def test_addition(self):
        self.assertEqual(1 + 1, 3)

    # 测试减法
    def test_subtraction(self):
        self.assertEqual(2 - 1, 1)

    # 测试乘法
    def test_multiplication(self):
        self.assertEqual(2 * 2, 5)

    # 测试除法
    def test_division(self):
        self.assertEqual(4 / 2, 2)

    # 测试除法,预期会失败
    def test_division_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            1 / 0


# 定义另一个测试类
class StringTests(unittest.TestCase):

    # 测试字符串连接
    def test_string_concatenation(self):
        self.assertEqual("Hello, " + "World", "Hello, World")

    # 测试字符串相等
    def test_string_equality(self):
        self.assertEqual("Hello", "Hello")

    # 测试字符串不相等
    def test_string_inequality(self):
        self.assertNotEqual("Hello", "Goodbye")


# 如果这个脚本被直接运行,则执行测试
if __name__ == '__main__':
    # 创建测试套件,添加测试类
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(MathTests))
    suite.addTest(unittest.makeSuite(StringTests))

    # 利用unittestreport生成测试报告
    runner = TestRunner(suite,
                        filename='report.html',
                        report_dir="./test_reports",
                        title='加减乘除和字符串案例的测试报告',
                        tester="我是测试开发的烦恼",
                        desc='测试加减乘除、字符串等案例')
    runner.run()


执行结果

在这里插入图片描述


DDT - 参数化扩展


什么是DDT(数据驱动)
  • DDT又叫数据驱动(Data-Driven Design)
  • 通常用于将测试数据与函数代码进行分离,存储某个位置的文件中;在自动化测试中数据驱动框架会读取文件中的数据,然后把数据作为参数传递到功能函数中,并会根据数据的条目数量多次运行同一个功能函数;
  • 这样做的好处就是提高代码的复用性、可维护性;
  • 数据驱动的数据文件可以是:txt、csv、Excel、json、yaml等文件

为什么要使用数据驱动

数据与代码分离

  • 通过将测试数据与函数代码分离,可以使测试代码更加清晰和易于维护。
  • 测试数据通常存储在外部文件中,如txt、csv、Excel、json、yaml等,这样可以在不修改代码的情况下更改测试数据。

提高代码复用性

  • 使用数据驱动框架,可以多次运行同一个功能函数,每次使用不同的测试数据。> - 这样,相同的测试逻辑可以被重复使用,提高了代码的复用性。

提高可维护性

  • 当测试数据发生变化时,只需要更新存储测试数据的文件,而不需要修改测试代码。
  • 这降低了维护成本,并提高了测试脚本的可维护性。

灵活性

  • 数据驱动测试允许测试人员快速添加、删除或修改测试数据,以适应不同的测试场景和需求。

易于扩展

  • 由于测试数据与代码是分离的,因此可以更容易地扩展测试范围,包括增加新的测试数据或添加新的测试场景。

安装

DDT 属于第三方库,需要额外下载安装,命令如下:

pip install ddt

示例代码

传递一个参数

# 导入unittest框架、ddt驱动模块、data数据传递模块
import unittest
from ddt import ddt
from ddt import data

# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
    # 使用@data装饰器将声明的数据传递给测试用例方法中的形参
    @data('18900001234','15800001234','13900001234')
    # phone(1个形参):接收data传递来的数据
    # 因为只有一个形参传递进来的数据都是给phone使用的
    def test_01_phone(self,phone):
        print('手机号:',phone)

传递多个参数

# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack


# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
    # 使用@data装饰器将声明的数据传递给测试用例方法中的形参
    @data(['admin','123456','78oa'],['guest','111111','a3b6'])
    # username,password,code(3个形参):接收data传递来的数据
    # 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
    # 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
    # 使用@unpack将传递进来的参数进行拆解,分别传递形参
    @unpack
    def test_02_login(self,username,password,code):
        print('账号:',username,'密码:',password,'验证码:',code)

使用ddt读取txt文件内容传递

# phone.txt内容
----------------------------------
15800001111
15600002222
18900003333
13900004444
18011115555
13500006666

# userdata.txt内容
----------------------------------
admin,123456,s7c8
guest,111111,08hg
visitor,000000,kl0s

# ddt_demo.py内容
----------------------------------
# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack

"""
with 语句实质是上下文管理。
1、上下文管理协议。包含方法__enter__() 和 __exit__(),支持该协议对象要实现这两个方法。
2、上下文管理器,定义执行with语句时要建立的运行时上下文,负责执行with语句块上下文中的进入与退出操作。
3、进入上下文的时候执行__enter__方法,如果设置as var语句,var变量接受__enter__()方法返回值。
4、如果运行时发生了异常,就退出上下文管理器。调用管理器__exit__方法。
"""

'''
    读取phone.txt文件获取数据作为参数
    1、声明phone列表用来存储读取并处理后的数据
    2、使用with机制以utf-8编码格式用只读的模式open文件
    3、循环遍历文件中的所有行,将每一行去除空白符添加到列表中
    4、返回phone列表
'''

def read_phone():
    phone = []
    with open(file='phone.txt',mode='r',encoding='utf-8') as f:
        for lina in f.readlines():
            phone.append(lina.strip())
    return phone

'''
    读取userdata.txt文件获取数据作为参数
    1、声明user_info列表用来存储读取并处理后的数据
    2、使用with机制以utf-8编码格式用只读的模式open文件
    3、循环遍历文件中的所有行,将每一行去除空白符后以','分割成列表
    4、将处理后的行添加到user_info列表并返回
'''
def read_userdata():
    user_info = []
    with open(file='userdata.txt',mode='r',encoding='utf-8') as u:
        for rows in u.readlines():
            rows_str = str(rows.strip()).rsplit(',')
            user_info.append(rows_str)
    return user_info


# 声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
    # 使用data装饰器,调用read_phone()函数用其返回值传递给测试用例的形参
    # 因为文件中含有多条数据,需要使用动态参数传递
    # 否则就会被看成一条数据,只会执行一条用例
    @data(*read_phone())
    # phone(1个形参):接收data传递来的数据
    def test_01_phone(self,phone):
        print('手机号:',phone)

    # 使用data装饰器,调用read_userdata()函数用其返回值传递给测试用例的形参
    # 因为文件中含有多条数据,需要使用动态参数传递
    # 否则就会被看成一条数据,只会执行一条用例
    @data(*read_userdata())
    # username,password,code(3个形参):接收data传递来的数据
    # 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
    # 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
    # 使用@unpack将传递进来的参数进行拆解,分别传递形参
    @unpack
    def test_02_login(self,username,password,code):
        print('账号:',username,'密码:',password,'验证码:',code)

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


使用ddt读取json数据文件内容传递

# phone.txt内容
----------------------------------
[
  "15800001111",
  "15600002222",
  "18900003333"
]

# userdata.txt内容
----------------------------------
[
  {
    "username":"admin",
    "password":"123456",
    "code":"s7c8"
  },

  ["guest","111111","08hg"]
]

# ddt_demo.py内容
----------------------------------
# 导入unittest框架、ddt驱动模块、data数据传递模块、unpack拆分传递的数据包模块、json模块
import unittest
from ddt import ddt
from ddt import data
from ddt import unpack
import json

'''
    使用json类对象调用load加载函数从phone.json、userdata.json数据文件加载数据
'''
def read_phone():
    return json.load(open(file='phone.json',mode='r',encoding='utf-8'))

def read_userdata():
    return json.load(open(file='userdata.json',mode='r',encoding='utf-8'))


#  声明TestMyCase()类使用ddt数据驱动
@ddt
class TestMyCase(unittest.TestCase):
    # 使用data装饰器,调用read_phone()函数用其返回值传递给测试用例的形参
    # 因为文件中含有多条数据,需要使用动态参数传递
    # 否则就会被看成一条数据,只会执行一条用例
    @data(*read_phone())
    # phone(1个形参):接收data传递来的数据
    def test_01_phone(self,phone):
        print('手机号:',phone)

    # 使用data装饰器,调用read_userdata()函数用其返回值传递给测试用例的形参
    # 因为文件中含有多条数据,需要使用动态参数传递
    # 否则就会被看成一条数据,只会执行一条用例
    @data(*read_userdata())
    # username,password,code(3个形参):接收data传递来的数据
    # 因为存在多个形参,解释器并不知道将哪个数据传递给哪个形参,所以需要先将数据拆解
    # 将data中的第一个数据包拆解开来,分别传递给形参,再接着依次拆解数据包分配给对应的形参
    # 使用@unpack将传递进来的参数进行拆解,分别传递形参
    @unpack
    def test_02_login(self,username,password,code):
        print('账号:',username,'密码:',password,'验证码:',code)

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


parameterized - 参数化扩展

parameterized是python的第三方库,用于为unittest测试提供参数化支持。

它允许为测试用例提供多个参数集,以便多次运行相同的测试逻辑,但每次使用不同的输入参数。


安装

parameterized 属于第三方库,需要额外下载安装,命令如下:

pip install parameterized

示例代码
from parameterized import parameterized
import unittest

class TestParameterized(unittest.TestCase):

    # 单个参数
    @parameterized.expand([
        ("foo",),
        ("bar",),
    ])
    def test_with_single_parameter(self, s):
        self.assertIn(s, ["foo", "bar", "baz"])

    # 多个参数
    @parameterized.expand([
        (1, 2, 3),
        (4, 5, 9),
    ])
    def test_with_multiple_parameters(self, a, b, expected_sum):
        self.assertEqual(a + b, expected_sum)

    # 列表参数
    @parameterized.expand([
        ([1, 2, 3],),
        ([4, 5, 6],),
    ])
    def test_with_list_parameter(self, lst):
        self.assertEqual(sum(lst), sum([i for i in lst]))

    # 字典参数
    @parameterized.expand([
        ({"a": 1, "b": 2, "expected": 3},),
        ({"a": -1, "b": 1, "expected": 0},),
    ])
    def test_with_dict_parameter(self, kwargs):
        self.assertEqual(kwargs["a"] + kwargs["b"], kwargs["expected"])

if __name__ == '__main__':
    unittest.main()
  • 79
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值