接口自动化测试框架开发实践

 再次整理接口自动化测试框架的内容,其实过往写了很多类似的文章,基本上从概念为大家介绍,现在计划用这篇文章手把手让框架从0到1建设起来。

 

 

一、基本概念


测试框架:诸如Unittest、Pytest、TestNG之类,它们的功能主要在于管理测试用例、执行测试用例、参数化、断言、生成测试报告等基础建设工作,像本文,就是在基于pytest的基础之上,进一步扩展接口自动化测试框架的其他内容

测试工具:类似selenium、jmeter、postman等,目的是为了完成某一类的测试工作,如UI自动化、接口测试等

二、被测系统


用Django写了一个简单计算器接口,主要功能为支持1-100的2个输入,并算出最后结果,代码如下所示:

# calculator_app/views.py  
from django.http import JsonResponse  
from django.views.decorators.csrf import csrf_exempt  
import json 
from django.shortcuts import render   
  
@csrf_exempt  
def calculate(request):  
    if request.method == 'POST':  
        try:  
            data = json.loads(request.body)  
            num1 = int(data['num1'])  
            num2 = int(data['num2'])  
  
            if 1 <= num1 <= 100 and 1 <= num2 <= 100:  
                result = num1 + num2  
                return JsonResponse({'result': result})  
            else:  
                return JsonResponse({'error': 'Numbers must be between 1 and 100'}, status=400)  
        except json.JSONDecodeError:  
            return JsonResponse({'error': 'Invalid JSON'}, status=400)  
        except ValueError:  
            return JsonResponse({'error': 'Invalid input'}, status=400)  
    else:  
        return JsonResponse({'error': 'Invalid request method'}, status=405)


  
def calculator_page(request):  
    return render(request, 'calculator.html')

 


页面如下所示:

 

三、被测系统测试用例


 

用例编号用例标题输入1输入2预期
TC001 正常情况测试(50+30) 50 30 80
TC002 边界值测试(两个最小值1+1) 1 1 2
TC003 边界值测试(两个最大值100+100) 100 100 200
TC004 非法输入测试(输入0) 0 50 error
TC005 非法输入测试(输入大于100的数) 150 50 error

四、最简接口脚本实现


 

 

import requests
import pytest  

def testcase1():
      
    # 接口的URL  
    url = 'http://192.168.8.220:8085/calculate/'  
      
    # 请求头  
    headers = {  
        'Content-Type': 'application/json',  
    }  
    # 请求体  
    payload = {"num1": "50", "num2": "30"}
    # 发送POST请求  
    response = requests.post(url, headers=headers, json=payload)  
    # 响应断言  
    # 检查HTTP状态码  
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常   
    # 将响应内容解析为JSON  
    json_response = response.json()  
    # 具体的响应断言(根据你的接口文档来编写)  
    assert json_response['result'] == 80
    # 可以添加更多断言来验证响应内容  
    print("Test passed!") 


def testcase2():
      
    # 接口的URL  
    url = 'http://192.168.8.220:8085/calculate/'  
      
    # 请求头  
    headers = {  
        'Content-Type': 'application/json',  
    }  
    # 请求体  
    payload = {"num1": "1", "num2": "2"}
    # 发送POST请求  
    response = requests.post(url, headers=headers, json=payload)  
    # 响应断言  
    # 检查HTTP状态码  
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常   
    # 将响应内容解析为JSON  
    json_response = response.json()  
    # 具体的响应断言(根据你的接口文档来编写)  
    assert json_response['result'] == 3
    # 可以添加更多断言来验证响应内容  
    print("Test passed!") 


if __name__ == '__main__':
    pytest.main(['-s',"example.py"])


最简单的接口测试脚本如上所示,包含请求头、请求体、响应断言等,单看一条用例的话感觉没啥问题,但看后面的工作,多条用例实现的话,出现了大量的重复代码,而且数据和业务没有分离,如果需要调整,修改起来特别麻烦痛苦。


最简单的接口测试脚本如上所示,包含请求头、请求体、响应断言等,单看一条用例的话感觉没啥问题,但看后面的工作,多条用例实现的话,出现了大量的重复代码,而且数据和业务没有分离,如果需要调整,修改起来特别麻烦痛苦。

五、优化第一步,脚本参数化


 

testdata = [("50","30",80),("1","1",2),("100","100",200)]


@pytest.mark.parametrize("data1,data2,result",testdata)
def testcase(data1,data2,result):
      
    # 接口的URL  
    url = 'http://192.168.8.220:8085/calculate/'  
      
    # 请求头  
    headers = {  
        'Content-Type': 'application/json',  
    }  
    # 请求体  
    payload = {"num1": data1, "num2": data2}
    # 发送POST请求  
    response = requests.post(url, headers=headers, json=payload)  
    # 响应断言  
    # 检查HTTP状态码  
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常   
    # 将响应内容解析为JSON  
    json_response = response.json()  
    # 具体的响应断言(根据你的接口文档来编写)  
    assert json_response['result'] == result
    # 可以添加更多断言来验证响应内容  
    print("Test passed!") 

if __name__ == '__main__':
    pytest.main(['-s',"example.py"])
    

测试结果:
============================= test session starts =============================
platform win32 -- Python 3.7.3, pytest-7.2.1, pluggy-1.0.0
rootdir: F:\zhan\pythoncode\test_requests
plugins: allure-pytest-2.13.1, Faker-18.6.2, base-url-2.0.0, html-3.2.0, metadata-2.0.4, playwright-0.3.3, xdist-3.2.1
collected 3 items

example.py Test passed!
.Test passed!
.Test passed!
.

 


通过@pytest.mark.parametrize这个装饰器的使用,使得不用再重复写多条一样的用例,把测试数据从用例里面剥离出阿里,从而达成了一个函数完成了多条用例的实现,这就是最简的数据驱动测试。

六、优化第二步,数据与脚本分离


通过上述优化之后,代码简化了不少,但是还存在一个问题,当不同业务多了之后,每次去调整数据的时候,还是需要进入代码里面去翻,现在最通用的办法是,让数据与脚本分离出来,创建一个新的data目录,让数据存储在data里面,如果出现了数据变动,直接去更改data目录里面的数据就行了。数据存储方面,有用excel、json、yaml的,目前来说,用yaml存储数据的比较常见,故用yaml作为示例:

加入读取yaml代码,读取用例名称、http请求、期望结果

 

import yaml

def get_test_data(test_data_path):
    case = []
    http = []
    expected = []
    with open(test_data_path, encoding='utf-8') as f:
        data = yaml.load(f.read(),Loader=yaml.SafeLoader)
        test = data['test']
        for td in test:
            case.append(td.get('case',''))
            http.append(td.get('http',''))
            expected.append(td.get('expected',''))
    parameters = zip(case,http,expected)
    return case,parameters

cases,parameters = get_test_data("data\\exampledata.yaml")
print(cases,list(parameters))

 

 

 

创建data目录,里面创建yaml文件,把用例1的数据存储起来

test:  
  - case: 验证成功的用例 
    http:  
      method: POST  
      path: http://192.168.8.220:8085/calculate/
      headers:  
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
        content-type: application/json;charset=UTF-8  
      params:  
        num1: "30"
        num2: "50"  
    expected:  
      response:  
        result: 80

 


然后再对测试用例进行改造:

cases,parameters = get_test_data("data\\exampledata.yaml")
testdata = list(parameters)
url = testdata[0][1]['path']
headers = testdata[0][1]['headers']
payload = testdata[0][1]['params']
result = testdata[0][2]['response']['result']

def testcase1():
    response = requests.post(url, headers=headers, json=payload)  
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常   
    json_response = response.json()   
    assert json_response['result'] == result
    # 可以添加更多断言来验证响应内容  
    print("Test passed!")

 


可以看到,测试用例精简了很多,再结合我们上面的数据驱动测试,再进行简单改造:

首先把所有的用例数据整理进入yaml文件:

test:  
  - case: 验证成功的用例 
    http:  
      method: POST  
      path: http://192.168.8.220:8085/calculate/
      headers:  
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
        content-type: application/json;charset=UTF-8  
      params:  
        num1: "30"
        num2: "50"  
    expected:  
      response:  
        result: 80
  - case: 验证边界值用例1
    http:  
      method: POST  
      path: http://192.168.8.220:8085/calculate/
      headers:  
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
        content-type: application/json;charset=UTF-8  
      params:  
        num1: "1"
        num2: "1"  
    expected:  
      response:  
        result: 2
  - case: 验证边界值用例2
    http:  
      method: POST  
      path: http://192.168.8.220:8085/calculate/
      headers:  
        User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
        content-type: application/json;charset=UTF-8  
      params:  
        num1: "100"
        num2: "100"  
    expected:  
      response:  
        result: 200

 


然后再整改原有的数据驱动代码:

cases,parameters = get_test_data("data\\exampledata.yaml")
yamldata = list(parameters)
@pytest.mark.parametrize("case,http,expected",yamldata)
def testcase1(case,http,expected):
    response = requests.post(http["path"], headers=http["headers"], json=http['params'])  
    response.raise_for_status()  # 如果响应状态码不是200,将引发HTTPError异常   
    json_response = response.json()   
    assert json_response['result'] == expected["response"]['result']
    # 可以添加更多断言来验证响应内容  
    print("Test passed!")

 


 

这样,我们就做到数据分离,也做到了测试用例的去重了,对比原先的流水化用例,是不是精简了很多

 

七、后续


 

经过上面整合后,我们的框架里面的数据部分已经分离出去了,但开始里面还有很多东西没完成,如配置、报告、日志等等,后续再更新。

posted @ 2025-05-14 15:44  寻虫测试  阅读(44)  评论(0)    收藏  举报