1.代码即用例(case的可读性和易用性) - Be5yond/pytest_demo GitHub Wiki

分层模块化组织自动化测试的实践demo

比如我们需要测试一个系统,🐬海豹系统(seal),对接一个审核平台🦛河马系统(hippo),两个系统分别有自己的前端界面和数据库,系统之间使用MQ进行通信。
drawio
我们需要对海豹的服务进行api自动化测试,按照分层设计自动化的代码,结构如下。

/lib/

Req.py ④
db.py ④
seal.py③
hippo.py③

/cases/

module_granularity/

conftest.py②
test_demo.py①

文件名后面的数字就是它的功能所出的层级,下面结合实际的代码来解释每一层都干了什么以及这么做的好处。

1.用例层

/cases/module_granularity/test_demo.py

使用模块和接口拼装业务场景完成测试
示例中的代码如下,只看测试方法中的内容

def test_audit_pass(self, user, auditor, data, scm):
    with allure.step('1.提交工单'):
        user.commit_order(data[0])
        user.stash('json.type', 'order_id')
    with allure.step('2.审核通过'):
        auditor.approve(data[1])
    with allure.step('3.验证工单状态为已通过'):
        user.get_order_detail(data[2])
        user.validate_resp(scm)

通过代码看出,这条用例的测试场景是3步,user提交一个工单,auditor对工单审核通过,然后user查询并校验工单状态验证审核结果。
通过模块化的封装实现代码即是用例,提高了case的😀可读性
封装成和业务相关的关键字,也提高了case的编写效率,即😀易用性
快速的组合user.commit_order -> auditor.reject - > user.get_order_detail即可完成一条工单被驳回的场景的自动化case。

2.模块层

/cases/module_granularity/conftest.py

分别对Seal类和Hippo类创建一个实例,user和auditor,对被测系统进行请求。 将一些接口操作组合成工作流,封装fixture,实现通用的业务步骤,比如创建一个create_valid_order,后续的测试方法中直接引用这个fixture,将testcase中的内容突出要测试的功能。

@pytest.fixture(scope='class')
def user(request):
    """Seal系统实例化一个user

    Returns:
        Session:  a request Session
    """
    env = request.config.getoption("--env")
    ression = Seal(env=env)
    return ression

...

@allure.step('创建一个审核通过的工单')     
@pytest.fixture(scope='class')
def create_valid_order(user, auditor):
    user.commit_order()
    order_id = user.jsan().json.type()
    auditor.approve({'order_id': order_id})
    user.cache['order_id'] = order_id

3.接口层

/lib/seal.py
/lib/hippo.py

创建Seal和Hippo两个类,方法实现和系统http接口的调用,有些测试需要操作数据库也可以放到这个文件里。 系统内通用数据的处理,Session级别和特定接口级别

比如:Seal系统中每个请求要有时间戳字段,或者要把请求的数据进行加密

def _get(self, path, para):
    """
    send get request
    Params:
    | para | query args dict |
    Return: response object
    """
    para = self._add_common_params(para)   # 每个get请求都默认添加了timestamp和trace_id字段

    self.response = self.send('GET', self._host+path, para=para)
    return self.response

def _add_common_params(self, args):
    trace_id = str(uuid.uuid4())
    self.cache['trace_id'] = trace_id
    args.update({'traceId': trace_id,
                 'timestamp': int(time.time()*1000),
                })
    return args

某些接口有自己的必填参数但是和我的业务测试并不强相关,可以放到这里的接口上自动加上去,使testdata更加专注于业务,且减少无意义的testdata的维护。
比如Seal系统中query_stats接口有4个参数[StartTime, EndTime, Type, Animal],测试需要查询过去一个小时内动物的统计信息,需要改变的只有animal的种类。 比起维护一个

[{'StartTime': '{% timestr(minutes=-65) %}',
  'EndTime': '{% timestr() %}',
  'Type': 'default',
  'Animal': 'cat'},
 {'StartTime': '{% timestr(minutes=-65) %}',
  'EndTime': '{% timestr() %}',
  'Type': 'default',
  'Animal': 'dot'},
 {'StartTime': '{% timestr(minutes=-65) %}',
  'EndTime': '{% timestr() %}',
  'Type': 'default',
  'Animal': 'horse'}.
 ...
]   

这样的数据,更希望testdata只关注业务相关的字段的数据
使用DefaultData将默认参数加到接口上,testdata只需要维护animal字段的数据即可。 参数中的'{% %}'字符串解释见数据渲染与校验

@DefaultData(data={                            # 调用query_stats函数时data中会默认添加StartTime,EndTime,Type字段。
    "StartTime": "{% timestr(minutes=-65) %}", # DefaultData装饰器代码实现在/lib/Req.py中
    "EndTime": "{% timestr() %}",
    "Type": "default"
})
def query_stats(self, data):
    self._post('/post', data)
# testdata
querydata = [{'Animal': 'dog'},{'Animal': 'cat'},{'Animal': 'horse'}]

4.会话层

/lib/Req.py

实现http session的建立 上下文数据的缓存cache 结果校验函数的实现。 详细见数据渲染与校验

/lib/db.py(本例中没有这个文件😁)

实现对数据库的连接