unittest框架学习

unittest工作原理

1.unittest的作用

unittest是python内置的单元测试框架,主要用于单元测试,可以编写用例、组织用例、执行用例、输出报告等作用

2.unnitest的重要概念
unittest 是 Python 的一个内置测试框架,用于编写和运行单元测试。它提供了一组用于构建测试的模块和类,并且可以轻松地执行测试并生成测试报告。下面是对 unittest 框架的详细解释:
1.测试用例(Test Case):
unittest 中的测试用例是指继承自 unittest.TestCase 类的测试类,其中包含了一系列测试方法。每个测试方法都应该测试系统中的一个特定功能或组件。
2.测试套件(Test Suite):
测试套件是一组测试用例的集合,可用于按照特定的顺序执行多个测试。unittest 提供了 TestSuite 类来创建测试套件。
3.断言(Assertion):
在测试方法中使用断言来验证预期的行为是否符合实际结果。unittest 提供了多种断言方法,如 assertEqual、assertTrue、assertFalse 等,用于检查预期结果是否成立。
4.测试加载与执行:
unittest 可以通过命令行或测试运行器执行测试。通过调用 unittest.main() 函数可以执行测试用例,也可以使用测试运行器来执行测试并生成测试报告。
5.测试装置(Test Fixture):
unittest 提供了 setUp 和 tearDown 方法,用于在测试用例执行前后进行准备和清理工作。这些方法可用于初始化资源、创建测试环境、清理临时文件等操作。
6.测试运行器(Test Runner):
是用于执行测试的工具,它可以加载测试用例、执行测试代码,并生成测试结果报告。在测试框架中,测试运行器是一个关键的组成部分,负责协调测试的执行过程并提供测试结果的反馈。
用于执行测试用例。run(test)会执行TestSuite/TestCase中的run(result)方法。测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
7.测试报告:
unittest 可以生成测试报告,报告包括测试用例的执行结果、成功与失败的测试数量、耗时等信息。同时也支持 XML 格式的输出,以便于与其他工具进行集成。
7.扩展性:
unittest 框架支持测试用例的参数化、测试套件的嵌套组合、测试装置的定制化等高级特性,以满足不同测试场景的需求。
3.unnitest的工作原理
(1)编写好测试用例TestCase
(2)然后TestLoader加载TestCase到TestSuite
(3)由TextTestRunner来运行TestSuit
(4)运行结果保存在TextTestResult中
(5)整个过程集成在unittest.main模块中
注意:TestCase可以是多个、TestSuite也可以是多个

TestCase(测试用例)

  • TestCase类
    unittest.TestCase是所有测试用例的基础类,提供了测试用例的基本结构。当定义一个测试类时,需要继承该unnitest.TestCase
    (1)初始化
def __init__(self, methodName='runTest'):
    super(TestCase, self).__init__(methodName)
    # TestResult was originally a protected attribute

构造函数可以接收一个methodName参数,它指定了要执行的测试方法的名称。如果没有提供方法名称,默认为runTest(不过,在大多数情况下,会让框架自动查找以 test_ 开头的方法来运行,而不需要显式地指定 methodName。)
(2)断言方法
unittest.TestCase提供了一系列断言方法,用于验证测试结果是否符合预期结果

def assertEqual(self, first, second, msg=None):
    """Fail if the two objects are unequal as determined by the '=='
       operator.
    """
    if not first == second:
        standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second))
        self.fail(self._formatMessage(msg, standardMsg))

fail方法用于立即失败当前测试,并可以附带一个错误消息

def fail(self, msg=None):
    """Fail immediately, with the given message."""
    raise self.failureException(msg)

(3)测试方法的执行顺序
①setUp():方法在每个测试方法之前执行,用于初始化测试环境
②tearDown():方法在每个测试方法之后执行,用于清理测试环境
③测试方法:

  • 这些方法按照定义的顺序执行
  • 控制执行顺序:
    1.使用数字前缀:
    在测试方法名前加上数字前缀,如 test_01_method, test_02_method 等。
    这种方法利用 Python 的字符串排序机制来控制执行顺序。
    2.使用装饰器:
    可以自定义装饰器来控制测试方法的执行顺序。
    这种方法需要你自己实现装饰器逻辑,并应用到测试方法上。
    (4)运行测试
def run(self, result=None):
    orig_result = result
    if result is None:
        result = self.defaultTestResult()
        startTestRun = getattr(result, 'startTestRun', None)
        if startTestRun is not None:
            startTestRun()

    result.startTest(self)

    testMethod = getattr(self, self._testMethodName)
    if (getattr(self.__class__, "__unittest_skip__", False) or
            getattr(testMethod, "__unittest_skip__", False)):
        # If the class or method was skipped.
        try:
            skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                        or getattr(testMethod, '__unittest_skip_why__', ''))
            self._addSkip(result, skip_why)
        finally:
            result.stopTest(self)
    else:
        try:
            self._outcome = outcome = _WritelnDecorator(result)
            try:
                self._feedErrorsToResult(result, self._invoke_test_method())
            except KeyboardInterrupt:
                raise
            except:
                outcome.errors.append((self, sys.exc_info()))
                outcome.shouldStop = True
            finally:
                self.doCleanups()
                self._feedErrorsToResult(result, outcome.errors)
                del self._outcome
        finally:
            stopTestRun = getattr(result, 'stopTestRun', None)
            if stopTestRun is not None:
                stopTestRun()
    return result

run()方法是测试用例的入口点,run 方法的主要作用是执行测试用例,并处理测试过程中可能出现的各种情况,包括跳过、异常捕获、清理等。它负责调用测试方法、收集测试结果,并确保测试前后环境的正确设置和清理。通过这一系列的操作,run 方法确保了测试用例能够按预期执行,并且结果能够被正确记录和报告。
(5)测试用例

  1. 方法命名:测试方法必须以test_开头,这样的命名的方法会被unittest框架识别为测试用例
  2. 方法执行顺序:测试方法按照它们在类中定义的顺序执行。如果需要改变执行顺序,可以在方法名前加上数字前缀,如 test_01_method, test_02_method 等。

TestLoader(测试加载)

unittest.TestLoader是unittest框架中一个重要的组成部分,用于加载测试用例并构建测试套件。和TestSuite功能一样。

  1. 加载测试用例
    loadTestsFromTestCase
def loadTestsFromTestCase(self, testCaseClass):
        """Return a suite of all test cases contained in testCaseClass"""
        if issubclass(testCaseClass, suite.TestSuite):
            raise TypeError("Test cases should not be derived from "
                            "TestSuite. Maybe you meant to derive from "
                            "TestCase?")
        testCaseNames = self.getTestCaseNames(testCaseClass)
        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
            testCaseNames = ['runTest']
        loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
        return loaded_suite

该方法用于从给定的测试类加载所有的测试用例,并将他们添加到测试套件中返回(通过getTestCaseNames方法获取测试方法的名称)
getTestsCaseNames

def getTestCaseNames(self, testCaseClass):
        """Return a sorted sequence of method names found within testCaseClass
        """
        def shouldIncludeMethod(attrname):
            if not attrname.startswith(self.testMethodPrefix):
                return False
            testFunc = getattr(testCaseClass, attrname)
            if not callable(testFunc):
                return False
            fullName = f'%s.%s.%s' % (
                testCaseClass.__module__, testCaseClass.__qualname__, attrname
            )
            return self.testNamePatterns is None or \
                any(fnmatchcase(fullName, pattern) for pattern in self.testNamePatterns)
        testFnNames = list(filter(shouldIncludeMethod, dir(testCaseClass)))
        if self.sortTestMethodsUsing:
            testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
        return testFnNames

该方法用于获取测试类中的测试方法名称,判断测试用例名称是否是以test开头(testMethodPrefix = “test”),返回方法名称列表
discover:

def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
        set_implicit_top = False
        if top_level_dir is None and self._top_level_dir is not None:
            # make top_level_dir optional if called from load_tests in a package
            top_level_dir = self._top_level_dir
        elif top_level_dir is None:
            set_implicit_top = True
            top_level_dir = start_dir

        top_level_dir = os.path.abspath(top_level_dir)

        if not top_level_dir in sys.path:
            # all test modules must be importable from the top level directory
            # should we *unconditionally* put the start directory in first
            # in sys.path to minimise likelihood of conflicts between installed
            # modules and development versions?
            sys.path.insert(0, top_level_dir)
        self._top_level_dir = top_level_dir

        is_not_importable = False
        is_namespace = False
        tests = []
        if os.path.isdir(os.path.abspath(start_dir)):
            start_dir = os.path.abspath(start_dir)
            if start_dir != top_level_dir:
                is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py'))
        else:
            # support for discovery from dotted module names
            try:
                __import__(start_dir)
            except ImportError:
                is_not_importable = True
            else:
                the_module = sys.modules[start_dir]
                top_part = start_dir.split('.')[0]
                try:
                    start_dir = os.path.abspath(
                       os.path.dirname((the_module.__file__)))
                except AttributeError:
                    # look for namespace packages
                    try:
                        spec = the_module.__spec__
                    except AttributeError:
                        spec = None

                    if spec and spec.loader is None:
                        if spec.submodule_search_locations is not None:
                            is_namespace = True

                            for path in the_module.__path__:
                                if (not set_implicit_top and
                                    not path.startswith(top_level_dir)):
                                    continue
                                self._top_level_dir = \
                                    (path.split(the_module.__name__
                                         .replace(".", os.path.sep))[0])
                                tests.extend(self._find_tests(path,
                                                              pattern,
                                                              namespace=True))
                    elif the_module.__name__ in sys.builtin_module_names:
                        # builtin module
                        raise TypeError('Can not use builtin modules '
                                        'as dotted module names') from None
                    else:
                        raise TypeError(
                            'don\'t know how to discover from {!r}'
                            .format(the_module)) from None

                if set_implicit_top:
                    if not is_namespace:
                        self._top_level_dir = \
                           self._get_directory_containing_module(top_part)
                        sys.path.remove(top_level_dir)
                    else:
                        sys.path.remove(top_level_dir)

        if is_not_importable:
            raise ImportError('Start directory is not importable: %r' % start_dir)

        if not is_namespace:
            tests = list(self._find_tests(start_dir, pattern))
        return self.suiteClass(tests)

该方法用于指定的目录自动发现并加载所有符合特定模式的测试用例,从 start_dir 开始递归搜索所有符合 pattern 的测试文件,对于找到的每个测试文件,最终返回一个包含所有找到的测试用例的测试套件。
参数说明

  • start_dir:指定搜索的起始目录
  • pattern:匹配测试文件的模式,默认是“test*.py”
  • top_level_dir:指定顶级目录,用于确定相对导入路径

TestSuite(测试套件)

unittest.TestSuite是一个用于组织和执行一组测试用例的容器。继承于BaseTestSuite类。

  • 初始化:
def __init__(self, tests=()):
        self._tests = []
        self._removed_tests = 0
        self.addTests(tests)

初始化可接受一个可选参数列表tests,如果没有提供默认创建一个空测试套件。

  • 添加测试用例:
def addTest(self, test):
    # ...
    if isinstance(test, collections.abc.Iterable) and \
       not isinstance(test, (str, bytes, bytearray)):
        for subtest in test:
            self.addTest(subtest)
    else:
        self._tests.append(test)
 - addTest、addTests方法用于向测试套件中添加测试用例
 - 如果传入的test是一个可迭代对象,则递归地添加

TestSuite和TestLoader的区别:
1.unittest.TestSuite 用于组织和管理测试用例,但不负责加载测试用例。
2.unittest.TestLoader 负责从不同来源加载测试用例,并返回一个测试套件。
通常情况下,会先使用 unittest.TestLoader 加载测试用例,然后使用 unittest.TestSuite 组织这些测试用例,并最终通过 unittest.TextTestRunner 或其他测试运行器执行测试套件。
(1)共同点:测试套件
(2)不同点:实现方式不同
TestSuite: 要么添加指定的测试类中所有test开头的方法,要么添加指定测试类中指定某个test开头的方法
TestLoader: 搜索指定目录下指定字母开头的模块文件中以test字母开头的方法并将这些方法添加到测试套件中,最后返回测试套件

TestRunner(测试执行)

TestRunner是unittest框架中的一个测试运行器,用于执行测试套件并将结果以文本形式输出。

  • 初始化
def __init__(self, stream=None, descriptions=True, verbosity=1,
                 failfast=False, buffer=False, resultclass=None, warnings=None,
                 *, tb_locals=False):
        """Construct a TextTestRunner.

        Subclasses should accept **kwargs to ensure compatibility as the
        interface changes.
        """
        if stream is None:
            stream = sys.stderr
        self.stream = _WritelnDecorator(stream)
        self.descriptions = descriptions
        self.verbosity = verbosity
        self.failfast = failfast
        self.buffer = buffer
        self.tb_locals = tb_locals
        self.warnings = warnings
        if resultclass is not None:
            self.resultclass = resultclass

参数:
(1)stream: 输出流,默认为 sys.stderr,如果要指定结果输出流,可以为stream赋值
(2)descriptions: 是否显示测试用例的描述,默认为 True。

Fixture(测试夹具)

fixture是一种机制,用于设置和清理测试环境,确保每个测试用例在一个干净的环境中运行。
1.方法级别
(1)setUp():setUp是在每个测试用例前调用一次
(2)tearDown():tearDown方法是在每个测试用例执行后被调用
2.类级别
(1)setUpClass方法在整个测试类开始时被调用一次
(2)tearDown方法在整个测试类结束后被调用一次
注意:类级别的方法需要用@classmethod修饰

断言

断言方法是用于验证测试结果是否符合预期的关键工具。

基本断言

基本断言方法通常用于简单的比较和验证。以下是一些常用的基本断言方法:

  • assertEqual(a, b): 断言 a 和 b 相等。
  • assertTrue(x): 断言 x 是 True。
  • assertFalse(x): 断言 x 是 False。
  • assertIs(a, b): 断言 a 和 b 是同一个对象。
  • assertIsNone(x): 断言 x 是 None。
  • assertIn(a, b): 断言 a 在 b 中。
  • assertNotIn(a, b): 断言 a 不在 b 中。

复杂断言

复杂断言方法用于更高级的比较和验证。以下是一些常用的复杂断言方法:

  • assertAlmostEqual(a, b[, places]): 断言 a 和 b 在指定的小数位数内相等。
  • assertNotAlmostEqual(a, b[, places]): 断言 a 和 b 不在指定的小数位数内相等。
  • assertListEqual(a, b): 断言两个列表相等。
  • assertTupleEqual(a, b): 断言两个元组相等。
  • assertSetEqual(a, b): 断言两个集合相等。
  • assertDictEqual(a, b): 断言两个字典相等。
  • assertCountEqual(a, b): 断言两个序列包含相同的元素,但不要求顺序相同。
  • assertLess(a, b): 断言 a 小于 b。
  • assertGreater(a, b): 断言 a 大于 b。
  • assertLessEqual(a, b): 断言 a 小于等于 b。
  • assertGreaterEqual(a, b): 断言 a 大于等于 b。
  • assertIsInstance(a, b): 断言 a 是类型 b 的实例。
  • assertNotIsInstance(a, b): 断言 a 不是类型 b 的实例。

异常处理

经常需要验证函数是否会抛出预期的异常。unittest 提供了专门的方法来处理这种情况:

  • assertRaises(expected_exception, callable, *args, **kwargs): 断言 callable(*args, **kwargs) 会抛出 expected_exception 异常。
  • assertRaisesRegex(expected_exception, expected_regex, callable, *args, **kwargs): 断言 callable(*args, **kwargs) 会抛出 expected_exception 异常,并且异常的消息匹配正则表达式 expected_regex。
  • assertWarns(expected_warning, callable, *args, **kwargs): 断言 callable(*args, **kwargs) 会发出 expected_warning 警告。
  • assertWarnsRegex(expected_warning, expected_regex, callable, *args, **kwargs): 断言 callable(*args, **kwargs) 会发出 expected_warning 警告,并且警告的消息匹配正则表达式 expected_regex。

参数化

unitest本身是不支持参数化的,可以通过第三方插件来实现,parameterized库可以为同一个测试方法提供不同的输入参数,测试不同的场景。

  1. 使用parameterized.expand进行参数化
    parameterized.expand是一个装饰器,用于将不同的参数传递给测试方法.
  2. 使用的基本步骤
    (1)导入必要的库
      导入parameterized库
    (2)定义测试方法
      使用@parameterized.expend装饰器来定义测试方法
      装饰器的参数是一个列表,其中每个元素都是一组参数
    (3)编写测试逻辑
      在测试方法内部编写测试逻辑
      根据传入的参数进行相应的测试
    (4)实例
import unittest
from parameterized import parameterized

def add(a, b):
    return a + b

class TestAddition(unittest.TestCase):
    @parameterized.expand([
        (1, 2, 3),
        (10, 5, 15),
        (-1, -1, -2)
    ])
    def test_addition(self, x, y, expected):
        result = add(x, y)
        self.assertEqual(result, expected)

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

parameterized.expand接受一个列表,其中每个元素都是一个元组,包含了测试用例的参数
2. 如果希望整个测试类的多个测试方法可以共享一组参数,可以使用parameterized_class装饰器。

import unittest
from parameterized import parameterized_class

@parameterized_class(('x', 'y'), [
    (1, 2),
    (10, 5),
    (-1, -1)
])
class TestAddition(unittest.TestCase):
    def setUp(self):
        super().setUp()
        self.expected_result = self.x + self.y

    def test_addition(self):
        result = add(self.x, self.y)
        self.assertEqual(result, self.expected_result)

    def test_subtraction(self):
        result = subtract(self.x, self.y)
        self.assertEqual(result, self.x - self.y)

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

parameterized_class接受一个元组列表,其中每个元素都是一个元组,包含了测试类的参数

数据驱动

数据驱动测试(Data-Driven Testing, DDT)是一种测试方法,它允许测试用例根据不同的输入数据运行多次。这种方法使得测试更加灵活,能够更容易地处理多种测试场景,同时减少代码的重复性。

  1. 安装必要的库
pip install ddt
  1. 使用@ddt装饰类,标记该类中的方法将使用数据驱动
  2. 使用@data指定方法的数据集
    @data传入一个包含测试数据的列表或元组,其中元素可以是单个值或元组、列表,具体取决于参数需求。多个参数进行传参时@data里面要用列表形式
  3. @unpack用于解包数据集中的元素(解包元组或列表),使其成为测试方法的参数
import unittest
import ddt

# 假设我们有一个简单的加法函数
def add(a, b):
    return a + b

@ddt.ddt
class TestAddFunction(unittest.TestCase):

    # 使用@data装饰器定义测试数据
    @ddt.data(
        (1, 2, 3),  # 元组形式的数据点
        (10, 5, 15),
        (-1, -1, -2)
    )
    @ddt.unpack  # 使用@unpack解包数据元组
    def test_add(self, a, b, expected):
        """ 测试add函数 """
        result = add(a, b)
        self.assertEqual(result, expected)

if __name__ == '__main__':
    unittest.main()
  1. 数据驱动:代码和数据分离
    数据和代码分离的好处:当需要添加或修改测试数据,无需改动测试逻辑代码,相同的测试逻辑可以应用于不同的数据集上,减少重复编写相似测试脚本的工作量
    (1)使用外部文件存储数据
    数据可以存储在CSV文件、Excel表格、XML或者json文件等外部文件中,这样可以在不改变测试代码的情况下更新数据。
    ①使用CSV文件
    CSV文件是一种常用的文本格式,用于存储表格数据。
#test_data.csv
username,password,expected_result
user1,pass1,True
user1,wrongpass,False
user2,pass2,True
nonexistent,any,False
from ddt import ddt, data, unpack
import os
import csv

import unittest

# 模拟登陆函数
def login_function(username, password):
    vaild_data = {
        'user1': 'pass1',
        'user2': 'pass2',
        'user3': 'pass3'
    }
    return vaild_data.get(username) == password

# 加载测试数据
def load_data(file_path):
    data = []
    with open(file_path, 'r') as csvfile:
        reader = csv.reader(csvfile,delimiter=',')
        print(reader)
        next(reader)
        for row in reader:
            data.append(row)
    return data

@ddt
class TestLogin(TestCase):
    
    @data(*load_data(r'E:\workspace\day\log26\test_data.csv'))  #星号的作用是将load_test_data函数返回的数据解包成单独的参数,然后传递给@unittest.data装饰器
    @unpack
    def test_login(self, username, password, expected):
       """测试登陆功能"""
       result = login_function(username, password)
       self.assertEqual(str(result), expected)

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

*小tips:CSV文件的读写操作:
1.读取CSV文件

import csv
# 打开CSV文件
with open('example.csv', newline='', encoding='utf-8') as csvfile:
    # 创建CSV读取器
    csvreader = csv.reader(csvfile)
    
    # 逐行读取CSV文件
    for row in csvreader:
        print(row)1)打开文件:使用open函数以只读模式打开文件
(2)创建CSV读取器:使用csv.reader创建CSV读取器
(3)读取数据:通过迭代CSV读取器对象,可以逐行读取CSV文件内容。每一行都会被解析为一个列表

2.写入CSV文件

import csv

# 打开CSV文件以写入模式
with open('output.csv', mode='w', newline='', encoding='utf-8') as csvfile:
    # 创建CSV写入器
    writer = csv.writer(csvfile, delimiter=',',lineterminator='\n')
    
    # 写入表头
    csvwriter.writerow(['Name', 'Age', 'City'])
    
    # 写入多行数据
    csvwriter.writerows([
        ['Alice', 30, 'New York'],
        ['Bob', 25, 'San Francisco'],
        ['Charlie', 35, 'Los Angeles']
    ])
创建CSV写入器:
使用 csv.writer() 创建CSV写入器对象。
写入数据:
使用 writerow() 方法写入一行数据。
使用 writerows() 方法写入多行数据。

3.使用DictReader和DictWriter
如果你的CSV文件包含列名(通常称为表头),并且希望按名称访问列,可以使用 DictReader 和 DictWriter。

import csv

# 读取CSV文件
with open('example.csv', newline='', encoding='utf-8') as csvfile:
    # 创建CSV字典读取器
    csvreader = csv.DictReader(csvfile)
    
    # 逐行读取CSV文件
    for row in csvreader:
        print(row['Name'], row['Age'])

# 写入CSV文件
with open('output.csv', mode='w', newline='', encoding='utf-8') as csvfile:
    # 定义字段名
    fieldnames = ['Name', 'Age', 'City']
    
    # 创建CSV字典写入器
    csvwriter = csv.DictWriter(csvfile, fieldnames=fieldnames)
    
    # 写入表头
    csvwriter.writeheader()
    
    # 写入多行数据
    csvwriter.writerows([
        {'Name': 'Alice', 'Age': 30, 'City': 'New York'},
        {'Name': 'Bob', 'Age': 25, 'City': 'San Francisco'},
        {'Name': 'Charlie', 'Age': 35, 'City': 'Los Angeles'}
    ])
使用DictReader:
csv.DictReader() 用于创建一个字典读取器对象,它会根据表头自动创建字典。
每一行都会被解析为一个字典,其中键是表头的字段名,值是对应的字段值。
使用DictWriter:
csv.DictWriter() 用于创建一个字典写入器对象。
使用 writeheader() 方法来写入表头。
使用 writerows() 方法来写入多行数据

②使用Excel文件
Excel 文件是一种常用的表格数据存储格式,可以使用 Python 的 openpyxl 库来读取 Excel 文件中的数据。
1.安装openyxl

pip install openpyxl

xlsx

| username | password | expected_result |
|----------|----------|-----------------|
| user1    | pass1    | True            |
| user1    | wrongpass| False           |
| user2    | pass2    | True            |
| nonexistent| any     | False           |

脚本

import os
import csv

import unittest

# 模拟登陆函数
def login_function(username, password):
    vaild_data = {
        'user1': 'pass1',
        'user2': 'pass2',
        'user3': 'pass3'
    }
    return vaild_data.get(username) == password
    
# 加载Excel测试数据
def load_data_excel(file_path):
    import openpyxl
    data = []
    wb = openpyxl.load_workbook(file_path)
    sheet = wb.active
    v = sheet.values
    next(v) # 跳过标题行
    for row in v:
        data.append(row)
    print(data)
    return data
   
@ddt
class TestLogin(TestCase):
    
    @data(*load_data_excel(r'E:\workspace\day\log26\test_data.'))  #星号的作用是将load_test_data函数返回的数据解包成单独的参数,然后传递给@unittest.data装饰器
    @unpack
    def test_login(self, username, password, expected):
       """测试登陆功能"""
       result = login_function(username, password)
       self.assertEqual(str(result).lower(), str(expected).lower())

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

③使用JSON文件
JSON文件是一种轻量级的数据交换格式,非常适合用来存储测试数据。

[
    {"username": "user1", "password": "pass1", "expected_result": true},
    {"username": "user1", "password": "wrongpass", "expected_result": false},
    {"username": "user2", "password": "pass2", "expected_result": true},
    {"username": "nonexistent", "password": "any", "expected_result": false}
]

from ddt import ddt, data, unpack
import os
import csv
import unittest

# 模拟登陆函数
def login_function(username, password):
    vaild_data = {
        'user1': 'pass1',
        'user2': 'pass2',
        'user3': 'pass3'
    }
    return vaild_data.get(username) == password

# 加载JSON测试数据
def load_data_json(file_path):
    data = []
    import json
    with open(file_path, 'r') as f:
        data_json = json.load(f)
        for item in data_json:
            data.append((item['username'], item['password'], item['expected_result']))
    return data

@ddt
class TestLogin(TestCase):
  
    @data(*load_data_json(r'E:\workspace\day\log26\test_data.json'))  #星号的作用是将load_test_data函数返回的数据解包成单独的参数,然后传递给@unittest.data装饰器
    @unpack
    def test_login(self, username, password, expected):
       """测试登陆功能"""
       result = login_function(username, password)
       self.assertEqual(str(result).lower(), str(expected).lower())

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

④使用yaml文件
1.安装PyYAML

pip install PyYAML
# test_data.yaml
- username: user1
  password: pass1
  expected_result: true
- username: user1
  password: wrongpass
  expected_result: false
- username: user2
  password: pass2
  expected_result: true
- username: nonexistent
  password: any
  expected_result: false

2.脚本

from ddt import ddt, data, unpack
import os
import csv

import unittest

# 模拟登陆函数
def login_function(username, password):
    vaild_data = {
        'user1': 'pass1',
        'user2': 'pass2',
        'user3': 'pass3'
    }
    return vaild_data.get(username) == password

# 加载yaml测试数据
def load_data_yaml(file_path):
    data = []
    import yaml
    with open(file_path, 'r') as f:
        data_yaml = yaml.load(f, Loader=yaml.FullLoader)
        for item in data_yaml:
            data.append((item['username'], item['password'], item['expected_result']))
    return data

@ddt
class TestLogin(TestCase):
  
    @data(*load_data_yaml(r'E:\workspace\day\log26\test_data.yaml'))  #星号的作用是将load_test_data函数返回的数据解包成单独的参数,然后传递给@unittest.data装饰器
    @unpack
    def test_login(self, username, password, expected):
       """测试登陆功能"""
       result = login_function(username, password)
       self.assertEqual(str(result).lower(), str(expected).lower())

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

(2)使用数据库存储数据
如果数据量非常大或者需要频繁更新,可以考虑将数据存储在数据库中。这种方式可以更灵活地管理数据,并且可以利用数据库的功能来进行数据的检索和更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值