当前位置: 首页 > article >正文

中间件自动化测试框架cmdlinker

背景

作为一个中间件的测试工程师,如何对于中间件提供的命令进行自动化的回归,这一直是一个难题,市面上好像缺乏了对于命令进行自动化回归的合理解决方案。
常见方式有下面两种:

  • 直接写字符串的命令,然后使用各种编程语言的SSH库进行连接,然后执行命令字符串,获取执行结果,如果需要对传入的参数进行校验,还需要各种提取字符串,或者设置各种命令变量,比较繁琐
  • 编写命令对象,通过操作命令对象set/get方式,生成命令字符串给SSH库进行命令执行,如果需要对传入的参数进行校验,只需要通过命令对象get的方式进行获取,当然缺点就是,每次都需要进行大量的工作去编写命令对象,重复性劳动

通过对比之后,会发现,使用第二种方式更加直观、简洁,提取响应数据也更加方便,但是第二种方式对于大量重复性的去编写命令对象的方式,也让人十分恶心。如果有一个工具能够帮助大家去生成命令对象,然后通过调用命令对象执行请求响应的方式就能让大家从这种困境中走出来。

所以引入了cmdlinker这个开源python库,cmdlinker能够帮助我们做什么?

  • 通过编写命令对象yaml的方式,生成命令对象,无需手动进行命令对象代码的编写
  • 命令对象自带runner,可以直接获取命令执行的请求响应
  • 支持本地shell执行及远程ssh执行的方式

安装

pip3 install cmdlinker

文档地址

https://github.com/chineseluo/cmdlinker

案例演示:

使用docker命令举例,查询容器,获取容器配置信息,断言容器的ID是否包含请求的ID
编写yaml文件:

entry: "docker"
mapping_entry: "Docker"
module_name: "docker"
class_name: "Docker"
out_path: "./"
mode: "SSH"
parameters:
  - mapping_name: "ps"
    original_cmd: "ps"
    value: False # 是否需要值
    mutex: False # 是否互斥
    default: None
  - mapping_name: "inspect"
    original_cmd: "inspect"
    value: True # 是否需要值
    mutex: False # 是否互斥
    default: None
  - mapping_name: "f"
    original_cmd: "-f"
    value: True # 是否需要值
    mutex: False # 是否互斥
    default: None

执行生成命令:

CmdLinker init -f ./docker.yaml

生成python命令对象

from loguru import logger
from typing import Union, Text
from cmdlinker.builtin.exception import CmdLinkerMutexException, CmdLinkerMulMutexException
from cmdlinker.builtin.ssh_utils import SSHClient
from cmdlinker.builtin.shell_utils import ShellClient


class Cmds:
    def __init__(self):
        self.index = 0
        self.CMD_LIST = []


class Runner:

    def __init__(self):
        self._mutexs = []
        self._gcs = []
        self._not_mutexs = []
        self.pre: Runner = object
        self.root: Runner = object
        self.next: Union[Runner] = object
        self.main_cmd: Text = None
        self.cmds: Cmds = None
        self.ssh_client: SSHClient = None
        self.sudo: bool = True
        self.timeout: int = 20

    def _exclude(self, cmd_obj):
        if not cmd_obj.mark:
            logger.debug(f"命令对象:{cmd_obj.__class__.__name__}未被使用")
            return False
        else:
            return True

    def _get_execute_cmd(self, cmd_obj):
        if cmd_obj.need_value:
            return f"{cmd_obj.meta_data['original_cmd']} {cmd_obj.value}"
        else:
            return f"{cmd_obj.meta_data['original_cmd']}"

    def _get_log_desc(self):
        if self.pre == self.root:
            log_desc = "子"
        elif self.pre is None and self.root == self:
            log_desc = "根"
        else:
            log_desc = "父"
        return log_desc

    def cmd_checker(self):
        log_desc = self._get_log_desc()
        logger.info("==" * 20 + f"开启{log_desc}命令{self.__class__.__name__}合法性检查" + "==" * 20)
        for cmd_obj_str in vars(self).keys():
            cmd_obj = getattr(self, cmd_obj_str)
            if isinstance(cmd_obj, BaseCmd):
                if cmd_obj_str == "next":
                    continue
                if not self._exclude(cmd_obj):
                    continue
                if cmd_obj.mutex:
                    self._mutexs.append(cmd_obj)
                    logger.debug(f"{self.main_cmd}添加互斥对象:{cmd_obj},最新互斥对象列表:")
                else:
                    self._not_mutexs.append(cmd_obj)
                    logger.debug(f"{self.main_cmd}添加非互斥对象:{cmd_obj},最新非互斥对象列表:{self._not_mutexs}")
        if len(self._mutexs) > 1:
            raise CmdLinkerMulMutexException(self.__class__.__name__, self._mutexs)
        if len(self._mutexs) != 0 and len(self._not_mutexs) != 0:
            raise CmdLinkerMutexException(self.__class__.__name__, self._mutexs, self._not_mutexs)
        if self.pre == self.root:
            logger.debug(f"启用命令对象{self.__class__.__name__} 根命令: {self.root.__class__.__name__} 检查")
            self.pre.cmd_checker()
        elif self.pre is None and self.root == self:
            pass
        else:
            logger.debug(f"启用命令对象{self.__class__.__name__}父命令: {self.pre.__class__.__name__} 检查")
            self.pre.cmd_checker()
        self._not_mutexs = []
        self._mutexs = []
        logger.info("==" * 20 + f"{log_desc}命令{self.__class__.__name__}合法性检查通过" + "==" * 20)

    def runner(self):
        self.cmd_checker()
        cmd = self.exec_cmd()
        self.cmds.CMD_LIST = []
        return self.ssh_client.run_cmd(cmd, timeout=self.timeout)

    def collector(self):
        return self.cmds.CMD_LIST.sort(key=lambda cmd_obj: cmd_obj.index)

    def exec_cmd(self):
        self.cmds.CMD_LIST.sort(key=lambda cmd_obj: cmd_obj.index)
        cmd_list = [self.main_cmd] + [self._get_execute_cmd(cmd) for cmd in self.cmds.CMD_LIST]
        if self.sudo:
            cmd_list.insert(0, "sudo")
        logger.info(f"执行命令列表:{cmd_list}")
        cmd = " ".join(cmd_list)
        return cmd


class BaseCmd(Runner):
    def __init__(self):
        super().__init__()
        self.cmds: Cmds = None
        self.meta_data = None
        self.mutex = None
        self.need_value = None
        self.has_child_cmd = None
        self.gc = None
        self.child_cmd = None
        self.default_value = None
        self.value = None
        self.mark = None
        self.index = None
        self.level = 0
        self._mutexs = []
        self._gcs = []
        self._not_mutexs = []
        self.main_cmd = "docker"
        self.pre: object = object
        self.root: object = object
        self.next: Union[object] = object
        self.ssh_client: SSHClient = None


class Ps(BaseCmd):
    def __init__(self, root_obj, pre_obj):
        super().__init__()
        self.pre: Docker = pre_obj
        self.root: Docker = root_obj
        self.next: Union[Ps] = self
        self.meta_data = {'mapping_name': 'ps', 'original_cmd': 'ps', 'value': False, 'mutex': False, 'default': 'None',
                          'has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker', 'root_cmd': 'Docker'}
        self.level = 2
        self.mutex = False
        self.need_value = False
        self.has_child_cmd = False
        self.child_cmds = []
        self.gc = False

        self.default_value = "None"
        self.value = self.default_value
        self.mark = False


class Inspect(BaseCmd):
    def __init__(self, root_obj, pre_obj):
        super().__init__()
        self.pre: Docker = pre_obj
        self.root: Docker = root_obj
        self.next: Union[Inspect] = self
        self.meta_data = {'mapping_name': 'inspect', 'original_cmd': 'inspect', 'value': True, 'mutex': False,
                          'default': 'None', 'has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker',
                          'root_cmd': 'Docker'}
        self.level = 2
        self.mutex = False
        self.need_value = True
        self.has_child_cmd = False
        self.child_cmds = []
        self.gc = False

        self.default_value = "None"
        self.value = self.default_value
        self.mark = False


class Format(BaseCmd):
    def __init__(self, root_obj, pre_obj):
        super().__init__()
        self.pre: Docker = pre_obj
        self.root: Docker = root_obj
        self.next: Union[Format] = self
        self.meta_data = {'mapping_name': 'format', 'original_cmd': '--format', 'value': True, 'mutex': False,
                          'default': 'None', 'has_child_cmd': False, 'child_cmds': [], 'parent_cmd': 'Docker',
                          'root_cmd': 'Docker'}
        self.level = 2
        self.mutex = False
        self.need_value = True
        self.has_child_cmd = False
        self.child_cmds = []
        self.gc = False

        self.default_value = "None"
        self.value = self.default_value
        self.mark = False


class Docker(Runner):
    def __init__(self, host=None, name=None, pwd=None, port="22", timeout="60", sudo=False):
        super().__init__()
        self.cmds: Cmds = Cmds()
        self.pre: object = None
        self.root: Docker = self
        self.next: Union[Ps, Inspect, Format,] = None
        self.main_cmd = "docker"
        self._mutexs = []
        self._gcs = []
        self._not_mutexs = []
        self.mode = "SSH"
        self.ssh_client = SSHClient(host, name, pwd, port)
        self._ps: Ps = Ps(self, self)
        self._inspect: Inspect = Inspect(self, self)
        self._format: Format = Format(self, self)

    def _set_ps(self):
        self._ps.mark = True
        self._ps.index = self.cmds.index
        self.cmds.index += 1
        self.next = self._ps
        self.cmds.CMD_LIST.append(self._ps)
        self._ps.cmds = self.cmds
        self._ps.ssh_client = self.ssh_client

    def tset_ps(self):
        """
        传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级
        """
        self._set_ps()
        return self._ps

    def hset_ps(self, ):
        """
        保持HOLD模式,该方法返回该对象本身,不返回子对象
        """
        self._set_ps()
        return self

    def _set_inspect(self, value=None):
        self._inspect.mark = True
        self._inspect.index = self.cmds.index
        self.cmds.index += 1
        self.next = self._inspect
        self.cmds.CMD_LIST.append(self._inspect)
        self._inspect.cmds = self.cmds
        self._inspect.ssh_client = self.ssh_client

        if self._inspect.need_value:
            self._inspect.value = value

    def tset_inspect(self, value=None):
        """
        传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级
        """
        self._set_inspect(value=value)
        return self._inspect

    def hset_inspect(self, value=None):
        """
        保持HOLD模式,该方法返回该对象本身,不返回子对象
        """
        self._set_inspect(value=value)
        return self

    def _set_format(self, value=None):
        self._format.mark = True
        self._format.index = self.cmds.index
        self.cmds.index += 1
        self.next = self._format
        self.cmds.CMD_LIST.append(self._format)
        self._format.cmds = self.cmds
        self._format.ssh_client = self.ssh_client

        if self._format.need_value:
            self._format.value = value

    def tset_format(self, value=None):
        """
        传递TRANSMIT模式,可以获取子命令对象,可通过,root/pre/next,控制命令对象的根/父/子级
        """
        self._set_format(value=value)
        return self._format

    def hset_format(self, value=None):
        """
        保持HOLD模式,该方法返回该对象本身,不返回子对象
        """
        self._set_format(value=value)
        return self

    def ps(self) -> Ps:
        return self._ps

    def inspect(self) -> Inspect:
        return self._inspect

    def format(self) -> Format:
        return self._format

执行测试

进行请求响应断言(PS:自行替换下面的host/name/pwd/port等参数验证)

if __name__ == '__main__':
    docker = Docker(host="192.168.1.2", name="root", pwd="xxxx")
    ps_res = docker.hset_ps().hset_format("{{.ID}}").runner()
    inspect_res = docker.hset_inspect(ps_res["stdout"]).runner()
    assert ps_res["stdout"].strip() in inspect_res["stdout"][0]["Id"]

检查日志

C:\Users\Dell\AppData\Local\Programs\Python\Python39\python.exe E:\开源项目\CmdLinker\tests\docker.py 
2025-01-03 16:38:03.116 | INFO     | __main__:cmd_checker:53 - ========================================开启根命令Docker合法性检查========================================
2025-01-03 16:38:03.116 | DEBUG    | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Ps object at 0x000001ADA171E9A0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>]
2025-01-03 16:38:03.116 | DEBUG    | __main__:_exclude:31 - 命令对象:Inspect未被使用
2025-01-03 16:38:03.116 | DEBUG    | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Format object at 0x000001ADA171EAF0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Format object at 0x000001ADA171EAF0>]
2025-01-03 16:38:03.116 | INFO     | __main__:cmd_checker:81 - ========================================根命令Docker合法性检查通过========================================
2025-01-03 16:38:03.116 | INFO     | __main__:exec_cmd:97 - 执行命令列表:['sudo', 'docker', 'ps', '--format {{.ID}}']
2025-01-03 16:38:03.693 | INFO     | cmdlinker.builtin.logger_operation:console_output:18 - 
================== cmd request  details ==================
execute cmd on host : 192.168.1.2
execute cmd : sudo docker ps --format {{.ID}}

2025-01-03 16:38:03.694 | WARNING  | cmdlinker.builtin.ssh_utils:run_cmd:67 - 尝试对response stdout进行json转换失败,原样输出
2025-01-03 16:38:03.694 | INFO     | cmdlinker.builtin.logger_operation:console_output:18 - 
================== cmd response  details ==================
status_code : 0
execute_time : 578ms
stdout   : 7393aa6334e6

stderr   : 

2025-01-03 16:38:03.694 | INFO     | __main__:cmd_checker:53 - ========================================开启根命令Docker合法性检查========================================
2025-01-03 16:38:03.694 | DEBUG    | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Ps object at 0x000001ADA171E9A0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>]
2025-01-03 16:38:03.694 | DEBUG    | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Inspect object at 0x000001ADA171EA60>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Inspect object at 0x000001ADA171EA60>]
2025-01-03 16:38:03.694 | DEBUG    | __main__:cmd_checker:66 - docker添加非互斥对象:<__main__.Format object at 0x000001ADA171EAF0>,最新非互斥对象列表:[<__main__.Ps object at 0x000001ADA171E9A0>, <__main__.Inspect object at 0x000001ADA171EA60>, <__main__.Format object at 0x000001ADA171EAF0>]
2025-01-03 16:38:03.694 | INFO     | __main__:cmd_checker:81 - ========================================根命令Docker合法性检查通过========================================
2025-01-03 16:38:03.694 | INFO     | __main__:exec_cmd:97 - 执行命令列表:['sudo', 'docker', 'inspect 7393aa6334e6\n']
2025-01-03 16:38:03.922 | INFO     | cmdlinker.builtin.logger_operation:console_output:18 - 
================== cmd request  details ==================
execute cmd on host : 47.97.37.176
execute cmd : sudo docker inspect 7393aa6334e6


2025-01-03 16:38:03.922 | INFO     | cmdlinker.builtin.logger_operation:console_output:18 - 
================== cmd response  details ==================
status_code : 0
execute_time : 228ms
stdout   : [
    {
        "Id": "7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd",
        "Created": "2024-06-26T07:04:49.182101334Z",
        "Path": "docker-entrypoint.sh",
        "Args": [
            "--requirepass",
            "yourpassword"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 27544,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2024-06-26T07:04:49.54446658Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:7614ae9453d1d87e740a2056257a6de7135c84037c367e1fffa92ae922784631",
        "ResolvConfPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/hostname",
        "HostsPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/hosts",
        "LogPath": "/var/lib/docker/containers/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd/7393aa6334e6a091aae54790f5a6e3505f6dbab9515c5643978afbdc221975dd-json.log",
        "Name": "/some-redis",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {
                "6379/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "6379"
                    }
                ]
            },
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": false,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "CgroupnsMode": "host",
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96-init/diff:/var/lib/docker/overlay2/cb066f8e71551530105d92d22aee4ac7d10beeff480389676e102be0d701e534/diff:/var/lib/docker/overlay2/b69aadb0a11079daa266a36401ba0ce147450516b500c645eff85176f8e7919b/diff:/var/lib/docker/overlay2/a57e79ac2a99e28c990210b22b831dcd8fad2c98d810de241fc8a3d99b64953a/diff:/var/lib/docker/overlay2/482ff82ce4a7763a75994358255c4e604abab9fcb77fd1f5f2d815a0a98a323b/diff:/var/lib/docker/overlay2/8e951ea76637a9804eb44f89eb8353a5a71da11d304056b22e2d1886e7da9c2d/diff:/var/lib/docker/overlay2/e88bc247f216bd19bd2329b2feeda9cc387df6241598b863b201cafe7ff0db3a/diff",
                "MergedDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/merged",
                "UpperDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/diff",
                "WorkDir": "/var/lib/docker/overlay2/da926ce47de0e966f295ddf249be649da1f2c1ea162be28fb84a5c5d28db3d96/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "volume",
                "Name": "502c8ea157173ae65d24a2e0173c0e990e5ba1398013ad01a048e7a594a62608",
                "Source": "/var/lib/docker/volumes/502c8ea157173ae65d24a2e0173c0e990e5ba1398013ad01a048e7a594a62608/_data",
                "Destination": "/data",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
        "Config": {
            "Hostname": "7393aa6334e6",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.2.6",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.6.tar.gz",
                "REDIS_DOWNLOAD_SHA=5b2b8b7a50111ef395bf1c1d5be11e6e167ac018125055daa8b5c2317ae131ab"
            ],
            "Cmd": [
                "--requirepass",
                "yourpassword"
            ],
            "Image": "redis",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "b98966d73f26b520343cc69867d8ac56dd3ac65655375c2edb047cabc9eec9c8",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {
                "6379/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "6379"
                    }
                ]
            },
            "SandboxKey": "/var/run/docker/netns/b98966d73f26",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "63a20510e954d1b81181d319b2d004e68918a2926d3db1e71e846197b79c72e1",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.2",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:02",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "7fdaf78d05fd8a3d1c3c2ae0151022ae8c326582524327e18403fdab4a26f16d",
                    "EndpointID": "63a20510e954d1b81181d319b2d004e68918a2926d3db1e71e846197b79c72e1",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.2",
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:02",
                    "DriverOpts": null
                }
            }
        }
    }
]
stderr   : 
Process finished with exit code 0

我们可以很方便的使用返回的status_code/stdout/stderr进行请求响应断言


OpenSourceTest cmdlinker 社区
欢迎小伙伴加群,讨论cmdlinker相关问题,或提出优化建议!
QQ群(自动化测试-夜行者):816489363


http://www.kler.cn/a/466492.html

相关文章:

  • NoSQL简介
  • 云备份项目--服务端编写
  • 【调试记录】在CARLA中插入可以播放视频的组件
  • 计算机网络 (15)宽带接入技术
  • 急需升级,D-Link 路由器漏洞被僵尸网络广泛用于 DDoS 攻击
  • C# 设计模式(结构型模式):桥接模式
  • SQL 中复杂 CASE WHEN 嵌套逻辑优化
  • ros2 笔记-1.1 体验C++编译
  • Deepseek v3 的笔记
  • 如何使用OpenCV进行抓图-多线程
  • 基于AI边缘计算盒子的智慧零售场景智能监控解决方案
  • (NIPS-2023)ProlificDreamer:通过变分分数蒸馏实现高保真、多样化的文本到 3D 生成
  • 电子电气架构 --- 中央处理器HPC及软件架构
  • 创龙3588——debian根文件系统制作
  • DevSecOps自动化在安全关键型软件开发中的实践、Helix QAC Klocwork等SAST工具应用
  • 批量读取pdf发票中二维码的信息
  • 算法-各位数相加,直至和为个位数
  • 笔记:一次mysql主从复制延迟高的处理尝试
  • 006-Jetpack Compose for Android之传感器数据
  • 【能用】springboot集成netty,解码器处理数据过长的问题
  • 代码随想录day20 | leetcode 669.修剪二叉搜索树 108.将有序数组转换为二叉搜索树 538.把二叉搜索树转换为累加树
  • Linux上安装配置单节点zookeeper
  • 容器化部署算法服务技术文档
  • SELECT的使用
  • 预测facebook签到位置
  • JavaSE——IO流(下)