HTTP联机接口测试的交易录放-录

HTTP联机接口测试中的交易录放–录

实现思路

之前文章中有提到过接口冒烟测试方法,这种方法主要通过解析应用文本日志,提取日志中的接口报文,并利用报文进行接口的冒烟测试。这种测试方法有效提升的接口测试的自动化及覆盖率,但是在实际应用中仍然会遇到一些问题。以下是问题及解决思路,请参考:

1.应用文本日志不记录报文内容或记录不规范:这个问题是最常见的问题,也是对原来的方法影响最大的问题。通常的做法是通过推动开发人员规范日志输出,但是这种方法沟通成本非常高,且主动权不在自己手中,于是果断放弃。偶然的机会,了解到了NGINX反向代理技术,非常适合我们的这个场景,利用NIGINX的lua插件,很轻松的就可以按照我们定义的标准格式记录HTTP请求的所有信息。通过将调用方host中服务方域名改为NGINX地址,或将网络域名指向NGINX地址,就轻松的实现了接口数据的捕获。

2.日志解析效率的问题:原方案是将日志保存至ES中,然后通过ES的API去解析接口,这就存在一个时效性的问题及解析效率的问题。由于解析规则通常比较复杂,一般都使用正则表达式的方式,且被采集端由于种种原因,时间并不是自然日期,且经常变化,每次只能对完整的数据集进行扫描,效率很差。后来做过一次优化,先通过ZABBIX AGENT获取被采集端时间,然后只针对该日期的索引进行扫描,但是对于交易量较大的应用,效率仍然是个问题。最后决定采用流数据的处理方式,logstash在写入ES的同时,再同步向KAFKA写入一份数据,配合消费端的规则处理程序,实现了一个日志流的处理,效率和时效性有了大幅提升。

3.测试框架的问题:不同应用日志规则不一样,虽然有了NGINX反向代理帮我们做了规范,但是我们仍然希望尽量减少对现有应用的改变,尽量使用应用日志。不同应用记录日志的格式千差万别,而测试框架又要保证普适性,不可能针对单一应用进行客户化开发,所以应用间的差异只能通过配置应用参数的方式去解决。我们将解析参数配置成数据字典,这里面也参考了开源APITESTENGINE的一些设计思路,将判断函数也写入了数据字典,保证了测试框架的简洁高效。

4.接口报文入库的问题:我们将接口发送和请求报文分开入库,二者以解析时间戳作为主键关联,因为我们希望发送报文作为测试的案例库,结果报文作为案例执行结果进行管理。一个案例可以多次测试产生多个结果,多次结果间可以进行自动比对,实现测试的自动化。

5.推广使用的问题:业务测试人员喜欢前台具象化的操作,技术人员喜欢后台大规模批量自动化调度。因此使用DJANGO为业务测试人员定制了接口测试管理的框架,针对不同应用的需求进行定制化开发改造,基本上可以在一周内上线一个面向业务测试人员的应用接口测试平台。

通过以上方法,基本实现了一个通用的联机HTTP接口报文录制框架,效率和稳定性较最初了方案有了很大的提高。

不再废话,直接上代码

非科班出身,请见谅

from pykafka import KafkaClient
import re
import json
import datetime
from urllib import request
import time
import httplib2
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
import hashlib
import multiprocessing

Base = declarative_base()
class Api_content(Base):
    __tablename__ = "API_CONTENT"
    seq = Column("seqxml", Integer(), primary_key=True)
    appname = Column("appname", String(10))
    ip = Column("ip", String(20))
    servername = Column("servername", String(20))
    tradeid = Column("tradeid", String(10))
    md5 = Column("md5", String(32))
    logtime = Column("logtime", DateTime())
    api_text = Column("api_text", Text())
    update_date = Column("update_date", DateTime(), default=datetime.datetime.now(), onupdate=datetime.datetime.now())
    flag = Column("flag", Integer(), default=0)
    respid = Column("respid", String(100))
    col2 = Column("col2", String(20))
    col3 = Column("col3", String(20))


class Api_result(Base):
    __tablename__ = "API_RESULT"
    seq = Column("seqxml", Integer(), primary_key=True)
    appname = Column("appname", String(10))
    ip = Column("ip", String(20))
    servername = Column("servername", String(20))
    tradeid = Column("tradeid", String(10))
    md5 = Column("md5", String(32))
    logtime = Column("logtime", DateTime())
    resp_text = Column("resp_text", Text())
    update_date = Column("update_date", DateTime(), default=datetime.datetime.now(), onupdate=datetime.datetime.now())
    flag = Column("flag", String(10))
    respid = Column("respid", String(100))
    col2 = Column("col2", String(20))
    col3 = Column("col3", String(20))


def apimd5(src):
    m = hashlib.md5()
    m.update(src.encode('GBK'))
    return m.hexdigest()


def checkmd5(engine, inputmd5):
    conn = engine.connect()
    s = select([Api_content.md5]).where(Api_content.md5 == inputmd5)
    # print(s)
    r = conn.execute(s).fetchone()
    if r:
        return 1
    else:
        return 0



def findcase(app, logtext,seq):
    engine = create_engine('oracle://mctest:mctest@192.168.1.1/mcdb')
    DBsession = sessionmaker(bind=engine)
    session = DBsession()
    try:
        # 提取tradeid,apitext,非贪婪模式
        s = logtext.split("###")
        # print('s',s)
        host = s[0]
        timestamp = s[1]
        message = s[2]
        # print("message",message)
        tradeidrule = re.compile(r'' + app["param"]["send"]["tradeid"]["str"] + '', re.S | re.I)
        tradeid = tradeidrule.search(message).group(1).strip()
        # print("tradeid",tradeid)
        apitextrule = re.compile(r'' + app["param"]["send"]["apitext"]["str"] + '', re.S | re.I)
        apitext = apitextrule.search(message).group(1)
        # print("apitext",apitext)
        if "sendid" in app["param"]["send"].keys() :
            sendidrule = re.compile(r'' + app["param"]["send"]["sendid"]["str"] + '', re.S | re.I)
            sendid = sendidrule.search(message).group(1)
            # print("respid",respid)
        else:
            sendid = ""
        logtime = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ") + datetime.timedelta(hours=8)
        md5 = apimd5(apitext)
        if checkmd5(engine, md5) == 0:
            try:
                api_content = Api_content(
                    seq=seq,
                    appname=app["appname"],
                    # ip=app["param"]["ip"],
                    servername=host,
                    tradeid=tradeid,
                    md5=md5,
                    logtime=logtime,
                    api_text=apitext,
                    respid=sendid,
                )
                session.add(api_content)
                session.commit()
                print(datetime.datetime.now(), app["appname"]," 插入一条发送记录 ", host, sendid)
            except Exception as e:
                # pass
                print(message, e)
        else:
            print(datetime.datetime.now(), app["appname"], "命中MD5,跳过",md5)
    except Exception as e:
        pass
        #print(e)


def catchresp(app, logtext,seq):
    engine = create_engine('oracle://mctest:mctest@192.168.1.1/mcdb')
    DBsession = sessionmaker(bind=engine)
    session = DBsession()
    try:
        s = logtext.split("###")
        # print('s',s)
        host = s[0]
        timestamp = s[1]
        message = s[2]
        tradeidrule = re.compile(r'' + app["param"]["recv"]["tradeid"]["str"] + '', re.S | re.I)
        tradeid = tradeidrule.search(message).group(1).strip()
        # print("tradeid",tradeid)
        resptextrule = re.compile(r'' + app["param"]["recv"]["resptext"]["str"] + '', re.S | re.I)
        resptext = resptextrule.search(message).group(1)
        # print("resptext",resptext)
        if "respid" in app["param"]["recv"].keys():
            respidrule = re.compile(r'' + app["param"]["recv"]["respid"]["str"] + '', re.S | re.I)
            respid = respidrule.search(message).group(1)
        else:
            respid=""
        if "respstatus" in app["param"]["recv"].keys():
            respstatusrule = re.compile(r'' + app["param"]["recv"]["respstatus"]["str"] + '', re.S | re.I)
            respstatus = respstatusrule.search(message).group(1)
            respfunc = eval(app["param"]["recv"]["respstatus"]["func"])
            respcode=respfunc(respstatus)
            print("respcode",respcode)
        else:
            respcode=0
        # print("respid",respid)
        logtime = datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ") + datetime.timedelta(hours=8)
        # md5 = apimd5(apitext)
        if 0 == 0:
            try:
                api_result = Api_result(
                    seq=seq,
                    appname=app["appname"],
                    # ip=app["param"]["ip"],
                    servername=host,
                    tradeid=tradeid,
                    logtime=logtime,
                    resp_text=resptext,
                    respid=respid,
                    flag=respcode
                )
                session.add(api_result)
                session.commit()
                print(datetime.datetime.now(), app["appname"], " 插入一条结果记录 ", host, respid)
            except Exception as e:
                # pass
                print(message, e)
        else:
            print(datetime.datetime.now(), app["appname"], "命中MD5,跳过")
    except Exception as e:
        pass
        # print (message,e)

def myjob(app):
    topic = app['param']['topic']
    print(topic," JOB START .....")
    # 启动kafka监控进程
    client = KafkaClient(hosts="192.168.1.2:9092")
    topic = client.topics[str.encode(str(topic))]
    consumer = topic.get_simple_consumer(
        consumer_group=b"elk",
        auto_commit_enable=True,
        auto_commit_interval_ms=1,
        consumer_id=str.encode(str(topic))
    )
    for x in consumer:
        if x is not None:
            seq = int(datetime.datetime.strftime(datetime.datetime.now(), "%Y%m%d%H%M%S%f"))
            # print(x.value.decode('utf-8'))
            findcase(app, x.value.decode('utf-8'), seq)
            if "recv" in app["param"].keys():
                catchresp(app, x.value.decode('utf-8'), seq)


if __name__ == '__main__':
    applist = [{"appname": "F-AAAA", "param": {"topic": "aaaa",
                                               "search": {"col": "message", "val": '上送报文:'},
                                               "send": {
                                                   "tradeid": {"str": "INFO   - (.*?),"},
                                                   "apitext": {"str": "上送报文:(.*?)$"},
                                                   "sendid": {"str": "\"eventid\":\"(.*?)\""}
                                               },
                                               "recv": {
                                                   "tradeid": {"str": "INFO   - (.*?),"},
                                                   "respid": {"str": "eventid=(.*?),"},
                                                   "resptext": {"str": '返回报文:.*({.*})'},
                                                   "respstatus": {"str": "\"RETCODE\":\"(.*?)\"","func":"lambda x:  1 if int(x)==0 else  2"}
                                               }
                                               }
                },
               {"appname": "F-BBBB", "param": {"topic": "bbbb",
                                               "search": {
                                                   "col": "msgtext",
                                                   "val": '收到信使消息'},
                                               "send": {
                                                   "tradeid": {
                                                       "str": "收到信使消息: .{33}(.{10})"},
                                                   "apitext": {
                                                       "str": "收到信使消息: (.*)"}
                                               }
                                               }
                }
               ]
    for app in applist:
        if app['appname'] in ["F-AAAA","F-BBBB"]:
            p=multiprocessing.Process(target=myjob,args=(app,))
        p.start()
    p.join()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值