Python pytest框架简介及使用指南

1、认识pytest框架

1、搭建自动化框架的思路与流程

1、搭建自动化测试框架的思路和流程,任意测试手段流程都是一致的:手工测试、自动化测试、工具测试

  1. 手工测试:熟悉业务 —— 写用例 —— 执行用例并记录结果 —— 生成测试报告
  2. 自动化测试:熟悉业务 —— 写自动化用例(来自于手工测试用例,格式转化为代码) —— 代码表达用例 —— 代码收集测试用例 —— 执行测试用例 —— 代码生成测试报告

2、测试框架(是一种技术栈):是一个现成的框架,区别于自动化测试框架(需要借助测试框架+基于项目实现搭建的针对项目的框架),介绍一下使用最多的2个框架:

  1. unittest:内置库,不需要安装,不能自动发现测试用例,手动收集用例
  2. pytest:第三方库,需要安装导入使用:智能自动收集所有用例,使用更广泛

                – 安装: pip install pytest

                – 导包:import  pytest

        3. unittest和pytest都是单元测试框架,都可以用来编写测试用例,运行用例,生成报告,实现测试前置和后置等

2、pytest语法

1、编写用例的2种规则(为了确保可以自动识别测试用例)

1、 使用函数格式编写时,函数名字以test_开头才会被识别为pytest测试用例的方法,不然就是普通的函数

2、 测试类的形式 编写测试用例,类名Test开头类里面方法 test_开头,才会被识别为pytest测试用例的方法

注意:当pytest识别出这个是测试用例后,这个函数前方会有一个小绿三角,点击小绿三角也可以执行用例,如下图所示:

如果没有小三角,可以这样做:File – setting – Tools – Python Integrated Tools – testing配置pytest

2、运行pytest用例

1、运行单个模块用例,右键运行,点击三角符号运行

2、完整项目框架里每个模块单独调用一个py文件管理,需要收集所有模块的用例,一起执行可以在项目的跟目录下创建一个main.py,会运行这个项目底下所有的用例,其原理是:不同模块,不同目录,主要符合命名规则的都会拿过来执行(范围:rootdir)

3、执行自动化测试用例
1、使用 pytest.main()执行所以用例

在项目最外层创建main.py文件,文件内容如下,它会自动在这个文件所在目录收集符合命名规则的文件,符合规则如下:

  1. 文件名字,以 test_开头、_test开头
  2. 用例名字:测试用例名字以 test开头,或者类以 Test开头 +test_开头的方法函数名字

注意:pytest用例执行搜索名字时,跟项目文件夹的名字无关

'''
main.py
'''

import pytest
pytest.main()  # 收集所有符合pytest语法命名的测试用例
2、执行部分用例的方式(3种方法)
  1. 修改文件和用例方法的名称
  2. 指定目录和文件执行,加参数控制,例如:pytest.main([r"testcase\test_01_demo.py"])表示仅执行test_01_demo.py文件
  3. 加标签【类比手工测试用例的优先级: P1 P2 P3 P4 (important critical major)  high  medium low】, 加参数过滤用例
       – 用例定义的加一个标签,pytest自带: 用装饰器形式:@pytest.mark.p2
       – 执行的时候 加参数 -m 标签,如下举例:

使用装饰器标记如下:

class Testdemo:
    @pytest.mark.p1    # 添加标记
    def test_case02(self):
        assert 1 == 10

    @pytest.mark.p2    # 添加标记
    def test_case03(self):
        assert 10 > gen_ran()

    def test_case04(self):
        assert 20 < gen_ran()

执行时使用-m参数如下:

import pytest
pytest.main(["-m p1 or p2"])

3、pytest用例实战

目录如下:

文件内容:

'''
test_01_demo
'''
import random
import pytest
def test_case_01():
    # 在这里写测试用例
    excepted = 2
    actual = 1
    assert actual == excepted

def test_case_02():
    return random.randint(1,10)

def gen_ran():
    return random.randint(1,100)

ans = gen_ran()
print(ans)
def add():
    return 1+3

class Testdemo:
    @pytest.mark.p1
    def test_case02(self):
        assert 1 == 10

    @pytest.mark.p2
    def test_case03(self):
        assert 10 > gen_ran()

    def test_case04(self):
        assert 20 < gen_ran()
'''
test_02_demo
'''
import random
# 使用方法写测试用例
def test_case_01():
    # 在这里写测试用例
    excepted = 2
    actual = 1
    assert actual == excepted

def test_case_02():
    a = 10
    b = random.randint(20,100)
    assert a < b

def gen_ran():
    return random.randint(1,100)

ans = gen_ran()
print(ans)

def add():
    return 1+3

# 使用类来写测试用例
class Testdemo1:

    def test_case02(self):
        assert 1 == 10

    def test_case03(self):
        assert 10 < gen_ran()

    def test_case04(self):
        assert 20 < gen_ran()
'''
main.py 文件
'''
import pytest
pytest.main()

执行main文件后,查看执行结果:

在单个文件中运行后,可以准确查看用例执行效果,如下:

注意点:有时候运行文件后,不会出现执行结果的小窗口,解决方法如下:

删除之后,再运行就会出现小窗口了,还有问题,那么考虑重新启动pycharm。

2、使用allure生成测试报告

1、概述

allure是一个开源的、独立的展示测试报告的构建工具,可以支持各种语言框架执行测试用例,使用allure会先生成allure可解析的结果文件,从而再去生成具备可读性的html文件。 

官网:Allure Framework

安装包下载地址:https://repo.maven.apache.org/maven2/io/qameta/allure/allure-commandline/

备注:安装时,尽量不要放在有中文的磁盘、也不要放在层级目录太多的磁盘,需要配置环境变量,到bin目录即可。

2、安装使用步骤

1、安装

1、windows和mac下载的都是zip包

2、下载后,解压

3、配置环境变量,例如:D:\allure\allure-2.23.1\bin

4、在命令行中,输入: allure  –version 查看版本号,如果出现版本号那就安装成功了~、

2、使用

1、allure结合pytest生成测试报告,还需要再按转包给一个第三方库:pip install allure-pytest | pycharm

2、在main文件中添加加参数:"–alluredir=outputs/allure_reports",其中“–alluredir=”部门可以当做固定语法,而“outputs/allure_reports”部分是存放allure生成的文件,是给allure看的哦,我们看不懂。这个文件每次执行都会生成文件,因此,可以添加"–clean-alluredir"参数,清楚以前的文件

3、在这个测试用例文件的rootdir(一般是文件夹目录下)下执行:allure serve .\outputs\allure_reports\,即可生成测试报告(你最近一次执行用例的测试报告),如下演示:

看一下结果展示,查看结果时,不要退出这个进程,即不要输出 Ctrl + C:

点击后,可以看到详细信息~

测试报告只能看到当前的用例执行结果,如果要看连贯的,需要结合Jenkins去做~

3、数据驱动ddt

1、重复的数据用例如何执行

pytest的ddt(数据驱动)解决以下的问题:
1、多条用例被执行的 但是在pytest结果里显示多条用例结果
2、如果第一条用例数据执行失败了 后续的用例依然会再执行的。

举例:有一个列表数据,列表中有3个字典需要跑登录场景的测试用例:

datas = [{"name":"admin1","pwd":"123456","expect":"登录成功"},
         {"name":"lemon","pwd":"123456","expect":"用户名错误"},
         {"name":"admin","pwd":"1234","expect":"密码错误"}]

那么该如何运行它呢?

有2种方法,第1种是写一个方法,然后遍历这个列表取出每条数据,第2种是写3个方法,分别执行第1、2、3条数据,具体写法如下:

方式1写法:

'''
方式一:写一个方法,然后遍历这个字典
'''
def test_login(datas):
    for data in datas:
        result = login(data["name"],data["pwd"])
        assert result == data["expect"]

方式1结果:

方式二写法:

def test_login01():
    result = login(datas[0]["name"], datas[0]["pwd"])
    assert result == datas[0]["expect"]

def test_login02():
    result = login(datas[1]["name"],datas[1]["pwd"])
    assert result == datas[1]["expect"]

def test_login02():
    result = login(datas[2]["name"],datas[2]["pwd"])
    assert result == datas[2]["expect"]

方式二结果:

总结:方法2可以运行完所有的测试用例,但是过于繁琐,如果有成百上千条用例,那么需要写这么多的函数,实在不是最优解

2、高效执行重复数据的方法

使用数据驱动实现一个方法,执行多条测试数据,使用不同的用例,得到不同的测试结果

1、数据驱动方式一
@pytest.mark.parametrize("变量",测试用例数据)  # 用例的方法上面
def test_login_02(变量):  # 参数要跟上面的变量名字一致
    result = login(变量["name"],变量["pwd"])
    assert result == 变量["expect"]
 - 依次取到测试用例数据里的每一个元素,赋值给变量
 - 变量作为用例方法的参数,依次执行每一条测试用例
 - 直到所有用例都执行完了。
 - 注意: 变量引号括起来

实战:

@pytest.mark.parametrize("data",datas)
def test_login01(data):
    result = login(data["name"], data["pwd"])
    assert result == data["expect"]

结果:

2、数据驱动方式二
- 如果数据驱动写在方法上面, 那么只有这个方法可以使用这个数据驱动,另外的方法就不可以用;
- 如果数据驱动写在类上面,那么类下面的所有的方法都可以用。
@pytest.mark.parametrize("变量", 测试用例数据)
class TestLogin:
    def test_login_01(self,data):
        result = login(data["name"], data["pwd"])
        assert result == data["expect"]
    def test_login_02(self,data):
        result = login(data["name"], data["pwd"])
        assert result == data["expect"]

实战:

@pytest.mark.parametrize("data",datas)
class Testlogin():
    def test_login02(self,data):
        result = login(data["name"],data["pwd"])
        assert result == data["expect"]

    def test_login03(self,data):
        result = login(data["name"],data["pwd"])
        assert result == data["expect"]

结果:

总结:使用数据驱动很完美的解决了这个问题

4、夹具(fixture、conftest)

1、为什么引入夹具

在实际项目中,接口或者UI操作肯定不是独立存在的,接口依赖/接口关联其他数据;UI中,点击按钮输入内容之前打开网页等等,因此在自动化中经常要注意这几点:

1、前置条件

        1、接口测试中购物车操作之前必须要登录,登录操作是前提

        2、UI测试:点击按钮输入内容之前,需要打开浏览器页面,这也是前提

2、后置操作

        1、接口测试完成后,需要数据清理,登出

        2、UI测试完毕后,需要关闭浏览器

而这些前置和后置操作,在pytest中使用非常灵活,前置和后置统一称为 夹具,前置会在用例执行前执行,后置在用例执行后运行

2、认识pytest中的夹具

备注:夹具可以理解为一个函数

1、定义夹具
pytest定义夹具:在函数名上方添加 “@pytest.fixture()” 即是一个夹具,如:
import pytest

# 定义夹具
@pytest.fixture()
def setup_teardown():
    print("我是前置代码")
    print("我是后置代码")
2、使用夹具

调用夹具时,在用例方法中调用,直接在方法的括号中,填写夹具函数的名称即可调用,如下:

# 调用夹具

def test_01(setup_teardown):
    print("这是测试用例代码......")

查看结果:

3、区分前置和后置关键字 yield

使用yeild可以区分前置和后置,yield之前的代码是前置,之后的代码是后置,如果没有yield区分,那么代码都会认为是前置代码,使用yeild之后的夹具定义如下:

@pytest.fixture()
def setup_teardown():
    print("我是前置代码")
    yield
    print("我是后置代码")

测试夹具及其结果如下:

def test_case01(setup_teardown):
    print("这是测试代码")

测试结果如下,测试通过:

4、夹具中的返回值,写在yield后面

如果前置中有返回值需要传递给测试用例,那这些返回值可以添加在yield的后面,如果有多个返回值则用逗号隔开,如下定义:

@pytest.fixture()
def setup_teardown():
    print("我是前置代码")
    yield
    yield"123459","username","是否成功"
    print("我是后置代码")

将yeild后的参数进行接收和使用,举例如下:

def test_case01(setup_teardown):
    print("这是测试代码")
    data = setup_teardown
    print("输出yield的返回值类型:",data)  # 查看多个参数时的数据类型是什么,最终确定为元组
    pwd,name,expect = setup_teardown      # 接收这些参数
    print(pwd,"=========",name,"========",expect)

查看结果:

以上便是夹具的基本用法

3、在方法和类中使用夹具

1、夹具的默认属性function和扩展属性class

前言:夹具默认的作用域是方法,可以通过给夹具添加参数 scope=class,修改作用域为类范围的作用域,如下修改作用域:

@pytest.fixture(scope="class") #声明这个函数是一个夹具函数,是class级别的夹具
def setup_teardown():
    print("我是前置代码")
    yield"123459","username","是否成功"
    print("我是后置代码")
1、在方法中的使用
1、夹具的作用域为方法

夹具定义:

@pytest.fixture()
def setup_teardown():
    print("我是前置代码")
    yield"123459","username","是否成功"
    print("我是后置代码")

在方法中的使用:

def test_case01(setup_teardown):
    print("这是测试代码")
    data = setup_teardown
    print("输出yield的返回值类型:",data)
    pwd,name,expect = setup_teardown
    print(pwd,"=========",name,"========",expect)
2、夹具的作用域为类

夹具定义:

@pytest.fixture(scope="class") #声明这个函数是一个夹具函数,是class级别的夹具
def setup_teardown():
    print("我是前置代码")
    yield"123459","username","是否成功"
    print("我是后置代码")

在方法中的使用:

@pytest.mark.usefixtures("setup_teardown")  #类级别的夹具,在函数中调用时,可以写在括号中使用,这一行可有可无
def test_case01(setup_teardown):
    print("这是测试代码")
    pwd,name,expect = setup_teardown
    print(pwd,"=========",name,"========",expect)

备注:如果是类夹具,在方法中使用时,依然写在方法的括号中

2、在类中的使用
1、夹具的作用域为方法

夹具定义:

@pytest.fixture()
def setup_teardown():
    print("我是前置代码")
    yield"123459","username","是否成功"
    print("我是后置代码")

在类中使用:

# pytest用例类的形式
class TestDemo:
    """
    类的形式调用夹具 直接写在方法括号 ,有其他的参数 用逗号隔开就可以了
    如果类下面有多个方法,只有调用的这个夹具的方法才会执行前置和后置。== 默认的情况下
    """
    def test_01(self,setup_teardown):    #这个方法调用了夹具,夹具只在这个方法中生效
        print("这是测试用例1代码..")

    def test_02(self):
        print("这是测试用例2代码..")
2、夹具的作用域为类

夹具定义:

'''
定义类作用域的夹具
'''
@pytest.fixture(scope="class") #声明这个函数是一个夹具函数,是class级别的夹具
def setup_teardown():
    print("我是前置代码")
    yield  "123459","username","是否成功"
    print("我是后置代码")

在类中使用:

'''
有2种方式
方式一:类中的函数不使用夹具中的参数,那么直接在类上方添加@pytest.mark.usefixtures("setup_teardown") 即可  
方式二:类中的函数需要使用夹具中的参数,那么,在第一个函数的括号中加夹具的名称即可
'''
'''
方式一举例
'''
@pytest.mark.usefixtures("setup_teardown")   # 这样也不行,放在这里方法里取不到返回值
class TestDemo:
    """
    类的形式调用夹具 直接写在方法括号 ,有其他的参数 用逗号隔开就可以了
    如果类下面有多个方法,只有调用的这个夹具的方法才会执行前置和后置。== 默认的情况下
    """
    def test_case01(self):
        print("这是第一个类的函数: ",1+2)
      

    def test_case02(self):
        print("这是第二个类的函数:",2+2)


'''
方式二举例
'''
# @pytest.mark.usefixtures("setup_teardown")   # 可以舍弃,放在这里方法里取不到返回值
class TestDemo:
    """
    类的形式调用夹具 直接写在方法括号 ,有其他的参数 用逗号隔开就可以了
    如果类下面有多个方法,只有调用的这个夹具的方法才会执行前置和后置。== 默认的情况下
    """
    def test_case01(self,setup_teardown):
        print("这是第一个类的函数: ",1+2)
        pwd, name, expect = setup_teardown
        print(pwd, "=========", name, "========", expect)

    def test_case02(self,setup_teardown):
        print("这是第二个类的函数:",2+2)
        pwd, name, expect = setup_teardown
        print(pwd, "=========", name, "========", expect)

    def test_case03(self,setup_teardown):
        print("这是第二个类的函数:",3+2)
        pwd, name, expect = setup_teardown
        print(pwd, "=========", name, "========", expect)

总结:
夹具的作用域: 默认是函数级别的作用域,是在函数方法前后执行的。
"function"(default): 默认配置,所以夹具默认函数前后执行的。
– – 前置就会在方法用例执行之前执行前置
– 在类里面的方法执行完成之后,再执行后置
– 前置  用例方法   后置

"class": 定义为类级别的夹具
– 前置就会在整个类里面的用例执行之前执行前置
– 在类里面的所有方法都执行完成之后,再执行后置
– 前置   类里所有方法   后置

其他的级别我们不用: "module","package"`` or ``"session"。

扩展了解:–夹具另外一种调用的方式:可以不掌握,重点掌握在方法的括号里调用返回值。
@pytest.mark.usefixtures("setup_teardown")  # 调用夹具,但是这种不能直接用返回值
class TestDemo:

@pytest.mark.usefixtures("setup_teardown")
def test_case(setup_teardown):

4、共享夹具

如果测试用例和夹具不在同一个文件里,不可以直接调用的;但是可以导包使用,但是比较麻烦。因此引入夹具的共享: conftest文件管理夹具。这个夹具定义在conftest文件里 ,其他的模块可以自动发现并直接使用不需要导入。文件有2个要求:
1、文件名字必须是conftest 不能改
2、作用范围是 conftest文件所在目录下的所有用例可以自动发现,超过这个文件夹的范围不行了
   (不管是否有子文件夹 都可以发现)

如果有多个夹具,使用原则就是就近原则:
1、名字不一样,就按照名字区分
2、名字一样  就近原则选择:
 – 第一步:优先用例所在文件里找fixture,找到就用
 – 第二步: 第一步里没有找到,就去当前所在目录下conftest.py找。找到了就用
 – 第三步: 第二步里没有找到,就去【当前文件所在目录的上一级目录】 下conftest里找,找到了就用
 – 一直找到rootdir(根目录)截止,没有报错。

如下举例说明:

1、目录如下,那么在testcase目录下的文件都可以使用conftest中的夹具:

conftest文件内容:

import pytest

# 定义夹具
@pytest.fixture(scope="class") #声明这个函数是一个夹具函数,是class级别的夹具
def setup_teardown():
    print("我是前置代码")
    yield"123459","username","是否成功"
    print("我是后置代码")

新建一个文件夹调用conftest中的夹具:

# from d12_pytest的夹具和pymysql链接.testcase.test_07_conftest import setup_teardown
'''
如果需要夹具共享,那么导包也可以使用
更简单的还是简历conftest文件
'''
def test_01(setup_teardown):
    print("这是文件9")

查看结果:

扩展

1、用例执行顺序是什么?
        – 文件: 名字排序,ASCII顺序,0-9a-zA-Z
        – 文件内部: 代码从上到下顺序执行
如果调整用例执行的顺序 按照规则调整即可。

物联沃分享整理
物联沃-IOTWORD物联网 » Python pytest框架简介及使用指南

发表评论