在接口测试框架技术选型上,可以选择可以Java 技术栈或者是Python技术栈的体系来搭建这部分,一般而言对众多公司而言都是围绕Java技术栈或者是Python技术栈以及两个语言都并行的模式来进行自动化测试技术的落地以及在业务侧测试效率的提升,从而提升回归测试以及搭建线上巡检和Smoke Test测试的模式来提升效率以及测试质量。一般而言在Java技术栈上会选择RestAssured与TestNG的组合来进行打造API的测试框架,在Python的技术栈上会选择使用Requests与Pytest组合的模式来打造接口测试框架,当然在这里我只是列了大概的关键字。抛开语言本身,不管是Java技术栈还是Python技术栈都离不开单元测试框架这部分的实际应用与组织,因为测试最终不管是什么形式的测试模式,都是通过CASE的模式来验证被测的对象。所以本文章主要详细的介绍下Pytest的知识进行介绍下,在后续文章中介绍TestNG测试框架的内容。

测试用例编写规范

        不管是Pytest还是TestNG框架,在编写自动化测试用例都需要遵守这么几个规范,具体如下。

  • 编写的自动化测试用例都必须得以test开头,这么在执行的时候测试框架就能够自动的识别并且执行改CASE
  • 编写的自动化测试用例最好都有标题,凡是测试用例都必须得有标题,这样就知道改CASE验证的目的是什么
  • 每个测试用例尽可能的只验证一个测试场景,不建议验证多个场景,通过是多个测试场景在出问题的时候排查错误的成本会上升
  • 编写的每个CASE都好保持它的独立性。业务之间是有关联关系的,但是编写的CASE都要保持独立性,这样在执行的过程中避免A执行失败导致了A关联的所有CASE都失败
  • 每个测试的CASE都必须得有断言,没有断言的自动化测试CASE是没有任何的价值,因为它测试不出任何的问题
  • 在编写的CASE中尽可能的少使用if以及else这样的判断语句,测试结果是一个确定性的过程,不会存在一个不符合要求再判断另外一个是否符合要求
  • 某一个CASE执行失败不要停止以及堵塞,继续让CASE执行

参数化应用

        参数化应用的场景主要是相同的测试步骤不同地测试数据来验证不同的测试场景,这样可以使用少量的代码能够达到最大化的验证模式。它的本质思想其实特别很好理解,就是测试过程中被使用到的数据都是列表里面的一个元素对象而言,这样执行的时候循环列表里面的对象,然后每个元素的值进行赋值的过程。下面结合一个登录的接口案例来详细的演示下这部分的应用,涉及到的案例代码如下。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:wuya

import pytest
import  requests
import  json

def data():
  return [
    (
      '{"username":"13484545195","password":"123456"}',
      '{"status":10001,"msg":"登录的用户名或者密码无效,请检查!"}'
    )
  ]

@pytest.mark.parametrize("body,result",data())
def test_login(body,result):
  print(type(json.loads(body)))
  r=requests.post(
    url='http://47.95.142.233:8000/login/auth/',
    json=json.loads(body))
  assert r.json()==json.loads(result)
  assert r.status_code==500

备注:如上代码中,函数data()主要返回的是被测试的数据,而且返回的形式是列表的数据类型,列表里面的元素当然可以是常用的数据类型(元组&列表&字典),分离出来的数据第一个是请求参数,第二个是响应,这样结合参数化就可以使用一个CASE的代码能够覆盖很多的测试场景。如果案例可以再延伸下,比如您测试一个系统的单接口的测试用例,但是这些测试用例有很多的,不管是使用PostMan还是JMeter以及MS平台都需要录制很久的,但是如果使用如上参数化的代码,只需要把请求参数以及响应结果维护起来,这样可以很快速的验证完,延伸后的案例代码如下。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:wuya

import pytest
import  requests
import  json

def data():
  return [
    (
      '{"username":"wuya", "password":"asd888","age":18,"sex":"男"}',
      '{ "age": 18, "password": "asd888", "sex": "男", "username": "wuya" }',
      200
    ),
    (
      '{ "password":"asd888","age":18,"sex":"男"}',
      '{"message": {"username": "用户名不能为空"}}',
      400
    ),
    (
      '{"username":"wuya", "password":"asd888","age":"asd","sex":"男"}',
      '{"message": {"age": "年龄必须为正正数"}}',
      400
    )
  ]

@pytest.mark.parametrize("body,result,status_code",data())
def test_login(body,result,status_code):
  print(type(json.loads(body)))
  r=requests.post(
    url='http://127.0.0.1:5000/login',
    json=json.loads(body))
  assert r.json()==json.loads(result)
  assert r.status_code==status_code

如上代码中其实核心思想和前面的是一样的,只不过这个过程中数据多了协议状态码的部分,其他都是一样的。其他代码都基本没什么变化,而这就是Pytest参数化的魅力,少量的代码又得到了多个测试场景的覆盖。

Fixture函数

        Fixture也是Pytest测试框架里面设计非常好的一点,但是很多人对这部分它的作用理解的不是那么很透彻。在任何的一个单元测试框架里面,都是存在测试固件的,比如UnitTest测试框架中的setUp()与tearDown()方法,但是测试固件在Pytest体现的不是那么很明显,但是既然是测试框架,必然存在测试固件的。所以Pytest中Fixture具有两大特性:

  • 函数返回值,使用函数返回值的思想来解决动态参数传递的问题
  • 测试固件,来处理初始化以及清理的逻辑

结合案例实战代码来看第一个特性,就是函数的返回值特性。在Python中,一切是对象是它设计最优雅的地方,因为了这个思想,基本装饰器以及鸭子类型等知识理解起来会非常的容易,下面结合案例主要体现Fixture函数是一个对象的过程,案例代码如下。

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:wuya

import pytest
import  requests
import  json

@pytest.fixture()
def access_token():
  r=requests.post(
    url='http://47.95.142.233:8000/login/auth/',
    json={"username":"13484545195","password":"asd888"})
  return r.json().get('token',None)

@pytest.fixture()
def headers(access_token):
  return {'Authorization':'JWT {token}'.format(token=access_token)}

def test_login(headers):
  r=requests.get(
    url='http://47.95.142.233:8000/interface/index',
    headers=headers)
  assert r.status_code==200

如上的access_token()与headers()函数都是Fixture的函数,所以最终在测试函数test_login中形式参数headers其实就是Fixture函数headers(),所以执行就能够获取到动态参数值TOKEN,也能够解决参数的关联问题,这是Fixture的第一个特性。

     接下来看Fixture函数的第二个特性就是测试固件的特性,也就是初始化与清理,那么编写查询的测试用例,那么的前置动作是添加,后置动作是删除。测试固件的核心思想是让系统始终保持如一的干净,所以使用完测试数据后进行删除,下面演示下Fixture函数测试固件的体现,代码如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
# author:wuya

import pytest
import  requests
import  json

@pytest.fixture()
def access_token():
  r=requests.post(
    url='http://47.95.142.233:8000/login/auth/',
    json={"username":"13484545195","password":"asd888"})
  return r.json().get('token',None)

@pytest.fixture()
def headers(access_token):
  return {'Authorization':'JWT {token}'.format(token=access_token)}

def writeID(prodctID):
  json.dump(str(prodctID),open('prodctID','w'))

def getID():
  return int(json.load(open('prodctID')))

def addProduct(headers):
  r=requests.post(
    url='http://47.95.142.233:8000/interface/product/',
    json={"name":"测试数据","product_type":"WEB","version":"1.0","master":"无涯","description":"测试"},
    headers=headers)
  writeID(prodctID=r.json()['id'])
  return r

def delProduct(headers):
  r=requests.delete(
    url='http://47.95.142.233:8000/interface/product/{productID}/'.format(productID=getID()),
    headers=headers)
  return r

@pytest.fixture()
def init(headers):
  addProduct(headers)
  yield
  delProduct(headers)

def test_so_product(init,headers):
  '''产品搜索验证'''
  r=requests.get(
    url='http://47.95.142.233:8000/interface/products?name=测试数据',
    headers=headers)
  assert r.status_code==200
  print(r.json())
  print(type(getID()))
  assert r.json()[0]['id']==getID()

在上面代码中编写的函数init其实就是测试固件的特性,不过它又是Fixture的函数,所以说Fixture把这二者完美的结合起来了,使用Fixture函数一方面可以解决接口测试中参数关联问题又可以完美的构造测试固件的特性。