关于接口自动化框架的从无到有

541 阅读5分钟

现在企业对于软件测试的人员的要求越来越高,自动化已经是中级软件测试工程师的基本职业技能之一,那么我们今天来搭建一个接口自动化框架玩玩

既然是框架,那么就得有结构设计理念,我们这用的是分层分包的方式,实现高内聚低耦合的面向对象的一种编程方式。

我这里用的是unittest+requests+htmltestrunner+ddt搭建的框架

先把各个不同功能的文件夹建好:

1.common包放公共的脚本,比如连接数据库的、写日志的、发请求的、还要其他什么读数据的啊、拿token的啊、读配置文件的往里面扔就完事了

2.conf包呢就主要是放配置文件,比如测试环境、SSH信息、数据库信息等,方便维护以及参数化

3.data包用来放测试数据,我这里用的是excel,之前也用过yml

4.testCase包呢,顾名思义,就是用来放测试用例的包啦

5.testLogs用来放日志文件,每次脚本运行都会生成日志文件,就统一放在这个文件夹内

6.testReport包就来放htmltestrunner生成的测试报告

7.最后留一个run方法,用来作为框架运行的入口

包建好了之后呢,就来开始写脚本了。

我先写的是连接数据库的脚本,因为之前没有用ssh连过mysql,这次想试一试,然后一顿操作呢,没有成功,在网上找了个脚本,自己改吧改吧,也就成了,我的ip和端口号那些都用了传参的方式,所以如果有大佬要用的话,就麻烦改一改,别到时候来骂我:


import pymysql
from sshtunnel import SSHTunnelForwarder
from API.common.readConfig import ReadConfig

class DataBase:

    def __init__(self):
        read = ReadConfig()

        self.server =  SSHTunnelForwarder(
            read.get_config_str("ssh","sshHost"), read.get_config_int("ssh","sshPort"),  #ssh IP和port
            ssh_password = read.get_config_str("ssh","sshPassword"),    #ssh 密码
            ssh_username = read.get_config_str("ssh","sshUser"),        #ssh账号
            remote_bind_address = (read.get_config_str("mysql","dbHost"), read.get_config_int("mysql","dbPort")))#数据库所在的IP和端口

        #启动服务
        self.server.start()
        #打印本地端口,已检查是否配置正确
        print(self.server.local_bind_host)


    def Get_db(self,sql):
        read = ReadConfig()

        self.goDB = pymysql.connect(host = read.get_config_str("mysql","dbHost"),     #固定写法
                       port = self.server.local_bind_port,
                       user = read.get_config_str("mysql","dbUser"),                  #数据库账号
                       passwd = read.get_config_str("mysql","dbPassword"),            #数据库密码
                        db = read.get_config_str("mysql","dbName"))                   #可以限定,只访问特定的数据库,否则需要在mysql的查询或者操作语句中,指定好表名


        cur = self.goDB.cursor()

        try:
            #执行SQL语句检查是否连接成功
            cur.execute("select version()")
            result = cur.fetchone()
            print("Database version: %s" % result)
            cur.execute(sql)
            data = cur.fetchall()

            return data

        except:
            print("Error")

#关闭连接
    def __del__(self):
        self.goDB.close()
        self.server.close()

然后是封装日志类的文件:

import logging
import time

class GetLog:

    def __init__(self):
        curTime = time.strftime('%Y-%m-%d')
        self.logname = 'testLogs/AutoTest' + '_' + curTime + '.txt'

    def get_log(self,level,msg):

        # 创建日志收集器
        logger = logging.getLogger()
        logger.setLevel('DEBUG')

        # 创建handler
        fh = logging.FileHandler(self.logname,'a',encoding='gbk')
        fh.setLevel('INFO')
        ch = logging.StreamHandler()
        ch.setLevel('INFO')

        # 定义handler的输出格式
        formatter = logging.Formatter('%(asctime)s - %(filename)s - %(name)s - %(levelname)s - 日志信息: %(message)s')
        ch.setFormatter(formatter)
        fh.setFormatter(formatter)

        # 给logger添加handler
        logger.addHandler(fh)
        logger.addHandler(ch)


        if level == 'DEBUG':
            logger.debug(msg)
        elif level == 'INFO':
            logger.info(msg)
        elif level == 'WARNING':
            logger.warning(msg)
        elif level == 'ERROR':
            logger.error(msg)
        elif level == 'CRITICAL':
            logger.critical(msg)

        logger.removeHandler(fh)
        logger.removeHandler(ch)
        fh.close()

    def log_debug(self,msg):
        self.get_log('DEBUG',msg)

    def log_info(self,msg):
        self.get_log('INFO',msg)

    def log_warning(self,msg):
        self.get_log('WARNING',msg)

    def log_error(self,msg):
        self.get_log('ERROR',msg)

    def log_critical(self,msg):
        self.get_log('CRITICAL',msg)

接着封装发请求的类:


import requests

class HttpRequest:

    def __init__(self,method,url,data=None,token=None,headers=None):
        try:
            if method == 'GET':
                self.response = requests.get(url=url,params=data,headers=headers)
            elif method == 'POST':
                self.response = requests.post(url=url,data=data,headers=headers)
            elif method == 'detele':
                self.response = requests.delete(url=url,data=data,headers=headers)
        except Exception as e:
            raise e

    def get_status_code(self):
        return self.response.status_code

    def get_text(self):
        return self.response.text

    def get_json(self):
        return self.response.json()



读写excel的类:

import openpyxl


class Case:

    def __init__(self):
        self.case_id = None
        self.url = None
        self.data = None
        self.title = None
        self.method = None
        self.expected = None

class ReadExcel:

    def __init__(self,file_name):
        try:
            self.filename = file_name
            self.wb = openpyxl.load_workbook(self.filename)
        except FileNotFoundError as e:
            print('{0} not found , please check file path'.format(self.filename))
            raise e

    def get_cases(self,sheet_name):
        sheet = self.wb[sheet_name]
        max_row =  sheet.max_row
        test_cases = []
        for r in range(2,max_row+1):
            case = Case() # 实例化一个data对象,用于存放读取的测试数据
            case.case_id = sheet.cell(row=r,column=1).value
            case.title = sheet.cell(row=r,column=2).value
            case.method = sheet.cell(row=r,column=3).value
            case.url = sheet.cell(row=r,column=4).value
            case.data = sheet.cell(row=r,column=5).value
            case.expected = sheet.cell(row=r,column=6).value
            case.header = sheet.cell(row=r,column=7).value
            test_cases.append(case)
        return test_cases

    # 获取到workbook里面所有的sheet名称的列表
    def get_sheet_name(self):
        return self.wb.sheetnames

    # 根据sheet_name定位到sheet,根据case_id定位到行,写入result
    def write_result(self,sheet_name,case_id,actual,result):
        sheet = self.wb[sheet_name]
        max_row = sheet.max_row
        for r in range(2,max_row+1):
            # 获取第r行第1列的 case_id值
            case_id_r = sheet.cell(row=r,column=1).value
            # 判断excel读取的当前行的case_id_r是否与传入的case_id相同
            if case_id_r == case_id:
                sheet.cell(row=r,column=7).value = actual
                sheet.cell(row=r,column=8).value = result
                self.wb.save(filename=self.filename)
                break

读配置文件的类:


import configparser



class ReadConfig:

    # 初始化函数:实例化conf对象,读取传入的配置文件
    def __init__(self):
        self.conf = configparser.ConfigParser()
        self.conf.read("conf/config.ini")

    def get_config_str(self,section,option):
        return self.conf.get(section,option)

    def get_config_boolean(self,section,option):
        return self.conf.getboolean(section,option)

    def get_config_int(self,section,option):
        return self.conf.getint(section,option)

    def get_config_float(self,section,option):
        return self.conf.getfloat(section,option)

    def get_items(self,section):
        return self.conf.items(section)


到这一步呢,common包内的方法就写的差不多了,我因为被测系统设计的原因,多加了一个get_token的类,这儿就不写出来了。

然后是run方法:

import unittest
from HTMLTestRunner import HTMLTestRunner
import time
from API.testCase.test_1 import TestRecharge

from API.common.getlog import GetLog

get_log = GetLog()

def RunTest():
    suite  = unittest.TestSuite()
    loader = unittest.TestLoader()
    suite.addTest(loader.loadTestsFromTestCase(TestRecharge))
    cur_time = time.strftime('%Y-%m-%d_%H_%M_%S')
    report_name = 'testReport/test_results' + cur_time + '.html'

    with open(report_name,'wb+') as file:
        runner = HTMLTestRunner.HTMLTestRunner(stream=file,
                                               verbosity=2,
                                               title='接口测试报告',
                                               description='基于python+unittest进行的数据驱动接口自动化测试',
                                               )
        runner.run(suite)

if __name__ == '__main__':
    get_log.log_info('「ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ Api Request AutoTest Start ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ」 ')
    RunTest()
    get_log.log_info('「ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ Api Request AutoTest End ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ」 ')


写到这里,最后一步就是test_case的脚本开发了,调用之前的公共方法,在这执行excel一页sheet的测试用例,然后用run方法调用testcase就可以了,这一步呢,需要结合项目实际情况去开发脚本:


from API.common.readConfig import ReadConfig
from API.common.dataBase import DataBase
from API.common.http_request import HttpRequest
from API.common.oper_token import Get_token
from API.common.read_excel import ReadExcel
from API.common.getlog import GetLog
from ddt import ddt,data
import json
import unittest


read_config = ReadConfig()
url_pre = read_config.get_config_str('api','hostName')

# 读取excel,获取login测试数据

read_excel = ReadExcel("data/123.xlsx")
recharge_cases = read_excel.get_cases("Sheet1")

get_log = GetLog()

@ddt
class TestRecharge(unittest.TestCase):

    def setUp(self):
        print('Test Start')

    def tearDown(self):
        print('Test End')

    @data(*recharge_cases)
    def test_recharge(self,case):
        url = url_pre + case.url
        token=Get_token().get_token()
        try:
            payload= json.loads(case.data)
            payload["token"]=token
        except:
            payload=case.data
        # 记录当前测试case信息
        get_log.log_info('''Test Case Info:
        case_id : {0}
        title   : {1}
        method  : {2}
        url     : {3}
        data    : {4}
        expected: {5}
        header  : {6}
        '''.format(case.case_id,case.title,case.method,url,payload,case.expected,case.header))


        response = HttpRequest(method=case.method,url=url,data=payload,headers=eval(case.header))

        actual = response.get_text()

        # 记录当前测试case接口响应信息
        get_log.log_info('''Test Case Request Response Result:
        response : {0}
        actual   : {1}
        '''.format(response.get_json(),actual))

        #断言并将结果写入excel最后一行
        try:
            self.assertEquals(case.expected,actual)
            read_excel.write_result('recharge',case.case_id,actual,'Pass')
            get_log.log_info('Test Result is Passed ! case_id is {0},title is {1} '.format(case.case_id,case.title))
        except Exception as e:
            read_excel.write_result('recharge',case.case_id,actual,'Fail')
            get_log.log_info('Test Result is Failed ! case_id is {0},title is {1} '.format(case.case_id,case.title))
            get_log.log_error('Error msg :{0}'.format(e))
            raise e

这套框架需要install的库有:

configparser==5.0.0
openpyxl==3.0.3
requests==2.23.0
pymysql==0.9.3
sshtunnel==0.1.5
ddt==1.4.1
HTMLTestRunner==0.8.0

一蓑一笠一扁舟

一丈丝纶一寸钩

一曲高歌一樽酒

一人独钓一江秋