在系统开发过程当中,咱们通常我的参与的开发都是局部的,本身负责的一部分与其它团队成员的成果组合在一块儿才能实现用户的完整行为。好比常见的电商系统就有几个主要的流程javascript
这些模块复杂的足以造成各自的团队,不一样团队相互之间依据必定的接口来配合协做。在开发阶段能够基于接口各自独立开发,对于依赖其它模块的接口能够经过接口mock来完成。但mock方式自己是有使用场景的,一旦依赖方接口稳定后,就会调用实际服务来代替mock接口。css
场景A: 在上面提到的购物流程都完善的前提下,一个作物流系统的同事须要真实模似真实的用户场景产生物流单,他须要去相应的环境进行商品搜索,加入购物车,登陆,下单,付款等众多操做以后才会轮到他的模块登场。想一想都是件至关繁琐的事情,因此但愿有一种很快捷的方式代替人工去完成这些复杂且花时间的体力劳动。固然你也能够找测试妹子说:那个谁,帮我在某某环境下一个单我测试物流单逻辑。html
指望只须要执行一条命令,就能快速获得指定条件的订单。java
场景B: 在上面提到的购物流程中,任意修改某个流程都有可能对总体流程构成不一样程度的影响,有没有快速简捷的方式给咱们确认新的修改是否会影响主流程的变动呢?通常的做法就是须要测试人员去回归主流程,但这个成本比较高并且并必定那么的可靠,咱们须要一种成本低又不知疲倦的工具为咱们完成上面的工做,即便达不到100%人工测试的效果。python
指望在代码提交后,每日构建工具可以在修改以后的版本上执行主流程的测试用例,确保第一时间反馈出问题来,而不须要等到测试人员报BUG或者是线上用户反馈出问题来才被动知道。web
场景C:shell
在作一个稍微大型的功能后,为了确保各个子功能可以相互协做正常,咱们通常首先会对子功能作单元测试,而后对大功能作集成测试。在集成测试时,须要快速的定义测试用例并获得预期结果。json
集成测试的方式可能有不少种,好比我以前对http api接口的集成测试就依靠谷歌的postman(固然你也能够用junit来搞集成测试,在里面作各类断言)。但这种方式须要人工判断接口是否正确,由于此工具只具有执行能力并不具有判断能力。因此须要一款脱离特定语言的测试用例工具来帮助咱们完成,并且很是容易的可以对环境进行扩展,好比定义开发环境,测试环境,预上线环境,生产环境等。api
与python配合完成,因此须要在执行测试用例的机器上至少须要安装:数据结构
另外能够为behave建立单独的python环境,能够引入virtualenv。每次运行时经过source xxxenv/bin/activate来进入特定的python环境,source的目的就是替换环境变量。
其它组件
好比我主要测试http api,因此会用到下面一些库:
https://pythonhosted.org/behave/index.html
针对HTTP API的集成测试的自动化。上面所说的场景A/B/C是我暂时理解的,不一样的人在不现的阶段对自动化测试的需求不同。好比我作为业务系统的开发者,场景C能够帮助在我提交大型功能前作联调测试,系统稳定后,咱们去修改一个功能但依赖其它模块数据时,但愿快速产生预期数据那么场景A适合咱们。当咱们比较惶恐的修改某种功能时,场景B的主流程测试可以给咱们信心。
这里以文章前面说的购物场景为例。
是建立的一个python独立环境,可选。
behave相关的全部测试用例文件
behave提供了对于环境的控制,咱们能够在以下函数中添加本身的逻辑:
根据以前所述,集成测试可能涉及到不一样小组提供的api,因此能够定义以下数据:
CONFIG = { 'dev': { 'hosts': { 'product': 'http://localhost:1234/api/product', 'order':'http://localhost:1234/api/order', 'cart': 'http://localhost:1234/api/cart', 'pay': 'http://localhost:1234/api/pay', 'user': 'http://localhost:1234/api/user', 'logistics': 'http://localhost:1234/api/logistics', } }, 'test': { 'hosts': { 'product': 'http://test.jim.com/api/product', 'order':'http://test.jim.com/api/order', 'cart': 'http://test.jim.com/api/cart', 'pay': 'http://test.jim.com/api/pay', 'user': 'http://test.jim.com/api/user', 'logistics': 'http://test.jim.com/api/logistics', } }, }
而后在before_all中进行数据初始化,环境参数能够经过命令行的-D参数来控制,好比: -D env=dev
env = 'dev' def before_all(context): global env if (context.config.userdata.get('env')): env = context.config.userdata['env'] for k, v in CONFIG[env]['hosts'].items(): hosts[k] = v
封装通用的功能,便于使用测试用例简单方便,容易管理。建立一个service.py,主体结构以下:
#coding=utf-8 import requests, json hosts = {} class BddService(object): def __init__(self, api, data={}, headers={}): # 数据初始化 def __before__(self, context, data, url): # 处理数据,好比从测试用例中取参数,存放到context的requestData中,供后续的http请求使用 def __after__(self, context, r, url): # 从http request中获取数据,存放到context的responseData中,供后续的断言使用 def get(self, context, url, data={}): # 完成 http 调用
咱们须要在用例下面直观灵活的指定参数,能够经过behave提供的文本功能实现,它能够读一段多行文本到context.text变量中,而后咱们对去作处理。
When 搜索商品 """ {"name":"product1"} """
在before函数中完成值的填充,将最终的请求参数存放在context的requestData变量中。
def __before__(self, context, data, url): if context.text: print (context.text) o = json.loads(context.text) print (o) for k in o: data[k] = o[k] context.requestData = data
在after函数中完成取值,将HTTP请求的结构存放在context的responseData变量中。对于HTTP请求的结构支持两类数据,一类是json数据,一类是简单值(好比直接返回一个数字或者一个bool值或者是一个字符串)。
def __after__(self, context, r, url): try: context.response = r.json() if context.response.get('value', None): context.responseData = context.response.pop('value') try: if type(context.responseData) == str or type(context.responseData) == unicode: context.responseData = json.loads(context.responseData) except: if not hasattr(context, 'responseData') or context.responseData == None: except: context.response = r.text
上面逻辑中的get('value'),是特殊的数据结构(约定HTTP接口都会按必定的固定格式返回),好比我这里的结构,其中的value才是真正的业务数据。
{ "result": true, "error": null, "value": [ { "id": 1, "name": "product1", "productImage": null } ] }
behave默认状况下进行断言,须要在@then中完成断言,就须要为每一个测试用例编写独立的断言函数,能够作统一的封装,主体支持两类操做。
建立一个assert.py
从@then脚本后面读取多行文本,若是为空直接跳过断言。
@then(u'获得响应数据') def step_impl(context): if not context.text: return try: expect = json.loads(context.text) except: expect = context.text assertObj(expect, context.responseData) @then(u'获得响应') def step_impl(context): if not context.text: return expect = json.loads(context.text) assertObj(expect, context.response)
须要判断比较值的类型,由于只支持对基本类型的数据作断言,若是是列表就须要迭代到成员对象,至于迭代到基本数据类型(好比字符串,数字),而后利用operator库作处理。
def assertObj(expect, actual): if(type(expect) == list): for i in range(len(expect)): assertObj(expect[i], actual[i]) elif type(expect)==bool or type(expect)==str or type(expect)==int: assertObjKey(None, expect, actual) else: for k in expect: if(type(expect[k]) == dict or type(expect[k]) == list): if(type(actual[k]) != type(expect[k])): actual[k] = json.loads(actual[k]) assertObj(expect[k], actual[k]) else: assertObjKey(k, expect[k], actual[k]) def assertObjKey(k,originExpect,actualValue): #测试用例的值支持<,<=,<,<=,!=,== #样例数据:{"premium":"ge$0"} expectArray = str(originExpect).split("$"); if (len(expectArray) == 2): action = expectArray[0]; realExpect = expectArray[1] if action == "ge": assert operator.ge(actualValue, long(realExpect)) elif action == "gt": assert operator.gt(actualValue, long(realExpect)) elif action == "le": assert operator.le(actualValue, long(realExpect)) elif action == "lt": assert operator.lt(actualValue, long(realExpect)) elif action == "ne": assert operator.ne(actualValue, realExpect) elif action == "eq": assert operator.eq(actualValue, realExpect) else: assert originExpect == actualValue else: assert originExpect == actualValue
优化
上面代码中判断操做符有众多if,基本上就是一种比较一条if,比较冗余,现更新以下:利用getattr来获取方法,与java中的反射有点像。
method= getattr(operator, action,None) if(method!=None): method(actualValue, float(realExpect)) else: assert operator.eq(actualValue, realExpect)
能够根据调用的不一样业务接口建立不一样的step文件,好比以下图所示:
这里贴一个登陆的step脚本示例,其他的大同小异。
R = BddService('user') @given(u'初始化数据') def given_init(context): context.userName="jim" context.password="123456" @when(u'登陆') def step_impl(context): R.get(context,"/login",{"userName":context.userName,"password":context.password})
建立实例
python中建立实例时没有关键字new,这与其它语言有比较大的区别,刚开始老是以为别扭,如今看仍是别扭。
建立一个order.feature
Feature:订单流程测试 Scenario:常规下单流程 Given 初始化数据 When 登陆 Then 获得响应 """ {"result":true} """ When 搜索商品 """ {"name":"product1"} """ Then 获得响应 """ {"result":true} """ When 加入购物车 Then 获得响应 """ {"result":true} """ When 提交订单 Then 获得响应数据 """ 1 """ When 支付订单 Then 获得响应数据 """ 1 """ When 生成物流单 Then 获得响应数据 """ 1 """
只须要在对应的目录执行以下脚本便可愉快的执行测试用例。
behave -D env=test features/dev/order.feature
若是运行正常,会看出以下的输出,黄色的表明执行经过,若是执行失败会打印出错误信息,以及用例执行到哪一步报错。另外说明下,behave在执行正常的状况下会屏蔽经过print输出的日志,貌似能够经过参数调,有兴趣的能够研究研究。