【api-object】多接口组合用例该怎么写?

框架思想采用了api-object,将接口全部单独封装。
当用例是多个接口组成的业务场景时,我目前的做法是,在testcase里分别去调用需要用到的接口,但是发现这样写下来,一个testcase的代码会有点多。

接口封装

class AlarmApi(BaseApi):
    _host = conf.get_str('env', 'url')
    _api_host = conf.get_str('env', 'api_url')

    def get_alarm_service_list(self, org_id):
        """
        获取指定机构的报警服务列表
        :param org_id: 组织机构编码
        :return:
        """
        data = {
            "method": "get",
            "url": self._host + conf.get_str('api', 'alarm_service_list'),
            "params": {'org': org_id, 'index': 1, 'page': 10}
        }
        response = self.send_http(data)
        return response.json()

    def get_alarm_host(self, alarm_service_code):
        """
        获取指定报警服务的报警主机列表
        :param alarm_service_code: 报警服务编码
        :return:
        """
        data = {
            "method": "get",
            "url": self._host + f'/api/web/vip/device/v1/alarmsystem/{alarm_service_code}/host',
            "params": {'index': 1, 'page': 10}
        }
        response = self.send_http(data)
        return response.json()

    def get_alarm_subsystem(self, alarm_host_code):
        """
        获取指定报警主机的报警子系统列表
        :param alarm_host_code: 报警主机编码
        :return:
        """
        data = {
            "method": 'get',
            "url": self._host + f'/api/web/vip/device/v1/alarmsystem/{alarm_host_code}/subsystem',
            "params": {'index': 1, 'page': 10}
        }
        response = self.send_http(data)
        return response.json()

    def get_subsystem_status(self, center_code, alarm_subsystem_code):
        """
        查询指定报警子系统状态
        :param center_code: 中心编码
        :param alarm_subsystem_code: 报警子系统编码
        :return:
        """
        data = {
            "method": "get",
            "url": self._api_host + f"/api/device/alarmsystem/v1/status/{center_code}/145",
            "params": {'codes': alarm_subsystem_code[-4:]}
        }
        response = self.send_http(data)
        return response.json()

    def control_subsystem(self, command: int, alarm_subsystem_code, token):
        """
        控制子系统:布防、撤防、清除报警
        :param command: 1-清除报警,2-布防,3-撤防
        :param alarm_subsystem_code:报警子系统编码
        :param token:鉴权信息,登录客户端获取
        :return:
        """
        datas = {"command": command, "operationmode": 1, "sourceapp": "OneAppVideoAppG1", "customext": "控制子系统"}
        data = {
            "method": "put",
            "url": self._api_host + f"/api/device/alarmsystem/v1/subsystem/{alarm_subsystem_code}",
            "data": datas,
            "headers": {"X-CMS-Token": token}
        }
        response = self.send_http(data)
        return response.json()

用例

class TestAlarmControl(AlarmApi):

    def test_arm_subsystem(self, get_cookie, get_token):
        """验证布防子系统成功"""
        cookie = get_cookie
        token = get_token
        # 获取顶级机构
        org_id = self.get_org_id()[0][0]
        # 1、获取报警服务code
        alarm_service_code = self.jsonpath(self.get_alarm_service_list(org_id), '$..code')[0]
        # 2、获取报警主机code
        alarm_host_code = self.jsonpath(self.get_alarm_host(alarm_service_code), '$..code')[0]
        # 3、获取报警子系统code
        alarm_subsystem_code = self.jsonpath(self.get_alarm_subsystem(alarm_host_code), '$..code')[0]
        # 4、获取当前运行环境中心编码
        center_code = self.get_center_code(cookie)
        # 5、查询报警子系统状态
        pre_status = self.get_subsystem_status(center_code, alarm_subsystem_code)
        # 6、判断该子系统当前状态,若为
        state = self.jsonpath(pre_status, '$..State')[0]
        if 'Disarmed' in state:
            response = self.control_subsystem(2, alarm_subsystem_code, token)
        else:
            self.control_subsystem(3, alarm_subsystem_code, token)
            response = self.control_subsystem(2, alarm_subsystem_code, token)
        time.sleep(10)
        after_status = self.get_subsystem_status(center_code, alarm_subsystem_code)
        after_state = self.jsonpath(after_status, '$..State')[0]
        try:
            assert response == {'Success': True, 'ErrorMessage': None, 'ErrorCode': None, 'StackMessage': None}
            assert state != after_state and 'Armed' in after_state
        except AssertionError as e:
            logger.info(f"布防接口响应:{response}")
            logger.info(f"报警子系统状态信息,布防前:{pre_status},布防后:{after_status}")
            logger.error('断言失败!')
            raise e

想问下,api-object这种框架该如何写多接口case?是像UI自动化那样,将测试步骤写到yaml中驱动还是其他方案?

跟用不用数据驱动和yaml没关系,就算是用yaml其实也是分yaml中的page object和用例的。
你的获取数据的api,调用了很多次,就是为了获取一个数据集合,所以你可以直接也封装到po中,单个的api封装,多个api也可以封装。总之po的中的方法代表的是你的业务操作场景,比如封装一个 get_data_from_alarm, 返回一个包含所有数据的词典结构,这样你的jsonpath和多次的api调用都会被隐藏在po内部了。用例就会特别清爽。是否要封装为po要从如下因素判断

  • 是否是业务操作场景,可否代表业务。
  • 是否每次使用的时候不关注底层
  • 会否存在复用,如果复用性不强,容易封装出很多琐碎的繁杂api,那就说明你封装的层次不对
  • 大量的基本编程细节不要暴露到用例中,比如jsonpath,可以封装到utils中,或者po中

噢~明白了~谢谢思寒大佬~!思寒大佬的每次解答都能让人醍醐灌顶~