Pytest接口自动化进阶实战

讨论QQ群:140814035 加群暗号:自动化测试

目录

 

正文

一、为什么要用Pytest来做接口自动化

1.Pytest的用途及优点

Pytest目前是自动化业界非常主流的一个自动化测试框架,它本质是Python的一个第三方单元测试库。和unittest一样,主要是用来管理自动化测试用例的执行的,比如用例执行,用例分组,执行日志输出等等。

Pytest的优点:
1.他可以自动的识别测试模块(测试文件)、测试类以及测试函数,规则很简单:
文件名:格式为test _ *.py或* _test.py的文件
类:Test开头
测试函数/方法:拥有test前缀的测试函数或方法

2.模块化夹具fixture可用来管理各类测试资源

3.对unittest完全兼容

我们知道在pytest出来之前,unittest应用也极为广泛,因此完全兼容unittest,在想要对自动化框架升级的时候可以节省不少开发成本。

4.Pytest是最能 装“插”的开源单元测试框架

https://docs.pytest.org/en/latest/reference/plugin_list.html

目前pytest已经有900多个插件,每天都会新增1到2个插件。著名的插件有:

pytest-allure、pytest-rerunfailures、pytest-xdist等等

插件能力:集成功能强大的测试报告组件,实现失败重跑,并发用例执行等

插件的安装也非常简单,基本上就是:pip install 插件名

5.pytest安装

1)安装命令
pip install pytest
2)验证安装
pytest --version

3)官方文档
https://docs.pytest.org/en/latest/contents.html

以上这些,充分说明pytest是一个简单、易用并且功能强大的用例管理框架。

二、接口测试简介

接口组成元素:
1.接口地址(url+端口+路径)
2.接口请求方式: post get delete put...
3.接口请求参数
4.响应数据

这里演示使用postman完成登陆接口调用:

 

 

 

 

 

 

 然后我们看到登陆成功后,响应报文里头有个token,这个东西是干嘛的呢,简单介绍一下:

token用途简介

登陆系统 = 进医院大门
token = 绿码
拥有了绿码才有进入医院的权限 = 拥有token才能访问系统内部的各个页面
没有绿码,不允许进入任何门诊 = 没有token,即使你拥有接口的链接,响应也会提示你鉴权失败

 

以上只是接口最简单的一个入门介绍,要真正的学好接口测试,要学的东西很多,比如必须熟悉的入门级的基础理论:接口通信原理、HTTP网络协议、接口鉴权机制等等,一节课肯定是没法熟悉接口自动化的,必须系统的去学习才能完整的掌握

三、用Python代码实现接口测试

相对于工具,用python做接口自动化的优势:

扩展性更强,更灵活
python 各种封装、调用
可集成各种库和工具,满足各种需求
allure 报告
jsonpath 报文解析
jenkins 持续集成
。。。

代码实操:Pytest_intf_advance/base_intf/demo.py

PS:这里的接口地址是自己本地搭建的一个接口测试服务,仅供大家参考,后期有时间再弄个免费的公网接口服务给大家使用

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : demo.py
import json

import jsonpath
import requests
from Pytest_intf_advance.base_intf.api_key import ApiKey

# 接口请求的模拟
# 数据的生成
data = {
    'username': 'admin',
    'password': '123456'

}

# 接口的地址
url = 'http://127.0.0.1:5000/api/login'

# 将数据传递到对应的接口地址,来实现一次该接口的请求下发并返回响应结果:定义对应的请求方法
res = requests.post(url=url, json=data)

# 输出响应结果:编译后的内容
print(res.text)
# 返回报文中的某个key,比如msg来获取相应的值
print(type(res.text))

# 输出响应结果:字典类型
print(res.json())
print(type(res.json()))

# 接口断言
assert 'success' == res.json()['msg']

# 接口断言,嵌套字典,{key:value,key:{key:value}}
# assert 'changsha' == res.json()['city']
assert 'changsha' == res.json()['adress']['city']

# 那么这里我们就可以用到jsonpath库,来简化取值操作
# jsonpath获取数据的表达式:成功则返回list,失败则返回false
# loads是将json格式的内容转换为字典的格式
# jsonpath接收的是dict类型的数据
value_list = jsonpath.jsonpath(res.json(), '$..{0}'.format('city'))
print(value_list)
value = value_list[0]
print(value)
assert 'changsha' == value

# 就有点繁琐,因此我们引入接口关键字封装,来简化代码,封装api_key
# 实例化工具类
ak = ApiKey()
assert 'changsha' == ak.get_text(res.text,'city')
print(ak.get_text(res.text,'city'))

代码执行输出信息

{
  "adress": {
    "city": "changsha"
  }, 
  "httpstatus": 200, 
  "info": {
    "age": 18, 
    "name": "admin"
  }, 
  "msg": "success", 
  "token": "23657DGYUSGD126731638712GE18271H"
}

<class 'str'>
{'adress': {'city': 'changsha'}, 'httpstatus': 200, 'info': {'age': 18, 'name': 'admin'}, 'msg': 'success', 'token': '23657DGYUSGD126731638712GE18271H'}
<class 'dict'>
['changsha']
changsha
changsha

Process finished with exit code 0

  

四、接口关键字封装

代码实操:Pytest_intf_advance/base_intf/api_key.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : api_key.py
"""
    这是接口关键字驱动类,用于提供自动化接口测试的关键字方法。
    主要实现常用的关键字内容,并定义好所有的参数内容即可
"""
import json

import allure
import jsonpath
import requests


class ApiKey:
    # 基于jsonpath获取数据的关键字:用于提取所需要的内容
    def get_text(self,data,key):
        # jsonpath获取数据的表达式:成功则返回list,失败则返回false
        # loads是将json格式的内容转换为字典的格式
        # jsonpath接收的是dict类型的数据
        dict_data = json.loads(data)
        value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key))
        return value[0]

    # get请求的封装:因为params可能存在无值的情况,存放默认None
    def get(self,url,params=None,**kwargs):
        return requests.get(url=url,params=params,**kwargs)

    #post请求的封装:data也可能存在无值得情况,存放默认None
    def post(self,url,data=None,**kwargs):
        return requests.post(url=url,data=data,**kwargs)



if __name__ == '__main__':
    ak = ApiKey()
    data = {
        'username': 'admin',
        'password': '123456'
    }
    res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data)
    print(res2.text)

 

代码执行输出信息:

{
  "adress": {
    "city": "changsha"
  }, 
  "httpstatus": 200, 
  "info": {
    "age": 18, 
    "name": "admin"
  }, 
  "msg": "success", 
  "token": "23657DGYUSGD126731638712GE18271H"
}

  

那么我们第一个接口自动化代码就写完了,我们可以看到,上述代码是不是很松散。没有以一个用例的形式来进行,那么这时候我们就可以上Pytest了

五、用Pytest框架编写接口自动化测试代码

1.基本用例组织

pytest用例运行测规则很简单,从上往下,完全按你放置的顺序来执行

代码实操:Pytest_intf_advance/pytest_intf/case/test_shopXo.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : test_shopXo.py
import pytest
import requests

from Pytest_intf_advance_V2.base_intf.api_key import ApiKey


class Test_ApiCase():
    # 登陆接口用例
    def test_login(self):
        url = 'http://127.0.0.1:5000/api/login'
        userInfo = {
            'username': 'admin',
            'password': '123456'
        }
        res = self.ak.post(url=url, json=userInfo)
        print(res.text)
        # 获取响应中的结果,用于校验是否成功
        msg1 = self.ak.get_text(res.text, 'msg')
        print(msg1)
        assert msg1 == 'success'

    # 查询用户信息接口
    def test_getuserinfo(self,token_fix):
        # 1.获取工具类、token
        ak, token = token_fix

        # 2.查询个人用户信息
        url = 'http://127.0.0.1:5000/api/getuserinfo'
        headers = {
            'token': token
        }
        res1 = ak.get(url=url, headers=headers)
        print(res1.text)
        name = ak.get_text(res1.text, 'nikename')
        assert "风清扬" == name

if __name__ == '__main__':
    # -s参数,在控制台输出打印信息
    # -v参数,在控制台输出详细信息
    pytest.main(['-s','-v','test_shopXo_02.py'])

代码执行输出信息:

============================= test session starts =============================
platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- D:\Python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.10', 'Platform': 'Windows-10-10.0.19044-SP0', 'Packages': {'pytest': '6.1.2', 'py': '1.9.0', 'pluggy': '0.13.1'}, 'Plugins': {'allure-pytest': '2.8.11', 'forked': '1.1.3', 'html': '3.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'parallel': '0.1.0', 'rerunfailures': '9.1.1', 'xdist': '1.31.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_152'}
rootdir: D:\Pytest_intf_advance_V2\pytest_intf\case
plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0
collecting ... collected 2 items

test_shopXo.py::Test_ApiCase::test_login {
  "adress": {
    "city": "changsha"
  }, 
  "httpstatus": 200, 
  "info": {
    "age": 18, 
    "name": "admin"
  }, 
  "msg": "success", 
  "token": "23657DGYUSGD126731638712GE18271H"
}

success
PASSED
test_shopXo.py::Test_ApiCase::test_getuserinfo {
  "data": [
    {
      "nikename": "王五", 
      "openid": "UEHUXUXU78272SDSassDD", 
      "userbalance": 5678.9, 
      "userid": 17890, 
      "username": "admin", 
      "userpoints": 4321
    }
  ], 
  "httpstatus": 200
}

PASSED

============================== 2 passed in 0.30s ==============================

Process finished with exit code 0

2.接口关联

实现接口关联有多种方式:

1)和工具类相似使用setup_class,用的时候加个self就可以了
2)公共变量可以定义一个公共变量放到类里,这样类中的所有用例都可以读取到了

那么上面这两种方式,其实是有局限性的,就是只能在单个类或者单个文件中使用,如果需要在整个项目的多个测试文件中使用,就不行了,现在就给大家着重介绍第三种方式,来实现项目级的token预置

 

六、fixture+conftest实现项目级token预置

接下来,我们先讲讲fixture,官方的介绍比较复杂,这边我个人总结了下。

1.pytest之fixture介绍

fixture是pytest提供的一个简化的装饰器,可以轻松的复用已定义的函数逻辑(比如登陆,获取token,环境数据预置)等操作。

官方介绍:
https://docs.pytest.org/en/latest/explanation/fixtures.html#about-fixtures

概念很简单,关键是如何用,下面给大家编写一个fixture快速入门的例子

代码实操:Pytest_intf_advance/base_intf/test_fix/test_quick_exam.py

#coding=utf-8
import pytest

# 通过@pytest.fixture声明这个函数为fixture
# 用例前置
@pytest.fixture
def first_fix():
    # 此处假设有代码逻辑几十行,比如执行登陆操作,获取token之类的
    # 返回一个list
    return ["a"]

# 将已声明为fixture的函数,填写在参数中,这样fixture函数会在该函数调用之前调用
# 测试用例
def test_string(first_fix):
    # 用例步骤
    # 使用first_fix返回的list添加元素
    first_fix.append("b")
    print(first_fix)

if __name__ == '__main__':
    pytest.main(['-s'])

  代码执行输出信息:

============================= test session starts =============================
platform win32 -- Python 3.8.10, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: D:\Pytest_intf_advance_V2\base_intf\test_fix
plugins: allure-pytest-2.8.11, forked-1.1.3, html-3.0.0, metadata-1.8.0, ordering-0.6, parallel-0.1.0, rerunfailures-9.1.1, xdist-1.31.0
collected 3 items

test_quick_exam.py ['a', 'b']
.
test_conftest_01\doc1\test_case01.py 
开始执行登陆操作
用例一
.
test_conftest_01\doc2\test_case02.py 
开始执行登陆操作
用例二
.

============================== 3 passed in 0.07s ==============================

Process finished with exit code 0

  以上就是fixture最基本的概念和用法。这时候,可能有人就想问了,那我想在其他文件中使用这个定义好的fixture怎么办呢?那这就要介绍下fixture的好基友,conftest.py配置文件了

2.conftest.py

conftest.py是pytest特有的本地测试配置文件,在这个文件中定义的Fixture可以在项目中多个文件使用,conftest.py文件名称是固定的,pytest会自动识别该文件,只作用于它所在的目录及子目录。

同样的,编写一个快速入门的代码例子:

代码实操:多文件代码,先看代码结构

 

 

 Pytest_intf_advance/base_intf/test_fix/test_conftest_01/conftest.py

#coding=gbk
import pytest

@pytest.fixture()
def fix1():
    print("\n开始执行登陆操作")

Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc1/test_case01.py

#coding=gbk
import pytest

def test_case02(fix1):
    print("用例一")

Pytest_intf_advance/base_intf/test_fix/test_conftest_01/doc2/test_case02.py

#coding=gbk
import pytest

def test_case02(fix1):
    print("用例二")

Pytest_intf_advance/base_intf/test_fix/test_conftest_01/main_run.py

#coding=gbk
import pytest


if __name__ == '__main__':
    pytest.main(['-s'])

运行main_run.py文件

代码输出:

collected 2 items

doc1\test_case01.py 
开始执行登陆操作
用例一
.
doc2\test_case02.py 
开始执行登陆操作
用例二
.

============================== 2 passed in 0.05s ==============================

Process finished with exit code 0


fixture+conftest的搭配是非常的有用的,接下来我们来在接口自动化中实战一把,来展现一下这对黄金组合的实力

3.接口中应用fix+conftest实现项目级token预置

1)先在conftest中定义fixture

初始化工具类,登陆,并返回工具类对象和token值

代码实操:多文件代码,先看代码结构

 

 

 Pytest_intf_advance_V2/pytest_intf/conftest.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : conftest.py
from random import random
from Pytest_intf_advance.base_intf.api_key import ApiKey

import pytest


# @pytest.fixture(scope='session')
# def token_fix():
#     a = random()
#     print(a)
#     return a

#项目级fix,整个项目只初始化一次


@pytest.fixture(scope='session')
def token_fix():
    # 初始化工具类
    ak = ApiKey()
    # 定义访问链接‘
    url = 'http://127.0.0.1:5000/api/login'
    # 定义请求用户数据
    userInfo = {
        'username': 'admin',
        'password': '123456'
    }
    # 发送post请求
    res = ak.post(url=url,json = userInfo)
    # 获取token
    token = ak.get_text(res.text,'token')
    # 返回多个值
    return ak,token

Pytest_intf_advance_V2\pytest_intf\api_keyword\api_key.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : api_key.py
"""
    这是接口关键字驱动类,用于提供自动化接口测试的关键字方法。
    主要实现常用的关键字内容,并定义好所有的参数内容即可
"""
import json

import allure
import jsonpath
import requests


class ApiKey:
    # 基于jsonpath获取数据的关键字:用于提取所需要的内容
    def get_text(self,data,key):
        # jsonpath获取数据的表达式:成功则返回list,失败则返回false
        # loads是将json格式的内容转换为字典的格式
        # jsonpath接收的是dict类型的数据
        dict_data = json.loads(data)
        value = jsonpath.jsonpath(dict_data,'$..{0}'.format(key))
        return value[0]

    # get请求的封装:因为params可能存在无值的情况,存放默认None
    def get(self,url,params=None,**kwargs):
        return requests.get(url=url,params=params,**kwargs)

    #post请求的封装:data也可能存在无值得情况,存放默认None
    def post(self,url,data=None,**kwargs):
        return requests.post(url=url,data=data,**kwargs)



if __name__ == '__main__':
    ak = ApiKey()
    # res = ak.get(url='http://127.0.0.1:5000/api/getuserinfo',timeout=0.1)
    # print(res.text)
    data = {
        'username': 'admin',
        'password': '123456'
    }
    res2 = ak.post(url='http://127.0.0.1:5000/api/login',json=data)
    print(res2.text)

Pytest_intf_advance_V2\pytest_intf\case\test_shopXo_02.py 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File : test_shopXo.py
import pytest
import requests

from Pytest_intf_advance_V2.base_intf.api_key import ApiKey


class Test_ApiCase():

    # 查询用户信息接口
    def test_getuserinfo(self,token_fix):
        # 1.获取工具类、token
        ak, token = token_fix

        # 2.查询个人用户信息
        url = 'http://127.0.0.1:5000/api/getuserinfo'
        headers = {
            'token': token
        }
        res1 = ak.get(url=url, headers=headers)
        print(res1.text)
        name = ak.get_text(res1.text, 'nikename')
        assert "张三" == name

if __name__ == '__main__':
    # -s参数,在控制台输出打印信息
    # -v参数,在控制台输出详细信息
    pytest.main(['-s','-v','test_shopXo_02.py'])

  

代码输出:

collecting ... collected 1 item

test_shopXo_02.py::Test_ApiCase::test_getuserinfo {
  "data": [
    {
      "nikename": "张三", 
      "openid": "UEHUXUXU78272SDSassDD", 
      "userbalance": 5678.9, 
      "userid": 17890, 
      "username": "admin", 
      "userpoints": 4321
    }
  ], 
  "httpstatus": 200
}

PASSED

============================== 1 passed in 0.20s ==============================

Process finished with exit code 0

通过fixture+conftest 这套组合,可以方便的在多个文件中使用同一个token,可以在项目内实现更大范围内的接口关联。  

 

本文来自博客园,作者:房间里的风,转载请注明原文链接:https://www.cnblogs.com/TigerRay/p/15919246.html


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容