3.数据驱动 - Be5yond/pytest_demo GitHub Wiki

数据驱动

接口测试经常需要对同一个接口或者同一个业务场景进行不同数据参数和校验结果,业务流程没有变化,将测试用例和数据进行分离来达到代码复用。

不同的数据格式推荐的存储方式

测试数据存储方式 适用参数类型
代码变量 参数比较少,且简单的接口
表格文件 数据较多,需要改变的变量很多
数据库 数据结构复杂,json格式嵌套深

下面来举例说明

1.存储到变量

如下面示例代码,测试一个用户登录接口,通过user.login函数调用登录接口并校验返回的结果.
两个测试方法test_user_login_pass, test_user_login_fail分别测试登录成功和失败,校验结果,成功返回code=200,失败返回code=401.
使用pytest.mark.parametrize来进行数据传参data为登录接口的数据,scm为schema数据校验模板。 参数中的'{{ }}'字符串解释见数据渲染与校验

class TestUserClass(object):
    @allure.feature("用户登陆")
    @pytest.mark.parametrize('data,scm', [
        ({'username': '{{ user }}', 'password': '{{ pwd }}'}, {'code': 200}),
    ])
    def test_user_login_pass(self, user, data, scm):
        with allure.step('login'):
            user.login(data)
            user.validate_resp(scm)

    @allure.feature("用户登陆")
    @pytest.mark.parametrize('data,scm', [
        ({'username': '{{ user }}', 'password': 'wrong'}, {'code': 401}),
        ({'username': 'wrong', 'password': '{{ pwd }}'}, {'code': 401})
    ])
    def test_user_login_fail(self, user, data, scm):
        with allure.step('login'):
            user.login(data)
            user.validate_resp(scm)

login_pass和login_fail方法内的代码是一样的,本例为了区分测试场景将测试数据进行了分组,也可以将数据放到一起传给pytest.mark.parametrize,
或者把测试数据放到数据文件中.如
testdata.py

login_data = [
        ({'username': '{{ user }}', 'password': '{{ pwd }}'}, {'code': 200}),
        ({'username': '{{ user }}', 'password': 'wrong'}, {'code': 401}),
        ({'username': 'wrong', 'password': '{{ pwd }}'}, {'code': 401})
    ])

test_case.py

import test_data

class TestUserClass(object):
    @allure.feature("用户登陆")
    @pytest.mark.parametrize('data,scm', testdata.login_data)
    def test_user_login_pass(self, user, data, scm):
        with allure.step('login'):
            user.login(data)
            user.validate_resp(scm)

可执行本项目下cases\datadriven_plain.py 查看运行结果。

2.表格文件(excel)

比如业务中遇到某个查询接口,传参枚举值很多(type, country, status, price)传参结构比较固定,比较适合用表格文件来管理测试数据。
将测试数据用表格管理,方便编辑可以快速编写接口的测试用例。
本着约定优于配置的原则,使用excel表格管理测试数据时,测试数据存放在datas目录下
测试类名 <==============> excel文件名称
测试方法名 <============> excel中sheet名称
sheet中的每一行即是一条测试用例
例如: 项目下cases\datadriven_xls.py 中的测试代码如下

class TestUserClass(object):
    @allure.feature("用户登陆")
    def test_user_login(self, user, data):
        with pytest.allure.step(data['id']):
            data.pop('id')
            scm = json.loads(data.pop('scm'))

        with pytest.allure.step('login'):
            user.login(data)
            user.validate_resp(scm)

    @allure.feature("用户登陆")
    def test_user_login_fail(self, user, data):
        with pytest.allure.step(data['id']):
            data.pop('id')
            scm = json.loads(data.pop('scm'))

        with pytest.allure.step('login'):
            user.login(data)
            user.validate_resp(scm)

对应的测试数据,datas目录下TestUserClass.xls文件,test_user_login_pass对应user_login_pass sheet下的数据,test_user_login_fail对应user_login_fail
sheet中的数据
excel_data

实现原理

使用pytest提供的pytest_generate_tests hook来实现自定义的参数化函数。/cases/datadriven_xls/conftest.py 下

def pytest_generate_tests(metafunc):
    #如果testmethod中引用了data fixture
    if 'data' in metafunc.fixturenames:    
        argnames = 'data' 
        # 使用metafunc反射找到当前执行的用例类名和方法名,作为参数查找测试数据。
        argvalues = gen_data(metafunc.cls.__name__, metafunc.function.__name__)   
        # 使用parametrice方法参数化配置
        metafunc.parametrize(argnames, argvalues, ids=idfn, scope="function")

gen_data函数在 /comm/xls.py内,查找对应文件下sheet表格中的数据

3.数据库管理(mongodb)

有些接口的数据结构比较复杂,比如某个接口的请求数据

{
    'operation': 'update',
    'data': {
        'type': 'ax',
        'resources': [
            {'name': 'foo', 'status': 3, 'desc': '...'},
            {'name': 'bar', 'status': 1, 'desc': '...'},
        ]
    }
}

现实中有比这个更加复杂的多的数据,不方便用表格来进行管理,这种测试数据比较适合使用基于文档的数据库。 类似用excel表格管理测试数据时的模式,测试数据数据库中,以mongodb为例
测试类名 <==============> mongo中一个database
测试方法名 <============> mongodb中的一个集合(collection)
collection中的每一个文档(document)即是一条测试用例。
例如: 项目下cases\datadriven_mongo.py 中的测试代码。
和excel表格管理类似,对应的测试数据,为数据库中UserClass中user_login下的数据