书接上回, 点击查看上回!!!

4 unittest单元测试框架

unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的功能。

4.1 认识unittest

使用unittest前需要了解该框架的五个概念:即test case,test suite,test loader,test runner,test fixture。
test case:一个完整的测试单元,执行该测试单元可以完成对某一个问题的验证,完整体现在:测试前环境准备(setUp),执行测试代码(run),以及测试后环境还原(tearDown)。
test suite:多个测试用例的集合,测试套件或测试计划。
testLoader:加载TestCase到TestSuite中的,其中loadTestsFrom_()方法用于寻找TestCase,并创建它们的实例,然后添加到TestSuite中,返回TestSuite实例。
test runner:执行测试用例,并将测试结果保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
test fixture:一个测试用例的初始化准备及环境还原,主要是setUp()和setDown()方法。

unittest单元测试框架功能:
提供用例组织与执行。
提供丰富的断言方法。
提供丰富的日志和报告。

4.2 unittest单元测试框架工作原理

unittest中最核心的四个概念是:test case, test suite, test runner, test fixture。
unittest的静态类图,如下图:
在这里插入图片描述
一个TestCase的实例就是一个测试用例。测试用例就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。元测试(unit test)的本质也就在这里,一个测试用例是一个完整的测试单元,通过运行这个测试单元,可以对某一个问题进行验证。
而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite。 TestLoader是用来加载TestCase到TestSuite中的,其中有几个loadTestsFrom()方法,就是从各个地方寻找TestCase,创建它们的实例,然后add到TestSuite中,再返回一个TestSuite实例。

TextTestRunner是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。
而对一个测试用例环境的搭建和销毁,是一个fixture。

如下例子:

# 导入unittest
import unittest
# 新建类,继承unittest.TestCase
class unitMath(unittest.TestCase):
    # setUp启动
    def setUp(self):
        print("setUp方法")
    # 编写测试用例必须以test开头
    def test_add_1(self):
        print("第一条测试用例")
    # tearDown方法释放资源
    def tearDown(self):
        print("tearDown")
# main方法
if __name__ == '__main__':
    unittest.main()

运行结果如下图:
在这里插入图片描述
注:常见报错可查阅下列文章。
点击我!!

分析上述代码:
无论setUp()方法,test_add_1()方法和tearDown()方法顺序如何排序,都不会影响输入结果顺序。
编写测试用例必须以test开头。
在main方法中运行unittest.main()

方法作用
setUp()进行测试用例的资源初始化,测试用例的前提写在这
test_xx()测试用例,把测试用例的步骤写在这个方法中
tearDowm()进行测试用例的资源释放

4.3 unittest的用法

unittest单元测试框架包括环境搭建,编写测试用例,形成测试集,执行测试用集,得出测试结果。

4.3.1 setUpclass()和setDownClass()

setUpclass()和setDownClass()需要修饰器,@classmethod。
@classmethod的作用实际是可以在class内实例化class,一般使用在有工厂模式要求时。作用就是比如输入的数据需要清洗一遍再实例化,可以把清洗函数定义在class内部并加上@classmethod装饰器已达到减少代码的目的。
一句话就是,@class method可以用来为一个类创建一些预处理的实例。
表现为:classmethod修饰符对应的函数不需要实例化,可以来调用类的属性,类的方法,实例化对象等。
工厂模式:用户只需通过固定的接口获得一个对象的实例,降低了维护的复杂性。
例子如下:

class E01(unittest.TestCase):
    # 修饰器
    @classmethod
    def setUpClass(cls):
        print("setUpClass方法被调用")

    @classmethod
    def tearDownClass(cls):
        print("tearDowwnClass方法被调用")
4.3.2 setUp()与setUpClass()的区别
setUp()setUpClass()
@classmethod不需要@classmethod需要@classmethod
调用方法需要创建对象再调用不需要创建对象也可以调用
执行测试用例在每一条测试用例执行之前运行一次在执行测试用例之前只运行一次
初始化对每一条测试用例都要初始化给当前单元测试所有的用例进行初始化

在setUp()方法实例化对象obj1,就不用在每一条测试用例前实例化对象,减少重复。此外,需要在tearDown()方法注释对象,释放资源,如下代码:

    def tearDown(self) :
        self.obj1 = None
        print("tearDown方法被调用")

还可以在setUpClass()方法实例化对象ojb1,不同的是,需要在tearDownClass()注释对象,释放资源。

4.3.3 TestCase类

TestCase以tesxt_xxx方法名格式出现,例子如下代码:

 def test_add_1(self):
        print("执行第一条测试用例")
        # obj1 = math()
        actual_result = self.obj1.add(2,3)
        execpt_result = 5
        self.assertEqual(actual_result,execpt_result,"预期结果和实际结果不相等")
    # 执行第二条测试用例
    def test_add_2(self):
        print("执行第二条测试用例")
        # obj1 = math()
        actual_result02 = self.obj1.add("ab","c")
        expect_result02 = "abc"
        self.assertEqual(actual_result02,expect_result02,"预期结果和实际结果不相等")

    # 执行第三条测试用例
    def test_multiplication_1(self):
        print("执行第三条测试用例")
        actual_result03 = self.obj1.multiplication(2,3)
        expect_result03 = 6
        self.assertEqual(actual_result03,expect_result03,"预期结果和实际结果不相等")

分析上述代码:
obj1对象调用方法得出实际结果,与预期结果做断言,分析用例是否通过。
assertEqual()主要应用于断言,即预期结果是否与实际结果一致。

如果想单独执行加法单元测试用例,如何操作呢?
答:用测试用例集合,把加法的测试用例添加到测试用例集合,运行该测试用例集合。

4.3.4 TestSuite类

多个测试用例集合在一起,就是TestSuite。
(1)创建测试用例集合
如下代码:

if __name__ == '__main__':
    # 创建测试用例集合,suite01
    suite01 = unittest.TestSuite()

(2)往测试用例集合添加测试用例
方法1:一个个测试用例添加到测试集中
如下代码:

if __name__ == '__main__':
    # unittest.main()
    # 创建测试用例集合,suite01
    suite01 = unittest.TestSuite()
    # 往suite01调价测试用例
    suite01.addTest(E01("test_add_2"))
    suite01.addTest(E01("test_add_1"))

分析上述代码:
需要在main方法中创建测试用例集合suite01对象,使用addTest方法往suite01对象添加测试用例。

方法2:一次性往测试集中添加多条测试用例
如下代码:

if __name__ == '__main__':
    # 创建测试用例集合,suite01
    suite01 = unittest.TestSuite()
    suite01.addTests(map(E01,["test_add_2","test_add_1"]))

分析上述代码:
使用addTests()实现一次性添加多条测试用例,map()方法。
注:map()知识点
描述:
map() 会根据提供的函数对指定序列做映射
map() 函数语法:
map(function, iterable, ...)
第一个参数function以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
第二个参数iterable,一个或多个序列。
(3)找到结果,运行结果,输入报告
代码如下:

    # 找到结果
    re = unittest.TestResult()
    # 使用run方法运行,需要有结果
    suite01.run(re)
    # 输出报告
    print(re.__dict__)

分析上述代码:
需要在main中写上述代码。

综上,demo3.py代码如下:

from demo01 import math
import unittest
class E01(unittest.TestCase):
    # 一种注释
    @classmethod
    def setUpClass(cls):
        print("setUpClass方法被调用")

    @classmethod
    def tearDownClass(cls):
        print("tearDowwnClass方法被调用")

    def setUp(self):
        print("setUp方法被调用")
        # 不在测试用例上实例化,减少重复
        self.obj1 = math()
    # 执行第一条测试用例,必须是test开头
    def test_add_1(self):
        print("执行第一条测试用例")
        # obj1 = math()
        actual_result = self.obj1.add(2,3)
        execpt_result = 5
        self.assertEqual(actual_result,execpt_result,"预期结果和实际结果不相等")
    # 执行第二条测试用例
    def test_add_2(self):
        print("执行第二条测试用例")
        # obj1 = math()
        actual_result02 = self.obj1.add("ab","c")
        expect_result02 = "abc"
        self.assertEqual(actual_result02,expect_result02,"预期结果和实际结果不相等")

    # 执行第三条测试用例
    def test_multiplication_1(self):
        print("执行第三条测试用例")
        actual_result03 = self.obj1.multiplication(2,3)
        expect_result03 = 6
        self.assertEqual(actual_result03,expect_result03,"预期结果和实际结果不相等")
    def tearDown(self) :
        self.obj1 = None
        print("tearDown方法被调用")
if __name__ == '__main__':
    # unittest.main()
    # 创建测试用例集合,suite01
    suite01 = unittest.TestSuite()
    # 往suite01添加测试用例
    suite01.addTest(E01("test_add_2"))
    suite01.addTest(E01("test_add_1"))
    # 一次性添加多条测试用例
    # suite01.addTests(map(E01,["test_add_2","test_add_1"]))
    # 找到结果
    re = unittest.TestResult()
    # 使用run方法运行,需要有结果
    suite01.run(re)
    # 输出报告
    print(re.__dict__)

demo01.py,代码如下:

# 定义一个类,可以实现加减乘除法
class math():
    # 加法功能
    def add(self,a,b):
        return a + b
    # 减法功能
    def subtraction(self,a,b):
        return a - b
    # 乘法功能
    def multiplication(self,a,b):
        return  a * b
    # 除法功能
    def divison(self,a,b):
        if b==0:
            return "Error"
        else:
            return a /b
if __name__ == '__main__':
    # 实例化对象obj1
    obj1 = math()
    # 编写第一条用例,两整型相加
    # obj1调用add方法,参数为2,3
    actual_result = obj1.add(2,3)
    # 预期结果为4
    expect_result = 5
    # 如果实际结果和预期结果一致,则加法功能实现
    if actual_result == expect_result:
        print("加法功能实现正确")
    # 编写第二条用例,为反向用例,a为字符串,b为整型
    try:
        # obj1对象调用add方法,参数为“a”,3,有异常
        actual_result02 = obj1.add("a",3)
    except Exception as e:
        print("反向用例通过",e)

    # 编写第三条用例,实现字符串拼接
    try:
       actual_result03 =  obj1.add("a","b")
       expect_result03 = "ab"
       if actual_result03 == expect_result03:
           print("该加法功能可以实现字符串拼接")
    except Exception as e:
        print(e)

再次强调,使用shift+F10运行,运行的是E01类。
如果测试用例数量很大,使用TestSuite自带的方法添加到用例集合中,显然不是明智的选择,所以需要TestLoader模块。

4.3.5 TestLoader类

(1) 创建TestLoader对象
如下代码:

if __name__ == '__main__':
    # 创建TestLoader对象
    loader01 = unittest.TestLoader()

(2)loadTestFromName()方法

if __name__ == '__main__':
    # 创建TestLoader对象
    loader01 = unittest.TestLoader()
    # 运行demo04文件中所有的测试用例
    suitt = loader01.loadTestsFromName("demo04")
    # 运行demo04文件中E01类中所有的测试用例
    suitt = loader01.loadTestsFromName("demo04.E01")
    # 运行demo04文件中E01类中的第一条测试用例
    suitt = loader01.loadTestsFromName("demo04.E01.test_add_1")

分析上述代码:
loadTestFromName()方法将指定的测试用例加载到测试集中。

(3)discover()方法
作用:将指定的测试用例加载到测试集中

if __name__ == '__main__':
 # 找到当前文件的目录
    filename = os.path.dirname(__file__)
    # print(filename)
    discover()方法在defaultTestLoader类下
    suitt = unittest.defaultTestLoader.discover(filename,pattern="*04.py")
    # 统计执行了的测试用例
    print(suitt.countTestCases())
    # testresult()方法存储测试结果的
    re = unittest.TestResult()
    # 使用run方法运行
    suitt.run(re)
    # 输出报告
    print(re.__dict__)

运行结果如下:
在这里插入图片描述

分析上述代码:
discover(path,pattern)
path参数指定存放测试用例的目录,即单元测试用例所在的文件。pattern参数指定匹配规则。
显然,执行结果可读性很差,结果不清晰,所以引入TestRunner模块。

4.3.6 TestRunner类

在unittest单元测试框架中,通过TextTestRunner模块提供的run()方法来执行testsuite或testcase,测试的结果会保存到TextTestResult中,其中信息包括运行了多少测试用例,通过了多少条,不通过多少条。
之前执行测试集合是用testsuite()的run()方法,如下:

    # TestResult()方法存储测试结果的
    re = unittest.TestResult()
    # 使用run方法运行
    suitt.run(re)
    # 输出报告
    print(re.__dict__)

分析上述代码:
需要有运行TestResult()方法存储测试结果,再使用run()方法才能得出结果。

使用TestRunner模块下的TextTestRunner()执行测试用例,如下代码:

if __name__ == '__main__':
    # 创建loader对象为,loader01
    loader01 = unittest.TestLoader()
    # 找到当前文件位置
    path = os.path.dirname(__file__)
    suite01 = unittest.defaultTestLoader.discover(path,pattern="*05.py")
    # 创建re.txt文件
    with open(r"./re.txt","w",encoding="utf-8") as f:
        # 创建runner对象
        runner = unittest.TextTestRunner(f,descriptions="单元测试",verbosity=2)
        # 运行测试用例
        runner.run(suite01)

分析上述代码:
使用discoer()将3条测试用例添加到测试集合,使用TextTestRunner()方法提供的run()方法运行测试集合,结果存储在re.txt文件中。
TextTestRunner()方法中的第一参数为文件。verbosity参数默认值为1,表示输入的txt文件只显示执行结果,没有显示具体哪一条执行成功或失败。当verbosity参数默认值大于1,则会有详细报告,如下图:
在这里插入图片描述
当verbosity参数默认值小于1,报告则很简略。

4.3.7 TestResult类

测试结果类,用来处理测试用例或测试集执行过程中的所有信息并最终输出。比如代码错误、异常、断言失败、skip等等。所以如果想要增加一些个性化的输出,可以通过或者此类或者基类(TestResult),扩展HTMLTestRunner的大神就是扩展了基类TestResult,增加了一下几个重要的统计属性和重写了基类的一些方法。

4.3.8 断言方法

在执行测试用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果与预期结果是否一致决定的。unittest框架的TestCase类提供用于测试结果判断的方法如下表:

方法检查描述
assertEqual(a,b)a == b判断a和b是否相等
assertNotEqual(a,b)a != b判断a和b是否不相等
assertTrue(x)bool(x) is True判断 x是否为True
assertFalse(x)bool(x) is False判断x是否为False
assertls(a,b)a is b判断a和b的内存地址是否相等,相等返回True
assertIsNot(a,b)a is not b判断a和b的内存地址是否不相等,不相等返回True
assertlsNone(x)x is None判断x是不是空指针,若是空指针,则返回True
assertlsNotNone(x)x is not None判断a是不是空指针,若不是空指针,则返回True
assertIn(a,b)a in b判断a是不是b的成员,若是,返回True
assertNotIn(a,b)a not in b判断a是不是b的成员,若不是,返回True
assertIsInstance(a,b)isinstance(a,b)判断a是不是b的一个实例对象,若是,返回True
asserNotIsInstance(a,b)not isinstance(a,b)判断a是不是b的一个实例对象,若不是,返回True

以上方法用在测试用例的断言部分,如果断言成功,则测试用例通过。

4.3.9 HTMLTestRunner子类

对软件测试人员来讲,测试的产出很难衡量。换句话说,测试人员的价值比较难以量化和评估,同样测试人员花费了很多时间和精力所做的自动化测试也是如此。所以,一份漂亮且通俗易懂的测试报告来展示自动化测试成果尤为重要,但是,我们前面简单的生成一个txt文件是不够的。
HTMLTestRunner是python标准库unittest单元测试框架的一个扩展,它生成易于使用的HTML测试报告。只有一个HTMLTestRunner.py文件,选中后右键另存为python的安装目录下: …/python/Lib。
下载HTMLTestRunner.py如下:

链接:https://pan.baidu.com/s/1tjNP1fC21W3wYEkp6hE2cA?pwd=3dfr 
提取码:3dfr

(1)把不同类型的测试用例进行分类
加法功能的测试用例放在demo06_add.py文件,乘法功能的测试用例放在demo06_mtiplication.py,除法功能的测用例放在demo06_division.py。
demo06_add.py文件如下:

import unittest
from demo01 import math
class E01_add(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        pass
    def setUp(self):
        # 实例化对象
        self.obj1 = math()
    def test_add_t_01(self):
        acutal_value = self.obj1.add(2,3)
        expect_value =5
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")
    # 加法功能第二条正向用例,字符串拼接
    def test_add_t_02(self):
        acutal_value = self.obj1.add("a", "bc")
        expect_value = "abc"
        # 写断言
        self.assertEqual(acutal_value, expect_value, "实际结果和预期结果不一致")
    def tearDown(self):
        self.obj1 = None

    @classmethod
    def tearDownClass(self):
        pass

demo06_mtiplication.py文件如下:

import unittest
from demo01 import math
class E01_mutiplication(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        pass
    def setUp(self):
        # 实例化对象
        self.obj1 = math()
    # 乘法第一条正向用例,参数为数字
    def test_multiplication_t_01(self):
        acutal_value = self.obj1.multiplication(2,3)
        expect_value =6
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")

    # 乘法第二条正向用例,参数为字符串
    def test_add_multiplication_t_02(self):
        acutal_value = self.obj1.multiplication("a",3)
        expect_value ="aaa"
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")
    def tearDown(self):
        # 释放对象
        self.obj1 = None

    @classmethod
    def tearDownClass(self):
        pass

demo06_division.py文件如下:

import unittest
from demo01 import math
class E01_divison(unittest.TestCase):
    @classmethod
    def setUpClass(self):
        pass

    def setUp(self):
        # 实例化对象
        self.obj1 = math()
    # 除法功能的反向用例
    def test_divison_f_02(self):
        acutal_value = self.obj1.divison(10,0)
        expect_value = "error"
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")
    # 除法功能正向用例
    def test_divison_t_01(self):
        acutal_value = self.obj1.divison(10,5)
        expect_value =2
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")
    def tearDown(self):
        self.obj1 = None

    @classmethod
    def tearDownClass(self):
        pass

(2)创建maintest.py用来组织运行测试用例的
maintest.py如下:

'''这是主测试文件,用来组织运行测试用例的'''
# 导入包
import unittest
import os
from HTMLTestRunner import HTMLTestRunner

# 找到当前文件目录
filepath = os.path.dirname(__file__)
# 创建测试集
suite01 = unittest.defaultTestLoader.discover(filepath,pattern="demo06_*.py")
# 创建存储测试结果的html文件
with open(filepath+'/result.html',"wb") as f:
    # 创建运行器对象
    runner = HTMLTestRunner(f,verbosity=2,title="单元测试报告",description="第一次运行")
    # 执行测试用例,把测试集放入运行器进行运行
    runner.run(suite01)

(3)运行结果
在这里插入图片描述
此结果无法查看具体测试用例验证了什么,所以需要在测试用例上添加字符串''' xx功能xxx验证''',如下:

    def test_multiplication_t_01(self):
        '''乘法功能的数字相乘验证'''
        acutal_value = self.obj1.multiplication(2,3)
        expect_value =6
        # 写断言
        self.assertEqual(acutal_value,expect_value,"实际结果和预期结果不一致")

在这里插入图片描述
每次运行maintest.py文件,都会生成一个测试结果HTML文件,如何区分这些HTML文件呢?
答:在HTML命名中加入时间,则每一个HTML文件的名字都是独一无二的。
实现代码如下:

# "%Y-%m-%d %H_%M_%S"是正确写法,而"%Y-%m-%d %H:%M:%S"是错误写法!!!
# 规定html文件加入时间
filename = time.strftime("%Y-%m-%d %H_%M_%S") + r".html"
# print(filename)
# 找到当前文件目录
path = os.path.dirname(__file__)+ r"/"
# 得到文件目录
filename = path + filename

综上,maintest.py文件,如下代码:

'''这是主测试文件,用来组织运行测试用例的'''
# 导入包
import unittest
import os
from HTMLTestRunner import HTMLTestRunner
import time
# 找到当前文件目录
filepath = os.path.dirname(__file__)
# 创建测试集
suite01 = unittest.defaultTestLoader.discover(filepath,pattern="demo06_*.py")

'''创建存储测试结果的html文件'''
# 规定html文件加入时间
filename = time.strftime("%Y-%m-%d %H_%M_%S") + r".html"
# 找到当前文件目录
path = os.path.dirname(__file__)+ r"/"
# 得到文件目录
filename = path + filename
# "%Y-%m-%d %H_%M_%S"是正确写法,而"%Y-%m-%d %H:%M:%S"是错误写法!!!

# 创建测试结果
with open(filename,"wb") as f:
    # 创建运行器对象
    runner = HTMLTestRunner(f,verbosity=2,title="单元测试报告",description="第一次运行")
    # 执行测试用例,把测试集放入运行器进行运行
    runner.run(suite01)

4.4 自动化测试项目结构

public:里面放一些公共模块。
test_cases:里面放测试用例文件。
test_datas:测试数据,如csv文件,excel文件。
test_reports:测试结果报告。
test_conf:项目配置文件。
test_logs:测试日志文件。
attachment:附件。
test.py:主测试类。
在这里插入图片描述

4.5 Unittest框架下的数据驱动测试

自动化测试过程中,绝大部分都会采用测试脚本和测试数据分离的设计,有利于降低维护成本和迁移成本和提高效率。
存储数据可以放在脚本内数据类型存储,如:字典、列表等。
脚本外使用csv格式文件、excel文件、配置文件和数据库存储数据。

4.5.1 数据驱动测试(DDT)概述

ddt全称:data driver test
@ddt:用于装饰类
@data:用于装饰方法

数据驱动,指在自动化测试中处理测试数据的方式。
通常测试数据与功能函数分离,存储在功能函数的外部位置。在自动化测试运行时,数据驱动框架会读取数据源中的数据,把数据作为参数传递到功能函数中,并会根据数据的条数多次运行同一个功能函数。
数据驱动的数据源可以是函数外的数据集合,如:csv文件、Excel表格、txt文件,以及数据库等。

ddt的作用:
1.可以循环读取文件中的数据,用来做接口自动化数据驱动测试
2.可以配合xlutils,xlrd,xlwt读写.xls格式文件,数据回写
3.可以配合openpyxl读写.xlsx格式文件,数据回写

为什么要做数据驱动?
1.数据驱动能够减少重复代码
没有数据驱动时,并且同一个功能函数存在多个测试数据,你只能多次调用这个功能函数。另外一旦某一个测试数据有更改/删除,你需要在函数调用里去更改相应的测试数据,非常不方便。但有了测试驱动时,无须进行多次调用,而且当测试数据发生改变时,仅需要更改数据源文件的数据就可以了。
2.数据所属的测试用例失败,不会影响到其他测试数据对应的测试用例。

4.5.2 QQ注册页面实战

QQ注册页面实战自动化实现:
1.打开QQ注册页面的首页
2.输入合法的昵称
3.输入合法的密码
4.输入合法的手机号码
5.点击【发送验证码】按钮
6.输入正确验证码
7.点击“我已阅读并同意相关服务条款和隐私政策”单选框
8.点击【立即注册】按钮
在这里插入图片描述
测试用例数据可存放在脚本内或脚本外的excel文件。
(1)在脚本内存储测试数据
要求:在unittest框架下,运用ddt和data模块进行数据驱动,执行测试用例。
第一步,在unittest类下创建列表字典数据,如下代码:

    dicData = [
        {"username": "十旬叶大叔", "password": "12345678a", "phonenumber": "15016307409", "vcode": "000000"},
        {"username": "十旬叶大叔", "password": "12345678@", "phonenumber": "15016307409", "vcode": "000000"},
        {"username": "十旬叶大叔", "password": "1234567a@", "phonenumber": "15016307409", "vcode": "000000"},
    ]

第二步,导入ddt模块,实现数据驱动,
在这里插入图片描述
点击【+】,搜索data,ddt安装好模块
如下代码:

from ddt import ddt,data

常见错误:点击下方链接
点击链接!!!

第三步,使用ddt修饰,修饰unittest类,如下代码:

# 创建类,继承TestCase类
@ddt
class E01(unittest.TestCase):

第四步,使用data修饰测试用例,将数据以参数的形式传入测试用例,如下代码:

    #第一条测试用例
    @data(*dicData)
    def test_t_1(self,para):

最后,在unittest下,运用ddt和data模块,执行测试用例,如下代码:

# 导入自动化包
from selenium import webdriver
import time
import os
# 导入单元测试框架
import unittest
from ddt import ddt,data

# 创建类,继承TestCase类
@ddt
class E01(unittest.TestCase):
    # 测试用例的数据
    dicData = [
        {"username": "十旬叶大叔", "password": "12345678a", "phonenumber": "15016307409", "vcode": "000000"},
        {"username": "十旬叶大叔", "password": "12345678@", "phonenumber": "15016307409", "vcode": "000000"},
        {"username": "十旬叶大叔", "password": "1234567a@", "phonenumber": "15016307409", "vcode": "000000"}
    ]
    # setUpClass方法需要用修饰器
    @classmethod
    def setUpClass(self):
        pass
    def setUp(self) -> None:
        pass
    #第一条测试用例
    @data(*dicData) 
    def test_t_1(self,para): # data中的数据以参数para形式传入测试用例
        # 创建浏览器对象
        driver = webdriver.Chrome()
        # 打开网址
        driver.get("https://ssl.zc.qq.com/v3/index-chs.html")
        time.sleep(2)
        # 在昵称文本框输入合法字符
        driver.find_element_by_id("nickname").send_keys(para["username"])
        time.sleep(2)
        # 输入有效密码
        driver.find_element_by_id("password").send_keys(para["password"])
        time.sleep(2)
        # 输入有效手机号码
        driver.find_element_by_id("phone").send_keys(para["phonenumber"])
        time.sleep(2)
        # 点击【发送验证码】按钮
        driver.find_element_by_id("send-sms").click()
        time.sleep(2)
        # 输入验证码
        driver.find_element_by_xpath('//*[@id="code"]').send_keys(para["vcode"])
        time.sleep(2)
        # 点击“同意协议”
        driver.find_element_by_xpath('/html/body/div[3]/div[2]/div[1]/form/div[8]/label/img[2]').click()
        time.sleep(2)
        # 点击【立即注册】按钮
        driver.find_element_by_id("get_acc").click()
        time.sleep(2)
        driver.quit()
    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass
if __name__ == '__main__':
    unittest.main()

执行成功!

(2)在脚本外存储测试数据
第一步,将测试用例数据存储在excel文件中,如下图:
在这里插入图片描述

第二步,由于ddt数据驱动中的数据必须是列表字典的形式,所以要把excel文件中的数据转换wie列表字典的形式,如下方法代码:

import xlrd
class ExcelUtil():
    # __init__ 构造方法
    def __init__(self,excelPath,sheetName = "Sheet1"):
        self.data = xlrd.open_workbook(excelPath)
        self.table = self.data.sheet_by_name(sheetName)
        # 获取第一行作为key值
        self.keys = self.table.row_values(0)
        # 获取总行数
        self.total_row = self.table.nrows
        # 获取总列数
        self.total_col = self.table.ncols
        # 将数据转化为列表字典的形式
    def dic_data(self):
        if self.total_row <= 1:
            print("总行数小于1")
        else:
            r = [] #定义一个空列表
            j = 1
            for i in range(self.total_row-1): #按行读取
                s = {}# 定义一个空字典
                # 从第二行取values值
                values =self.table.row_values(j)
                for x in range(self.total_col): #在一行中读取列数据
                    s[self.keys[x]] = values[x] #通过键找到值
                r.append(s) #将字典添加到列表中
                j = j + 1
            return r
if __name__ == '__main__':
    filepath = r"./Data/abc.xlsx"
    # 实例化类,创建对象data1
    data1 = ExcelUtil(filepath)
    # 对象data1调用dic_data()方法
    data = data1.dic_data()
    print(data)
    # for i in data:
    #     print(i)

第三步,在执行测试用例的py文件中,调用上述方法,如下代码:

# 导入自动化包
from selenium import webdriver
import time
# 导入单元测试框架
import unittest
from ddt import ddt,data
# 将excel数据转化为列表字典的方法导入
from excel_turnto_listdic import ExcelUtil
import warnings
# 创建类,继承TestCase类
@ddt
class E01(unittest.TestCase):
    '''测试用例的数据'''
    # 实例化类,对象为obj1
    obj1 = ExcelUtil(r"./Data/abc.xlsx")
    # obj1对象调用dic_data()方法,将结果存储在dicData
    dicData = obj1.dic_data()
    # setUpClass方法需要用修饰器
    @classmethod
    def setUpClass(self):
        warnings.simplefilter('ignore', ResourceWarning)

    def setUp(self) -> None:
        pass
    #第一条测试用例
    @data(*dicData)
    def test_t_1(self,para): # data中的数据以参数para形式传入测试用例
        # 创建浏览器对象
        driver = webdriver.Chrome()
        # 打开网址
        driver.get("https://ssl.zc.qq.com/v3/index-chs.html")
        time.sleep(2)
        # 在昵称文本框输入合法字符
        driver.find_element_by_id("nickname").send_keys(para["username"])
        time.sleep(2)
        # 输入有效密码
        driver.find_element_by_id("password").send_keys(para["password"])
        time.sleep(2)
        # 输入有效手机号码
        driver.find_element_by_id("phone").send_keys(para["phonenumber"])
        time.sleep(2)
        # 点击【发送验证码】按钮
        driver.find_element_by_id("send-sms").click()
        time.sleep(2)
        # 输入验证码
        driver.find_element_by_xpath('//*[@id="code"]').send_keys(para["vcode"])
        time.sleep(2)
        # 点击“同意协议”
        driver.find_element_by_xpath('/html/body/div[3]/div[2]/div[1]/form/div[8]/label/img[2]').click()
        time.sleep(2)
        # 点击【立即注册】按钮
        driver.find_element_by_id("get_acc").click()
        time.sleep(2)
        driver.quit()
    def tearDown(self) -> None:
        pass

    @classmethod
    def tearDownClass(cls) -> None:
        pass
if __name__ == '__main__':
    unittest.main()

上述代码中的warnings.simplefilter('ignore', ResourceWarning)作用是忽视警告。详细点击下方链接:
点击此链接!!!
到此,在unittest框架下,运用ddt和data模块进行数据驱动,测试用例数据存储在脚本外,执行测试用例,成功!

5 pytest单元测试框架

单元测试框架是指在软件开发过程中,针对软件的最小单位(函数、方法)进行正确性的检查测试。
在java中有junit和testing单元测试框架,在python中有unittest和pytest单元测试框架。
单元测试框架主要做什么?
测试发现。从多个文件里面去找到我们测试用例。
测试执行。按照一定的顺序和规则去执行。
测试判断。通过断言判断实际结果和预期结果的差异。
测试报告。统计测试进度、耗时和通过率,生成测试报告。

单元测试框架和自动化测试框架有什么关系?
单元测试框架只是自动化测试框架的一小部分。
自动化测试框架包括单元测试框架、pom设计模式、数据驱动、全局配置文件的封装、日志监控、selenium和requrest二次封装、断言、报告邮件等等。

5.1 pytest简介

pytest是一个成熟的python的单元测试框架,比unittest更灵活。
pytest可以和selenium,requests,appium结合实现web自动化、接口自动化和app自动化。
pytest可以实现测试用例的跳过以及reruns失败用例重试。
pytest可以和allure生成非常美观的测试报告。
pytest可以和Jenkins持续集成。
pytest有很多非常强大的插件,这些插件能够实现很多的操作,如下表:

插件描述
pytest单元测试框架
pytest-html生成html格式的自动化测试报告
pytest-xdist测试用例分布式执行,多CPU分发
pytest-ordering用于改变测试用例的执行顺序
pytest-rerunfailures用例失败后重跑
allure-pytest用于生成美观的测试报告

5.2 默认测试用例的命名规则

模块名。全小写,多个英文之间用_隔开。
类名。首字母大写。
方法名。全小写,多个英文之间用_隔开。

模块名必须以test_开头或_test结尾。
测试类必须以Test开头,并且不能有init方法。
测试方法必须以test开头。
如下图所示:
在这里插入图片描述
模块名为test_user.py,测试类为Testuser,测试方法为test开头,有test_03和test_04。

5.3 测试用例的运行方式

测试用例的运行方式有main函数模式、命令行模式和ini配置模式。

5.3.1 main函数模式

(1)运行所有测试用例。pytest.main()
(2)指定运行模块。
(3)指定目录。
(4)通过nodeid指定用例运行。modeid由模块名,分隔符::,类名,方法名,函数名组成。

5.3.2 命令行模式

(1)运行所有测试用例。pytest
(2)指定运行模块。
(3)指定目录。
(4)通过nodeid指定用例运行。modeid由模块名,分隔符::,类名,方法名,函数名组成。

mian()方法和命令行里面的参数,如下表格:

参数描述
-s输入调试信息。如:打印信息等等
-v显示更详细的信息,文件名,用例名等等
-n多线程或分布式运行测试用例
-x表示只要有一个失败用例报错,就停止测试
-maxfial出现N个测试用例失败,就停止测试
--html=report.html生成html的测试报告
- k根据测试用例的部分字符串指定测试用例,可以使用and,or

实操例子:
建立interface_testcase文件夹和web_testcase文件夹。web_testcase文件夹有test_goods.py模块和test_login.py模块。test_login.py模块下有TestLogin类,该类下面有test_01()方法。test_goods.py模块下有Testgoods类,该类中有test_02()方法。
interface_testcase文件夹下有test_user.py模块,test_user.py模块下有test_04()方法和Testuser类,该类中有test_03()方法。
all.py模块用于组织运行模块和测试用例。
demand.txt用于一次性下载第三方包。
文件结构如下图:
在这里插入图片描述
test_login.py模块代码如下:

class TestLogin:
    def test_01(self):
        print("web_testcase01通过")

test_goods.py模块代码如下:

class Testgoods:
    def test_02(self):
        print("web_testcase02通过")

test_user.py模块代码如下:

import pytest
def test_04():
    print("interface_testcase04通过")

class Testuser:
    def test_03(self):
        print("interface_testcase03通过")

首先,下载有关包,在demand.txt输入如下:

pytest
pytest-html
pytest-xdist
pytest-ordering
pytest-rerunfailures
allure-pytest

在PyCharm编辑器的Terminal窗口输入:pip install -r .\demand.txt
在这里插入图片描述
实现一次性下载多个包。

在all.py运行所有的测试用例,代码如下:

import pytest
if __name__ == '__main__':
    # 执行文件夹下所有模块的测试用例
	pytest.main()

此外,还可以在Terminal窗口下输入命令:pytest,执行结果如下图:
在这里插入图片描述

此方法注意文件路径。当前文件路径为:D:\Neusoft\test\test02\demo>

在all.py运行web_testcase文件夹下所有的测试用例,如下代码:

import pytest
if __name__ == '__main__':
    # 执行web_testcase文件夹所有的测试用例
    pytest.main(["-vs","./web_testcase"])

此外,还可以在Terminal窗口下输入命令:pytest,执行结果如下图:
在这里插入图片描述

在all.py运行web_testcase文件夹下test_user.py模块中test_04的的测试用例,如下代码:

import pytest
if __name__ == '__main__':
    # 执行文件夹下模块中的测试用例
    pytest.main(["-vs","./interface_testcase/test_user.py::test_04"])

此外,还可以在Terminal窗口下输入命令:pytest ./interface_testcase/test_user.py::test_04,执行结果如下图:
在这里插入图片描述

在all.py运行web_testcase文件夹下test_user.py模块中Testuser类例的test_03测试用例,如下代码:

import pytest
if __name__ == '__main__':
# 执行文件夹下模块中类里的测试用例
	pytest.main(["-vs","./interface_testcase/test_user.py::Testuser::test_03"])

此外,还可以在Terminal窗口下输入命令:pytest ./interface_testcase/test_user.py::Testuser::test_03,执行结果如下图:
在这里插入图片描述

多线程执行web_testcase文件下的测试用例,代码如下:

import pytest
if __name__ == '__main__':
    # 多线程执行web_testcase文件夹的测试用例
    pytest.main(["-vs","./web_testcase","-n=2"])

运行效果如下:
在这里插入图片描述
此外,还可以在Terminal窗口下输入命令:pytest -vs .\web_testcase\ -n 2 ,执行结果如下图:
在这里插入图片描述
重新执行没有通过的测试用例
在test_user.py模块下的test_03测试用例添加错误的断言,如:assert 1 == 2
在all.py运行interface_testcase文件夹下的所有测试用例,如下代码:

import pytest
if __name__ == '__main__':
    # 重新执行没有通过的测试用例
    pytest.main(["-vs","./interface_testcase","--reruns=2"])

此外,还可以在Terminal窗口下输入命令:pytest -vs .\interface_testcase\test_user.py --reruns 2,执行结果如下图:
在这里插入图片描述

出现一个测试用例不通过,测试则停止。使用x参数。
在all.py运行interface_testcase文件夹test_user.py模块的测试用例,如下代码:

import pytest
if __name__ == '__main__':
    pytest.main(["-vs","./interface_testcase/test_user.py","-x"])

此外,还可以在Terminal窗口下输入命令:pytest -vs .\interface_testcase\test_user.py -x ,执行结果如下图:
在这里插入图片描述

出现两个测试用例不通过,测试停止。使用maxfail参数。

import pytest
if __name__ == '__main__':
    pytest.main(["-vs","./interface_testcase/test_user.py","--maxfail=2"])

此外,还可以在Terminal窗口下输入命令:pytest -vs .\interface_testcase\test_user.py --maxfail 2,执行结果如下图:
在这里插入图片描述
执行带“test”的测试用例。
在all.py运行带“test”的测试用例,代码如下:

import pytest
if __name__ == '__main__':
    pytest.main(["-vs","-k test"])

此外,还可以在Terminal窗口下输入命令pytest -vs -k test,执行结果如下图:
在这里插入图片描述

5.3.3 pytest.ini配置文件

运行的规则:不管是主函数的模式运行,还是命令行模式运行,都需要读取pytest.ini配置文件
(1)pytest.ini配置文件必须放在项目的根目录
在项目根目录新建pytest.ini,如下图:
在这里插入图片描述
pytest.ini文件内容如下:

[pytest]
addopts = -vs
testpaths =  ./
python_files = test*.py
python_classes = Test*
python_functions = test

上述代码分析:采用 vs方式输入结果,默认打开路径是根目录,测试模块以teat开头,测试类以Test开头,测试用例以test开头。

(2)在web_testcase文件夹下新建aa.py模块,该模块里的代码如下:

class Test01:
    def test_06(self):
        print("interface_testcase06通过")

(3)在all.py运行,如下代码:

import pytest
if __name__ == '__main__':
    pytest.main()

分析上述代码:因为修改pytest.ini文件的默认参数,所以路径为根目录下的web_testccase文件夹,测试模块以a开头。

结果如下图:
在这里插入图片描述

5.4 执行测试用例的顺序

unittest默认采用ASCII的大小来执行测试用例。
pytest默认从上到下来执行测试用例。
改变默认的执行顺序可以使用装饰器和ordering包。

修改test_user.py模块测试用例的执行顺序,test_05测试用例第一个执行,test_03测试用例第二个执行,代码如下:

import pytest

def test_04():
    print("interface_testcase04通过")

class Testuser:
    @pytest.mark.run(order=2)
    def test_03(self):
        print("interface_testcase03通过")

    @pytest.mark.run(order=1)
    def test_05(self):
        print("interface_testcase05通过")

运行结果如下图:
在这里插入图片描述

5.4.1 分组执行

分组执行主要用于冒烟测试用例执行、分模块执行、接口测试用例执行。

实操例子:
在interface_testcase文件夹下的test_03测试用例出现冒烟,在web_testcase文件夹下的test_01测试用例出现冒烟。
第一,在测试用例前写上装饰器,如下代码:

    @pytest.mark.smoke
    def test_03(self):
        print("interface_testcase03通过")
        @pytest.mark.smoke
    def test_01(self):
        print("web_testcase01通过")

第二,在全局配置文件pytest.ini文件,添加如下内容:

markers =
            smoke:"冒烟测试用例"

第三,在all.py执行冒烟测试用例,如下代码:

import pytest
if __name__ == '__main__':
    pytest.main(["-m smoke"])

此外,还可以在Terminal窗口下输入命令pytest -m smoke,执行结果如下图:
在这里插入图片描述
注:还可以使用or进行多组执行。
修改pytest.ini文件内容,如下:
在这里插入图片描述

5.4.2 跳过测试用例

(1)无条件跳过
在测试用例上加装饰器
实操例子:跳过执行test_02测试用例
第一,在test_02测试用例上添加装饰器,如下:

    @pytest.mark.skip(reason="跳过执行test_02")
    def test_02(self):
        print("web_testcase02通过")

第二,直接在Terminal窗口输入命令:pytest,执行结果如下图:
在这里插入图片描述

(2)有条件跳过
在测试用例上加装饰器
实操例子:当a>18时,跳过执行test_02测试用例
第一,在test_02测试用例上添加装饰器和定义变量,如下:

import pytest
class Testgoods:
    a  = 20
    @pytest.mark.skipif(a > 18,reason="跳过执行test_02")
    def test_02(self):
        print("web_testcase02通过")

第二,直接在Terminal窗口输入命令:pytest,执行结果如下图:
在这里插入图片描述

5.5 前后置的处理

5.5.1 setup()/teardown()和setup_class()/teardown_class()

setup_class() 在执行所有的用例之前只运行一次。因此可以负责初始化的工作,如:创建日志对象,创建数据库的连接,创建接口的请求对象。

teardown_class()在所有的用例执行完之后只运行一次。因此可以负责扫尾的工作,如:销毁日志对象,取消数据库的连接,销毁接口的请求对象。

setup()在执行每个用例之前运行一次。因此可以负责执行测试用例之前初始化的代码,如:打开浏览器,输入网址。

teardown()在执行每个用例之后运行一次。因此可以复制每个测试用例之后的扫尾工作,如:关闭浏览器。

5.5.2 部分用例的前后置

使用@pytest.fixture()装饰器实现部分用例的前后置。

@pytest.fixture(scope = "",params = "",autouse = "",ids = "",name = "")

scope表示的是被@pytest.fixture标记的方法的作用域。参数有:function(默认),calss,module,package/seesion。
params。参数化。参数数据类型有列表([]),元组(()),字典列表([{},{},]),字典元组(({},{},{}))。
autouse=True,自动执行。默认autouse=False。
ids,当使用params参数化时,给每一个值设置一个变量名,很少使用。
name,表示的是被@pytest.fixture标记的方法取一个别名。

(1)scope
scope=“function”时,被@pytest.fixture标记的方法的作用域是函数,即函数内的代码就会被执行。

# 导入包
import pytest
# 添加装饰器
@pytest.fixture(scope="function") 
def tool(): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
    yield
    print("这是后置的方法")

class Testaad:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,tool):
        print("\n运行test_01测试用例")
    
    # 第二条测试用例
    def test_02(self):
        print("\n运行test_02测试用例")
    def teardown(self):
        print("\n运行teardown()方法")
    def teardown_class(self):
        print("\n运行teardown_class()方法")

运行结果,如下图:
在这里插入图片描述
分析上述代码和运行结果:
前置方法在执行测试用例之前运行。后置方法在yield之后,后置方法在执行测试用例之后马上运用。
前后置方法以参数的形式传入测试用例方法中。
同时yield后可以接返回值,在测试用例(方法)返回该函数的返回值。

def tool(): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
    yield "1"
    print("这是后置的方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,tool):
        print("\n运行test_01测试用例")

运行结果,如下:

这是前置的方法
运行test_01测试用例1
PASSED这是后置的方法

除此之外,return也可以返回值。需要在测试用例(方法)中拼接夹具(这里是tool)的返回值,是在运行测试用例中返回的,不是在测试用例介绍返回的。

def tool(): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
   	return "1"

    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,tool):
        print("\n运行test_01测试用例"+tool)

运行结果,如下:

这是前置的方法
运行test_01测试用例1
PASSED

yield VS return
yield是生成器,返回一个对象,对象中可以有多个值,yield后面可以接代码。
return返回一个值,renturn后面不能接代码。
yield与return不能同时使用。

class级别的前后置
在每个类的前后执行一次。

@pytest.fixture(scope="class")

调用方法。在类加上装饰器,xx为夹具名,如下:

@pytest.mark.usefixtures("xx")

module级别的前后置
在每个模块的前后执行一次,和setup_module,teardown_module效果一样。
定义并直接调用,如下:

@pytest.fixture(scope="module")

package/seession级别,一般和conftest.py文件一起使用,使用频率高。
名称是固定的,conftest.py,主要用于单独存放fixture夹具(固件)的。
级别为package时,可以在多个包甚至多个py文件里面共享前后置。例如:模块的共性,登录等等。
conftest.py文件里面的fixture不需要导包可以直接使用。
conftest.py可以多个嵌套conftest.py。
作用:
出现重复日志,初始化一次日志对象,避免日志重复。
连接数据库。
关闭数据库。
等一系列一次使用的对象。

conftest.py为function级别时优先级高于setup/teardown
conftest.py为class级别时优先级高于setup_class/teardown_class
conftest.py为package级别时优先级高于setup_module/teardown_module

(2)autouse
autouse=True的作用:每一条测试用例都执行被@pytest.fixture标记的方法。效果和使用setup(),teardown()一致。
例子:在@pytest.fixture()中添加autouse=True参数及其参数值。

# 导入包
import pytest
# 添加装饰器
@pytest.fixture(scope="function",autouse=True)
def tool(): #前后置方法,被pytest.fixture()修饰
    # return request.param
    print("这是前置的方法")
    yield
    print("这是后置的方法")

class Testaad:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self):
        print("\n运行test_01测试用例")
    # 第二条测试用例
    def test_02(self):
        print("\n运行test_02测试用例")
    def teardown(self):
        print("\n运行teardown()方法")
    def teardown_class(self):
        print("\n运行teardown_class()方法")

运行结果如下图:
在这里插入图片描述
分析上述代码和运行结果:
添加了autouse=True每条测试用例都执行前后置方法。autouse参数的默认值为False。
如果在前后置方法以参数形式传入测试用例,则只有该测试用例使用了前后置方法。

(3)params
fixture装饰器中添加参数及该参数值params=["wang","xin","ling"]

# 导入包
import pytest
# 添加装饰器
@pytest.fixture(scope="function",params=["wang","xin","ling"]) 
def tool(): #前后置方法,被pytest.fixture()修饰
    # return request.param
    print("这是前置的方法")
    yield
    print("这是后置的方法")
class Testaad:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,tool):
        print("\n运行test_01测试用例")
        print("--------------"+str(tool))
    # 第二条测试用例
    def test_02(self):
        print("\n运行test_02测试用例")
    def teardown(self):
        print("\n运行teardown()方法")
    def teardown_class(self):
        print("\n运行teardown_class()方法")

运行结果如下:

test_add.py::Testaad::test_01[wang]
运行setup_class()方法

运行setup()方法
这是前置的方法

运行test_01测试用例
--------------None
PASSED这是后置的方法

运行teardown()方法

test_add.py::Testaad::test_01[xin]
运行setup()方法
这是前置的方法

运行test_01测试用例
--------------None
PASSED这是后置的方法

运行teardown()方法

test_add.py::Testaad::test_01[ling]
运行setup()方法
这是前置的方法

运行test_01测试用例
--------------None
PASSED这是后置的方法

运行teardown()方法

test_add.py::Testaad::test_02
运行setup()方法

运行test_02测试用例
PASSED
运行teardown()方法

运行teardown_class()方法

分析上述代码和运行结果:
第一条测试用例执行了3次,但是每次执行用例都有不同,第一次第一条用例后是[wang],第二次第一条用例后是[xin],第三次第一条用例后是[ling]。都是params参数中的参数值,params参数中的参数值以列表的形式存储。
存在的问题:无法输出tool值,上述结果为None。
解决办法:在前后置方法中用request接收params的参数值,返回接收的值,如下代码:

@pytest.fixture(scope="function",params=["wang","xin","ling"]) 
def tool(request): #前后置方法,被pytest.fixture()修饰
    return request.param
class Testaad:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,tool):
        print("\n运行test_01测试用例")
        print("--------------"+str(tool))

运行结果如下:

function_testcase/test_add.py::Testaad::test_01[wang] 
运行setup_class()方法

运行setup()方法

运行test_01测试用例
--------------wang
PASSED
运行teardown()方法

function_testcase/test_add.py::Testaad::test_01[xin] 
运行setup()方法

运行test_01测试用例
--------------xin
PASSED
运行teardown()方法

function_testcase/test_add.py::Testaad::test_01[ling] 
运行setup()方法

运行test_01测试用例
--------------ling
PASSED
运行teardown()方法

function_testcase/test_add.py::Testaad::test_02 
运行setup()方法

运行test_02测试用例
PASSED
运行teardown()方法

运行teardown_class()方法

分析上述代码和运行结果:
tool()方法,传入request参数,返回params的参数值。
tool()方法以参数的形式传入测试用例,使用str()进行强制类型转换输出tool()方法中的值。

注:
yieldreturn不能连用,无法接收到request中的值,出现None。
所以不用return语句,只使用yield,如下代码:

def tool(request): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
    yield request.param
    print("这是后置的方法")

部分运行结果,如下图:
在这里插入图片描述
returnyield都有返回的作用,return语句后不能是代码,yield后可以是代码。

(4)ids 很少使用,略。

(5)name
作用:给被修饰的方法取别名。
当取了别名之后,原来的名称用不了。

例子:给tool()方法取别名,function01

# 导入包
import pytest
# 添加装饰器
@pytest.fixture(scope="function",params=["wang","xin","ling"],name="function01") #
def tool(request): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
    yield request.param
    print("这是后置的方法")


class Testaad:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_01(self,function01):
        print("\n运行test_01测试用例")
        print("--------------"+str(function01))

分析上述代码:
传入测试用例的参数都是取别名之后tool()方法,为fuction01。

5.5.3 全局前置应用

使用conftest.py和@pytest.fixture()实现全局的前置应用。
conftest.py文件是单独存放的一个一个配置文件,名称不能更改。
用处:在conftest.py文件写被fixture()修饰的函数,其他的py文件都可以使用该函数。
原则上,conftest.py需要和运行的用例放到同一层目录,并且不需要使用import导包的操作。

体现conftest.py的用处
在function_testcase文件夹下新建conftest.py,如下代码:

import pytest
# 添加装饰器
@pytest.fixture(scope="function") #
def tool(): #前后置方法,被pytest.fixture()修饰
    print("这是前置的方法")
    yield
    print("这是后置的方法")

在function_testcase文件夹下新建test_division.py,把fixture修饰的方法以参数的形式传入第三条用例中,如下代码:

# 导入包
import pytest

class Testdivision:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第一条测试用例,前后置方法以参数形式传入
    def test_03_division(self,tool):
        print("\n运行test_03测试用例")
    def teardown(self):
        print("\n运行teardown()方法")
    def teardown_class(self):
        print("\n运行teardown_class()方法")

部分运行结果如下:
在这里插入图片描述
分析上述代码和运行结果:
被fixture修饰的函数放在conftest.py文件中。第三条测试用例放在test_division.py文件中,没有导包,一样可以运行conftest.py中的tool()函数,这就是conftest.py的用处。

conftest.py文件可以和测试用例不同一级目录,如下例子
conftest.py文件还可以放在测试用例的上一层级,如下图:
在这里插入图片描述

要求:
test_subtratuion.py文件中的第五条测试用例,使用user_tool()方法和all_tool()方法(user_tool()方法在user文件夹下的conftest.py文件中,all_tool()方法在function_teatcase文件下的conftest.py文件中)。

在goods文件夹下新建conftest.py文件,代码如下:

import pytest
# 添加装饰器
@pytest.fixture(scope="function") #
def goods_tool(): #前后置方法,被pytest.fixture()修饰
    print("这是goods的前置的方法")
    yield
    print("这是goods后置的方法")

在goods文件夹下新建test_subtration.py,代码如下:

# 导入包
import pytest

class Testmutiplication:
    def setup_class(self):
        print("\n运行setup_class()方法")
    def setup(self):
        print("\n运行setup()方法")
    # 第五条测试用例,前后置方法以参数形式传入
    def test_05_subtration(self,goods_tool,all_tool):
        print("\n运行test_05测试用例")
    def teardown(self):
        print("\n运行teardown()方法")
    def teardown_class(self):
        print("\n运行teardown_class()方法")

function_teatcase文件下的conftest.py文件,代码如下:

import pytest
# 添加装饰器
@pytest.fixture(scope="function") 
def all_tool(): #前后置方法,被pytest.fixture()修饰
    print("这是全局的前置的方法")
    yield
    print("这是全局后置的方法")

执行部分结果如下图:
在这里插入图片描述

5.5.4 小结前后置的处理

setup(),teardown()作用于所有测试用例。
setup_class(),teardown_class()作用于所有类。
@pytest.fixture()通过参数可以作用于部分测试用例的前后置或所有测试用例的前后置。
conftest.py和@pytest.fixture()结合使用,作用于全局的前后置。

5.6 生成html报告

第一,在根目录新建存放html报告的文件夹,如下图:在这里插入图片描述

第二,在全局配置文件添加内容,如下:

addopts = -vs --html ./htmlreports/report.html

注意:addopts内的参数用空格隔开,html参数前为--两条横杠,其后为存放目录。
第三,直接在Terminal窗口输入命令:pytest,打开报告,如下图:
在这里插入图片描述

5.7 生成allure报告

1 下载 解压 配置环境变量
下载网址:https://github.com/allure-framework/allure2/releases
下载zip格式,如下图:
在这里插入图片描述
解压结果如下图:
在这里插入图片描述
配置系统环境变量,把allure的bin目录放在系统环境变量下,如下图:
在这里插入图片描述

验证系统环境变量是否配置成功
在dos窗口输入命令:allure --version

在这里插入图片描述

在pychram的Terminal窗口输入命令:allure --version
在这里插入图片描述
若出现以下报错,直接重启电脑。

allure : 无法将“allure”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
+ allure --version + ~~~~~~ + CategoryInfo : ObjectNotFound: (allure:String)

2 生成json的临时报告

addopts = -vs --alluredir ./temp

在这里插入图片描述
在all.py运行,得到结果如下:
在这里插入图片描述
3 得出allure报告
在all.py文件导入os模块,并输入以下代码:

import pytest
import os
if __name__ == '__main__':
    pytest.main()
    os.system("allure generate ./temp -o ./allure-reports --clean")

分析上述代码:
allure generate 生成allure
./temp 存放json文件的目录
-o 输出output
./allure-reports 生成allure报告的目录
--clean 清空./allure-reports目录原来的报告

在项目中出现allure-reports文件夹,如下图:
在这里插入图片描述

运行结果如下:
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐