基于python的接口自动化测试框架

项目背景

公司内部的软件采用B/S架构,大部分是数据的增删改查,由于还在开发阶段,所以UI界面的变化非常快,难以针对UI进行自动化测试,那样会消耗大量的精力与时间维护自动化脚本。针对此种情况,针对接口测试较为有效。

工具选择

针对接口测试的工具也很多,例如soup UI,robot framework,甚至jmeter这样的性能测试工具也可以进行接口测试。

robot framework测试框架有很多的第三方库可以使用,采用的是填表的方式进行,较容易上手,但是无法深入底层的了解客户端与服务器的交互过程。jmeter这样的专注性能测试的工具,进行接口测试,有点大材小用的感觉而且无法生成测试报告。

综上考虑,决定自己开发一个简单的框架,优点是足够灵活,可以随时根据需求进行变更,后台使用的是python flask进行开发,此次选用python 2.7.11进行框架的开发,python开发的速度很快,且容易上手,丰富的第三方库,大大加快了开发速度和难度。

框架思路

由于是框架,所以要考虑到框架的可重用性和可维护性。采用数据驱动的设计方式,将数据分层出来,与业务逻辑剥离。这样测试人员就可以通过数据文件专注的写测试用例,不用关注代码编写,提高了效率。此次框架采用基本的excel进行数据管理。通过对excel 的读取获得数据。

之后将测试的结果生成HTML格式的测试报告发送给相关开发人员。

第三方库介绍

Requests
python中有许多针对http的库,例如自带的urllib2,但是自带的urllib2编写起来实在是太费精力,所以采用号称"HTTP for Humans"的requests库。

xlrd
xlrd使得python可以方便的对excel文件进行读写操作,此次通过xlrd读取excel文件中的测试数据。

以上第三方库都可以通过pip直接安装或者通过pypi下载源码包安装。

模块介绍

get_conf:读取配置文件,获得邮件发送的配置信息,如smtpserver、receiver、sender等。

md5Encode:部分数据采用md5加密后传输,所以需要把从excel读取的数据进行md5加密。

sendMail:当测试完成后,将测试报告自动的发送给相关开发人员。

runTest:此部分读取excel中的数据,调用下方的interfaceTest方法,保存interfaceTest返回的信息。

interfaceTest:将runTest读取的excel数据作为入参,执行接口测试,并将后台返回的信息返回给runTest。

Excel

不知怎么图片一直上传失败,后面会将excel文件格式上传

代码实现

#通过自带的ConfigParser模块,读取邮件发送的配置文件,作为字典返回
import ConfigParser

def get_conf():
    conf_file = ConfigParser.ConfigParser()

    conf_file.read(os.path.join(os.getcwd(),'conf.ini'))

    conf = {}

    conf['sender'] = conf_file.get("email","sender")

    conf['receiver'] = conf_file.get("email","receiver")

    conf['smtpserver'] = conf_file.get("email","smtpserver")

    conf['username'] = conf_file.get("email","username")

    conf['password'] = conf_file.get("email","password") 

    return conf

#此处使用python自带的logging模块,用来作为测试日志,记录测试中系统产生的信息。
import logging,os
log_file = os.path.join(os.getcwd(),'log/sas.log')
log_format = '[%(asctime)s] [%(levelname)s] %(message)s'
logging.basicConfig(format=log_format, filename=log_file, filemode='w', level=logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
formatter = logging.Formatter(log_format)
console.setFormatter(formatter)
logging.getLogger('').addHandler(console)

#读取testcase excel文件,获取测试数据,调用interfaceTest方法,将结果保存至errorCase列表中。
import xlrd,hashlib,json

def runTest(testCaseFile):
      testCaseFile = os.path.join(os.getcwd(),testCaseFile)
      if not os.path.exists(testCaseFile):
          logging.error('测试用例文件不存在!')
          sys.exit()
      testCase = xlrd.open_workbook(testCaseFile)
      table = testCase.sheet_by_index(0)
      errorCase = []                #用于保存接口返回的内容和HTTP状态码

      s = None
      for i in range(1,table.nrows):
            if table.cell(i, 9).vale.replace('\n','').replace('\r','') != 'Yes':
                continue
            num = str(int(table.cell(i, 0).value)).replace('\n','').replace('\r','')
            api_purpose = table.cell(i, 1).value.replace('\n','').replace('\r','')
            api_host = table.cell(i, 2).value.replace('\n','').replace('\r','')
            request_method = table.cell(i, 4).value.replace('\n','').replace('\r','')
            request_data_type = table.cell(i, 5).value.replace('\n','').replace('\r','')
            request_data = table.cell(i, 6).value.replace('\n','').replace('\r','')
            encryption = table.cell(i, 7).value.replace('\n','').replace('\r','')
            check_point = table.cell(i, 8).value
            
            if encryption == 'MD5':              #如果数据采用md5加密,便先将数据加密
                request_data = json.loads(request_data)
                request_data['pwd'] = md5Encode(request_data['pwd'])
            status, resp, s = interfaceTest(num, api_purpose, api_host, request_url, request_data, check_point, request_methon, request_data_type, s)
            if status != 200 or check_point not in resp:            #如果状态码不为200或者返回值中没有检查点的内容,那么证明接口产生错误,保存错误信息。
                errorCase.append((num + ' ' + api_purpose, str(status), 'http://'+api_host+request_url, resp))
        return errorCase


#接受runTest的传参,利用requests构造HTTP请求
import requests

def interfaceTest(num, api_purpose, api_host, request_method,
                  request_data_type, request_data, check_point, s=None)
     headers = {'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8',
                      'X-Requested-With' : 'XMLHttpRequest',
                      'Connection' : 'keep-alive',
                      'Referer' : 'http://' + api_host,
                      'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36'
                }

     if s == None:
          s = requests.session()
     if request_method == 'POST':
          if request_url != '/login' :
              r = s.post(url='http://'+api_host+request_url, data = json.loads(request_data), headers = headers)         #由于此处数据没有经过加密,所以需要把Json格式字符串解码转换成**Python对象**
          elif request_url == '/login' :
              s = requests.session()
              r = s.post(url='http://'+api_host+request_url, data = request_data, headers = headers)          #由于登录密码不能明文传输,采用MD5加密,在之前的代码中已经进行过json.loads()转换,所以此处不需要解码
     else:
          logging.error(num + ' ' + api_purpose + '  HTTP请求方法错误,请确认[Request Method]字段是否正确!!!')
          s = None
          return 400, resp, s
     status = r.status_code
     resp = r.text
     print resp
     if status == 200 :
        if re.search(check_point, str(r.text)):
            logging.info(num + ' ' + api_purpose + ' 成功,' + str(status) + ', ' + str(r.text))
            return status, resp, s
        else:
            logging.error(num + ' ' + api_purpose + ' 失败!!!,[' + str(status) + '], ' + str(r.text))
            return 200, resp , None
        else:
            logging.error(num + ' ' + api_purpose + '  失败!!!,[' + str(status) + '],' + str(r.text))
            return status, resp.decode('utf-8'), None

import hashlib

def md5Encode(data):
      hashobj = hashlib.md5()
      hashobj.update(data.encode('utf-8'))
      return hashobj.hexdigest()

def sendMail(text):
      mail_info = get_conf()
      sender = mail_info['sender']
      receiver = mail_info['receiver']
      subject = '[AutomationTest]接口自动化测试报告通知'
      smtpserver = mail_info['smtpserver']
      username = mail_info['username']
      password = mail_info['password']
      msg = MIMEText(text,'html','utf-8')
      msg['Subject'] = subject
      msg['From'] = sender
      msg['To'] = ''.join(receiver)
      smtp = smtplib.SMTP()
      smtp.connect(smtpserver)
      smtp.login(username, password)
      smtp.sendmail(sender, receiver, msg.as_string())
      smtp.quit()


def main():
      errorTest = runTest('TestCase/TestCase.xlsx')
      if len(errorTest) > 0:
          html = '<html><body>接口自动化定期扫描,共有 ' + str(len(errorTest)) + ' 个异常接口,列表如下:' + '</p><table><tr><th style="width:100px;text-align:left">接口</th><th style="width:50px;text-align:left">状态</th><th style="width:200px;text-align:left">接口地址</th><th   style="text-align:left">接口返回值</th></tr>'
          for test in errorTest:
              html = html + '<tr><td style="text-align:left">' + test[0] + '</td><td style="text-align:left">' + test[1] + '</td><td style="text-align:left">' + test[2] + '</td><td style="text-align:left">' + test[3] + '</td></tr>'
              sendMail(html)

if __name__ == '__main__':
    main()

    

由于所有的操作必须在系统登录之后进行,一开始没有注意到cookie这一点,每读取一个测试用例,都会新建一个session,导致无法维护上一次请求的cookie。然后将cookie添加入请求头中,但是第二个用例仍然无法执行成功。后来用fiddler抓包分析了一下,发现cookie的值竟然是每一次操作后都会变化的!!!

所以只能通过session自动维护cookie。
在interfaceTest函数中,返回三个值,分别是HTTP CODE,HTTP返回值与session。再将上一次请求的session作为入参传入interfaceTest函数中,在函数内部判断session是否存在,如果不为None,那么直接利用传入的session执行下一个用例,如果为None,那么新建一个session。

不足之处

  • 框架十分简陋,只是简单想法的实现,对于编码的细节没有完善。
  • HTML的测试报告书写起来比较麻烦,可以考虑引入第三方库进行HTML测试报告的书写,将生成的HTML文件作为附件发送。
  • 只是针对公司内部的软件,换用其他平台就不适用,需要修改源码。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容