python中测试函数的简单介绍

python单元测试--mock使用mock , 可以将某个函数所依赖的对象或者变量mock掉,从而降低测试条件的负责度 。如下所示:
上述是mock对象的简单使用方法,通过实例化一个Mock对象从而模拟掉原始函数的返回值,高级一些的用法就是通过mock.patch装饰器 , 装饰在类或者函数上进行模拟测试 , 如下在test.py文件中有两个类:
测试用例设计如下:
以上测试用例说明,通过patch装饰器模拟了 test.ProductionClass1 这个类,在 test_01 中使用 mock_class 模拟 test.ProductionClass1。首先通过 mock_class.return_value 获取类实例(如果模拟的是函数,则不需要这一步) , 然后通过 obj1.pro1_method.return_value 设置方法的返回值,并进行测试 。测试结果说明无论是通过 mock_class 还是 test.ProductionClass1 还是 obj1 执行方法,获取到的结果都是设置的值 , 并且在另一个类中调用模拟类的方法 , 也能成功获取到设置的 return_value。
编写测试用例的代码时,经常会使用到函数, 那么Python中函数是什么? 有什么作用? 如何使用? 使用流程如何?函数的使用:
(1).函数是具有独立功能的代码块,是一个整体(封装了函数功能的代码), 完成某个功能的小工具
特点: 函数最大的特点是封装
(2).作用: 提高开发效率,实现代码的重用
(3).函数使用步骤:
① 定义函数: 把代码的功能封装成一个整体
② 调用函数: 享受封装的结果
(4).函数调用流程: ☆ ☆ ☆
① 函数定义时 不会执行函数体中代码
② 函数调用时,才会执行函数体中代码
③ 函数调用完成以后,一定要回到函数调用的地方继续向下执行
有关于软件测试的学习知识,可以看黑马程序员软件测试知识,有视频、文章、学习文档等等!
python中怎么判断函数是否可以调用Python: 测试函数是否被调用
# helper class defined elsewhere
class CallLogger(object):
def __init__(self, meth):
self.meth = meth
self.was_called = False
def __call__(self, code=None):
self.meth()
self.was_called = True
然后assert CallLogger的was_called为True就行了 。但是这样的Callable不是个函数:
isinstance(object, types.FunctionType) # Callable will be False
对于这种Callable获取参数个数需要用:
inspect.getargspec(fn.__call__)
后端编程Python3-调试、测试和性能剖析(下)单元测试(Unit Testing)
为程序编写测试——如果做的到位——有助于减少bug的出现,并可以提高我们对程序按预期目标运行的信心 。通常 , 测试并不能保证正确性 , 因为对大多数程序而言 , 可能的输入范围以及可能的计算范围是如此之大,只有其中最小的一部分能被实际地进 行测试 。尽管如此 , 通过仔细地选择测试的方法和目标,可以提高代码的质量 。
大量不同类型的测试都可以进行,比如可用性测试、功能测试以及整合测试等 。这里, 我们只讲单元测试一对单独的函数、类与方法进行测试,确保其符合预期的行为 。
TDD的一个关键点是,当我们想添加一个功能时——比如为类添加一个方法—— 我们首次为其编写一个测试用例 。当然,测试将失败,因为我们还没有实际编写该方法 。现在 , 我们编写该方法,一旦方法通过了测试,就可以返回所有测试,确保我们新添加的代码没有任何预期外的副作用 。一旦所有测试运行完毕(包括我们为新功能编写的测试),就可以对我们的代码进行检查,并有理有据地相信程序行为符合我们的期望——当然,前提是我们的测试是适当的 。
比如,我们编写了一个函数,该函数在特定的索引位置插入一个字符串,可以像下面这样开始我们的TDD:
def insert_at(string, position, insert):
"""Returns a copy of string with insert inserted at the position
string = "ABCDE"
result =[]
for i in range(-2, len(string)2):
... result.append(insert_at(string, i,“-”))
result[:5]
['ABC-DE', 'ABCD-E', '-ABCDE','A-BCDE', 'AB-CDE']
result[5:]
['ABC-DE', 'ABCD-E', 'ABCDE-', 'ABCDE-']
"""
return string
对不返回任何参数的函数或方法(通常返回None),我们通常赋予其由pass构成的一个suite,对那些返回值被试用的,我们或者返回一个常数(比如0),或者某个不变的参数——这也是我们这里所做的 。(在更复杂的情况下,返回fake对象可能更有用一一对这样的类,提供mock对象的第三方模块是可用的 。)
运行doctest时会失败,并列出每个预期内的字符串('ABCD-EF'、'ABCDE-F' 等) , 及其实际获取的字符串(所有的都是'ABCD-EF') 。一旦确定doctest是充分的和正确的 , 就可以编写该函数的主体部分,在本例中只是简单的return string[:position]insert string[position:] 。(如果我们编写的是 return string[:position]insert,之后复制 string [:position]并将其粘贴在末尾以便减少一些输入操作,那么doctest会立即提示错误 。)
Python的标准库提供了两个单元测试模块,一个是doctest,这里和前面都简单地提到过 , 另一个是unittest 。此外,还有一些可用于Python的第三方测试工具 。其中最著名的两个是nose (code.google.com/p/python-nose)与py.test (codespeak.net/py/dist/test/test.html), nose 致力于提供比标准的unittest 模块更广泛的功能,同时保持与该模块的兼容性,py.test则采用了与unittest有些不同的方法,试图尽可能消除样板测试代码 。这两个第三方模块都支持测试发现,因此没必要写一个总体的测试程序——因为模块将自己搜索测试程序 。这使得测试整个代码树或某一部分 (比如那些已经起作用的模块)变得很容易 。那些对测试严重关切的人,在决定使用哪个测试工具之前 , 对这两个(以及任何其他有吸引力的)第三方模块进行研究都是值 得的 。
创建doctest是直截了当的:我们在模块中编写测试、函数、类与方法的docstrings 。对于模块 , 我们简单地在末尾添加了 3行:
if __name__ =="__main__":
import doctest
doctest.testmod()
在程序内部使用doctest也是可能的 。比如 , blocks.py程序(其模块在后面)有自己函数的doctest,但以如下代码结尾:
if __name__== "__main__":
main()
这里简单地调用了程序的main()函数,并且没有执行程序的doctest 。要实验程序的 doctest,有两种方法 。一种是导入doctest模块,之后运行程序---比如,在控制台中输 入 python3 -m doctest blocks.py (在 Wndows 平台上 , 使用类似于 C:Python3 lpython.exe 这样的形式替代python3) 。如果所有测试运行良好,就没有输出,因此,我们可能宁愿执行python3-m doctest blocks.py-v,因为这会列出每个执行的doctest,并在最后给出结果摘要 。
另一种执行doctest的方法是使用unittest模块创建单独的测试程序 。在概念上,unittest模块是根据Java的JUnit单元测试库进行建模的,并用于创建包含测试用例的测试套件 。unittest模块可以基于doctests创建测试用例,而不需要知道程序或模块包含的任何事物——只要知道其包含doctest即可 。因此,为给blocks.py程序制作一个测试套件,我们可以创建如下的简单程序(将其称为test_blocks.py):
import doctest
import unittest
import blocks
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(blocks))
runner = unittest.TextTestRunner()
print(runner.run(suite))
注意 , 如果釆用这种方法 , 程序的名称上会有一个隐含的约束:程序名必须是有效的模块名 。因此,名为convert-incidents.py的程序的测试不能写成这样 。因为import convert-incidents不是有效的,在Python标识符中,连接符是无效的(避开这一约束是可能的 , 但最简单的解决方案是使用总是有效模块名的程序文件名,比如,使用下划线替换连接符) 。这里展示的结构(创建一个测试套件,添加一个或多个测试用例或测试套件,运行总体的测试套件,输出结果)是典型的机遇unittest的测试 。运行时,这一特定实例产生如下结果:
...
.............................................................................................................
Ran 3 tests in 0.244s
OK
每次执行一个测试用例时,都会输出一个句点(因此上面的输出最前面有3个句点),之后是一行连接符,再之后是测试摘要(如果有任何一个测试失败,就会有更多的输出信息) 。
如果我们尝试将测试分离开(典型情况下是要测试的每个程序和模块都有一个测试用例),就不要再使用doctests,而是直接使用unittest模块的功能——尤其是我们习惯于使用JUnit方法进行测试时ounittest模块会将测试分离于代码——对大型项目(测试编写人员与开发人员可能不一致)而言,这种方法特别有用 。此外,unittest单元测试编写为独立的Python模块,因此 , 不会像在docstring内部编写测试用例时受到兼容性和明智性的限制 。
unittest模块定义了 4个关键概念 。测试夹具是一个用于描述创建测试(以及用完之后将其清理)所必需的代码的术语,典型实例是创建测试所用的一个输入文件,最后删除输入文件与结果输出文件 。测试套件是一组测试用例的组合 。测试用例是测试的基本单元—我们很快就会看到实例 。测试运行者是执行一个或多个测试套件的对象 。
典型情况下 , 测试套件是通过创建unittest.TestCase的子类实现的,其中每个名称 以“test”开头的方法都是一个测试用例 。如果我们需要完成任何创建操作,就可以在一个名为setUp()的方法中实现;类似地,对任何清理操作,也可以实现一个名为 tearDown()的方法 。在测试内部,有大量可供我们使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(对于测试浮点数很有用)、assertRaises() 以及更多,还包括很多对应的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等 。
unittest模块进行了很好的归档,并且提供了大量功能,但在这里我们只是通过一 个非常简单的测试套件来感受一下该模块的使用 。这里将要使用的实例,该练习要求创建一个Atomic模块,该模块可以用作一 个上下文管理器,以确保或者所有改变都应用于某个列表、集合或字典,或者所有改变都不应用 。作为解决方案提供的Atomic.py模块使用30行代码来实现Atomic类, 并提供了 100行左右的模块doctest 。这里,我们将创建test_Atomic.py模块,并使用 unittest测试替换doctest,以便可以删除doctest 。
在编写测试模块之前,我们需要思考都需要哪些测试 。我们需要测试3种不同的数据类型:列表、集合与字典 。对于列表 , 需要测试的是插入项、删除项或修改项的值 。对于集合,我们必须测试向其中添加或删除一个项 。对于字典,我们必须测试的是插入一个项、修改一个项的值、删除一个项 。此外,还必须要测试的是在失败的情况下,不会有任何改变实际生效 。
结构上看,测试不同数据类型实质上是一样的,因此,我们将只为测试列表编写测试用例,而将其他的留作练习 。test_Atomic.py模块必须导入unittest模块与要进行测试的Atomic模块 。
创建unittest文件时,我们通常创建的是模块而非程序 。在每个模块内部,我们定义一个或多个unittest.TestCase子类 。比如 , test_Atomic.py模块中仅一个单独的 unittest-TestCase子类,也就是TestAtomic (稍后将对其进行讲解) , 并以如下两行结束:
if name == "__main__":
unittest.main()
这两行使得该模块可以单独运行 。当然,该模块也可以被导入并从其他测试程序中运行——如果这只是多个测试套件中的一个,这一点是有意义的 。
如果想要从其他测试程序中运行test_Atomic.py模块 , 那么可以编写一个与此类似的程序 。我们习惯于使用unittest模块执行doctests,比如:
import unittest
import test_Atomic
suite = unittest.TestLoader().loadTestsFromTestCase(test_Atomic.TestAtomic)
runner = unittest.TextTestRunner()
pnnt(runner.run(suite))
这里,我们已经创建了一个单独的套件,这是通过让unittest模块读取test_Atomic 模块实现的,并且使用其每一个test*()方法(本实例中是test_list_success()、test_list_fail() , 稍后很快就会看到)作为测试用例 。
我们现在将查看TestAtomic类的实现 。对通常的子类(不包括unittest.TestCase 子类),不怎么常见的是,没有必要实现初始化程序 。在这一案例中,我们将需要建立 一个方法 , 但不需要清理方法,并且我们将实现两个测试用例 。
def setUp(self):
self.original_list = list(range(10))
我们已经使用了 unittest.TestCase.setUp()方法来创建单独的测试数据片段 。
def test_list_succeed(self):
items = self.original_list[:]
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4]= -782
atomic.insert(0, -9)
self.assertEqual(items,
[-9, 0, 1, -915, 2, -782, 5, 6, 7, 8, 9, 1999])
def test_list_fail(self):
items = self.original_list[:]
with self.assertRaises(AttributeError):
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4] = -782
atomic.poop() # Typo
self.assertListEqual(items, self.original_list)
这里 , 我们直接在测试方法中编写了测试代码,而不需要一个内部函数,也不再使用unittest.TestCase.assertRaised()作为上下文管理器(期望代码产生AttributeError) 。最后我们也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法 。
正如我们已经看到的,Python的测试模块易于使用,并且极为有用 , 在我们使用 TDD的情况下更是如此 。它们还有比这里展示的要多得多的大量功能与特征——比如,跳过测试的能力,这有助于理解平台差别——并且这些都有很好的文档支持 。缺失的一个功能——但nose与py.test提供了——是测试发现,尽管这一特征被期望在后续的Python版本(或许与Python 3.2—起)中出现 。
性能剖析(Profiling)
如果程序运行很慢,或者消耗了比预期内要多得多的内存,那么问题通常是选择的算法或数据结构不合适 , 或者是以低效的方式进行实现 。不管问题的原因是什么,最好的方法都是准确地找到问题发生的地方,而不只是检査代码并试图对其进行优化 。随机优化会导致引入bug,或者对程序中本来对程序整体性能并没有实际影响的部分进行提速 , 而这并非解释器耗费大部分时间的地方 。
在深入讨论profiling之前,注意一些易于学习和使用的Python程序设计习惯是有意义的,并且对提高程序性能不无裨益 。这些技术都不是特定于某个Python版本的,而是合理的Python程序设计风格 。第一,在需要只读序列时,最好使用元组而非列表; 第二 , 使用生成器 , 而不是创建大的元组和列表并在其上进行迭代处理;第三,尽量使用Python内置的数据结构 dicts、lists、tuples 而不实现自己的自定义结构,因为内置的数据结构都是经过了高度优化的;第四 , 从小字符串中产生大字符串时,不要对小字符串进行连接,而是在列表中累积 , 最后将字符串列表结合成为一个单独的字符串;第五,也是最后一点,如果某个对象(包括函数或方法)需要多次使用属性进行访问(比如访问模块中的某个函数),或从某个数据结构中进行访问,那么较好的做法是创建并使用一个局部变量来访问该对象,以便提供更快的访问速度 。
Python标准库提供了两个特别有用的模块,可以辅助调査代码的性能问题 。一个是timeit模块——该模块可用于对一小段Python代码进行计时 , 并可用于诸如对两个或多个特定函数或方法的性能进行比较等场合 。另一个是cProfile模块,可用于profile 程序的性能——该模块对调用计数与次数进行了详细分解,以便发现性能瓶颈所在 。
为了解timeit模块 , 我们将查看一些小实例 。假定有3个函数function_a()、 function_b()、function_c(), 3个函数执行同样的计算,但分别使用不同的算法 。如果将这些函数放于同一个模块中(或分别导入),就可以使用timeit模块对其进行运行和比较 。下面给出的是模块最后使用的代码:
if __name__ == "__main__":
repeats = 1000
for function in ("function_a", "function_b", "function_c"):
t = timeit.Timer("{0}(X, Y)".format(function),"from __main__ import {0}, X, Y".format(function))
sec = t.timeit(repeats) / repeats
print("{function}() {sec:.6f} sec".format(**locals()))
赋予timeit.Timer()构造子的第一个参数是我们想要执行并计时的代码,其形式是字符串 。这里,该字符串是“function_a(X,Y)”;第二个参数是可选的 , 还是一个待执行的字符串,这一次是在待计时的代码之前,以便提供一些建立工作 。这里,我们从 __main__ (即this)模块导入了待测试的函数,还有两个作为输入数据传入的变量(X 与Y),这两个变量在该模块中是作为全局变量提供的 。我们也可以很轻易地像从其他模块中导入数据一样来进行导入操作 。
调用timeit.Timer对象的timeit()方法时 , 首先将执行构造子的第二个参数(如果有),之后执行构造子的第一个参数并对其执行时间进行计时 。timeit.Timer.timeit()方法的返回值是以秒计数的时间,类型是float 。默认情况下,timeit()方法重复100万次,并返回所 有这些执行的总秒数,但在这一特定案例中 , 只需要1000次反复就可以给出有用的结果, 因此对重复计数次数进行了显式指定 。在对每个函数进行计时后,使用重复次数对总数进行除法操作,就得到了平均执行时间,并在控制台中打印出函数名与执行时间 。
function_a() 0.001618 sec
function_b() 0.012786 sec
function_c() 0.003248 sec
在这一实例中 , function_a()显然是最快的——至少对于这里使用的输入数据而言 。在有些情况下一一比如输入数据不同会对性能产生巨大影响——可能需要使用多组输入数据对每个函数进行测试,以便覆盖有代表性的测试用例,并对总执行时间或平均执行时间进行比较 。
有时监控自己的代码进行计时并不是很方便 , 因此timeit模块提供了一种在命令行中对代码执行时间进行计时的途径 。比如,要对MyModule.py模块中的函数function_a()进行计时,可以在控制台中输入如下命令:python3 -m timeit -n 1000 -s "from MyModule import function_a, X, Y" "function_a(X, Y)"(与通常所做的一样,对 Windows 环境 , 我们必须使用类似于C:Python3lpython.exe这样的内容来替换python3) 。-m选项用于Python 解释器,使其可以加载指定的模块(这里是timeit),其他选项则由timeit模块进行处理 。-n选项指定了循环计数次数,-s选项指定了要建立,最后一个参数是要执行和计时的代码 。命令完成后 , 会向控制台中打印运行结果,比如:
1000 loops, best of 3: 1.41 msec per loop
之后我们可以轻易地对其他两个函数进行计时,以便对其进行整体的比较 。
cProfile模块(或者profile模块,这里统称为cProfile模块)也可以用于比较函数 与方法的性能 。与只是提供原始计时的timeit模块不同的是 , cProfile模块精确地展示 了有什么被调用以及每个调用耗费了多少时间 。下面是用于比较与前面一样的3个函数的代码:
if __name__ == "__main__":
for function in ("function_a", "function_b", "function_c"):
cProfile.run("for i in ranged 1000): {0}(X, Y)".format(function))
我们必须将重复的次数放置在要传递给cProfile.run()函数的代码内部,但不需要做任何创建,因为模块函数会使用内省来寻找需要使用的函数与变量 。这里没有使用显式的print()语句,因为默认情况下,cProfile.run()函数会在控制台中打印其输出 。下面给出的是所有函数的相关结果(有些无关行被省略,格式也进行了稍许调整,以便与页面适应):
1003 function calls in 1.661 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.003 0.003 1.661 1.661:1 ( )
1000 1.658 0.002 1.658 0.002 MyModule.py:21 (function_a)
1 0.000 0.000 1.661 1.661 {built-in method exec}
5132003 function calls in 22.700 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.487 0.487 22.700 22.700: 1 ( )
1000 0.011 0.000 22.213 0.022 MyModule.py:28(function_b)
5128000 7.048 0.000 7.048 0.000 MyModule.py:29( )
1000 0.00 50.000 0.005 0.000 {built-in method bisectjeft}
1 0.000 0.000 22.700 22.700 {built-in method exec}
1000 0.001 0.000 0.001 0.000 {built-in method len}
1000 15.149 0.015 22.196 0.022 {built-in method sorted}
5129003 function calls in 12.987 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.205 0.205 12.987 12.987:l ( )
1000 6.472 0.006 12.782 0.013 MyModule.py:36(function_c)
5128000 6.311 0.000 6.311 0.000 MyModule.py:37( )
1 0.000 0.000 12.987 12.987 {built-in method exec}
ncalls ("调用的次数")列列出了对指定函数(在filename:lineno(function)中列出) 的调用次数 。回想一下我们重复了 1000次调用,因此必须将这个次数记住 。tottime (“总的时间”)列列出了某个函数中耗费的总时间,但是排除了函数调用的其他函数内部花费的时间 。第一个percall列列出了对函数的每次调用的平均时间(tottime // ncalls) 。cumtime ("累积时间")列出了在函数中耗费的时间 , 并且包含了函数调用的其他函数内部花费的时间 。第二个percall列列出了对函数的每次调用的平均时间 , 包括其调用的函数耗费的时间 。
这种输出信息要比timeit模块的原始计时信息富有启发意义的多 。我们立即可以发现,function_b()与function_c()使用了被调用5000次以上的生成器,使得它们的速度至少要比function_a()慢10倍以上 。并且,function_b()调用了更多通常意义上的函数,包括调用内置的sorted()函数,这使得其几乎比function_c()还要慢两倍 。当然,timeit() 模块提供了足够的信息来查看计时上存在的这些差别,但cProfile模块允许我们了解为什么会存在这些差别 。正如timeit模块允许对代码进行计时而又不需要对其监控一样,cProfile模块也可以做到这一点 。然而,从命令行使用cProfile模块时,我们不能精确地指定要执行的 是什么——而只是执行给定的程序或模块,并报告所有这些的计时结果 。需要使用的 命令行是python3 -m cProfile programOrModule.py,产生的输出信息与前面看到的一 样,下面给出的是输出信息样例,格式上进行了一些调整 , 并忽略了大多数行:
10272458 function calls (10272457 primitive calls) in 37.718 CPU secs
ncalls tottime percall cumtime percall filename:lineno(function)
10.000 0.000 37.718 37.718:1 ( )
10.719 0.719 37.717 37.717:12( )
1000 1.569 0.002 1.569 0.002:20(function_a)
1000 0.011 0.000 22.560 0.023:27(function_b)
5128000 7.078 0.000 7.078 0.000:28( )
1000 6.510 0.007 12.825 0.013:35(function_c)
5128000 6.316 0.000 6.316 0.000:36( )
在cProfile术语学中,原始调用指的就是非递归的函数调用 。
以这种方式使用cProfile模块对于识别值得进一步研究的区域是有用的 。比如,这里 我们可以清晰地看到function_b()需要耗费更长的时间,但是我们怎样获取进一步的详细资料?我们可以使用cProfile.run("function_b()")来替换对function_b()的调用 。或者可以保存完全的profile数据并使用pstats模块对其进行分析 。要保存profile,就必须对命令行进行稍许修改:python3 -m cProfile -o profileDataFile programOrModule.py 。之后可以对 profile 数据进行分析,比如启动IDLE,导入pstats模块,赋予其已保存的profileDataFile,或者也可以在控制台中交互式地使用pstats 。
下面给出的是一个非常短的控制台会话实例,为使其适合页面展示,进行了适当调整,我们自己的输入则以粗体展示:
$ python3 -m cProfile -o profile.dat MyModule.py
$ python3 -m pstats
Welcome to the profile statistics browser.
% read profile.dat
profile.dat% callers function_b
Random listing order was used
List reduced from 44 to 1 due to restriction
Function was called by...
ncalls tottime cumtime
:27(function_b) - 1000 0.011 22.251:12( )
profile.dat% callees function_b
Random listing order was used
List reduced from 44 to 1 due to restriction
Function called...
ncalls tottime cumtime
:27(function_b)-
1000 0.005 0.005 built-in method bisectJeft
1000 0.001 0.001 built-in method len
1000 1 5.297 22.234 built-in method sorted
profile.dat% quit
输入help可以获取命令列表 , help后面跟随命令名可以获取该命令的更多信息 。比如, help stats将列出可以赋予stats命令的参数 。还有其他一些可用的工具 , 可以提供profile数据的图形化展示形式 , 比如 RunSnakeRun (), 该工具需要依赖于wxPython GUI库 。
使用timeit与cProfile模块,我们可以识别出我们自己代码中哪些区域会耗费超过预期的时间;使用cProfile模块,还可以准确算岀时间消耗在哪里 。
以上内容部分摘自视频课程 05后端编程Python-19调试、测试和性能调优(下),更多实操示例请参照视频讲解 。跟着张员外讲编程,学习更轻松,不花钱还能学习真本领 。
Python基础之pytest参数化pytest是目前比较成熟功能齐全的测试框架,使用率肯定也不断攀升 。在实际
工作中,许多测试用例都是类似的重复,一个个写最后代码会显得很冗余 。这里,我们来了解一下
@pytest.mark.parametrize装饰器,可以很好的解决上述问题 。
释义:参数名称
格式:字符串"arg1,arg2 , arg3"
释义:参数值列表
格式:必须是列表 , 如[val1,val2,val3]
单个参数,里面是值的列表,如@pytest.mark.parametrize("name",["Jack","Locus","Bill"])
多个参数,需要用元祖来存放值 , 一个元祖对应一组参数的值,如@pytest.mark.parametrize("user,age",[("user1",15),("user2" , 24),("user3",25)])
释义:可以理解为用例的id
格式:字符串列表,如["case1","case2","case3"]
释义:当indirect=True时,若传入的argnames是fixture函数名,此时fixture函数名将成为一个可执行的函数,
argvalues作为fixture的参数 , 执行fixture函数,最终结果再存入 request.param;当indirect=False时,fixture
函数只作为一个参数名给测试收集阶段调用 。
备注:这里可以将the setup phase(测试设置阶段)理解为配置 conftest.py 阶段,将the collection phase(
测试收集阶段)理解为用例执行阶段 。
由以上代码可以看到,当装饰器装饰测试类时 , 定义的数据集合会被传递给类的所有方法 。
当测试用例只需要一个参数时,我们存放数据的列表无序嵌套序列,@pytest.mark.parametrize("name", data)
装饰器的第一个参数也只需要一个变量接收列表中的每个元素,第二个参数传递存储数据的列表,那么测试用
例需要使用同名的字符串接收测试数据(实例中的name)且列表有多少个元素就会生成并执行多少个测试用例 。
当测试用例需要多个数据时,我们可以使用嵌套序列(嵌套元组嵌套列表)的列表来存放测试数据 。
装饰器@pytest.mark.parametrize()可以使用单个变量接收数据,也可以使用多个变量接收,同样,测
试用例函数也需要与其保持一致 。
当使用单个变量接收时 , 测试数据传递到测试函数内部时为列表中的每一个元素或者小列表,需
要使用索引的方式取得每个数据 。
当使用多个变量接收数据时,那么每个变量分别接收小列表或元组中的每个元素列表嵌套多少个多
组小列表或元组,测生成多少条测试用例 。
通过测试结果,我们不难分析 , 一个测试函数还可以同时被多个参数化装饰器装饰,那么多个
装饰器中的数据会进行交叉组合的方式传递给测试函数,进而生成n * n个测试用例 。
输出结果显示收集到4个用例,两个通过,一个被跳过,一个标记失败 , 当我们不想执行某组测试
数据时 , 我们可以标记skip或skipif;当我们预期某组数据会执行失败时 , 我们可以标记为xfail等 。
参数化装饰器有一个额外的参数ids,可以标识每一个测试用例,自定义测试数据结果的显示 ,
为了增加可读性,我们可以标记每一个测试用例使用的测试数据是什么 , 适当的增加一些说明 。
在使用前你需要知道,ids参数应该是一个字符串列表,必须和数据对象列表的长度保持一致 。
不加ids参数的返回结果
加ids参数的返回结果
我们可以看到带ids参数的返回结果中的用例都被一个列表明确的标记了,而且通过这种标记
可以更加直观的看出来,每个测试用例使用的数据名称及测试内容 。
Python 编写并测试函数change(str1),其功能是对参数str1进行大小写转换?def change(str1):
new_str = str()
for i in range(len(str1)):
if(65 = ord(str1[i]) = 90):
a = str1[i].lower()
print(a,end='')
elif(97 = ord(str1[i]) = 122):
a = str1[i].upper()
print(a,end='')
else:
a = str1[i]
print(a,end='')
return new_str
str2 = str(input("要转换的字符串:"))
print(change(str2))
【python中测试函数的简单介绍】关于python中测试函数和的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。

    推荐阅读