Python自动化测试用例:如何优雅的完成Json格式数据断言

news2024/11/26 8:28:11

目录

前言

直接使用

优化

封装

小结

进阶

总结

 资料获取方法


前言

记录Json断言在工作中的应用进阶。

直接使用

很早以前写过一篇博客,记录当时获取一个多级json中指定key的数据:

#! /usr/bin/python
# coding:utf-8 
""" 
@author:Bingo.he 
@file: get_target_value.py 
@time: 2017/12/22 
"""
def get_target_value(key, dic, tmp_list):
    """
    :param key: 目标key值
    :param dic: JSON数据
    :param tmp_list: 用于存储获取的数据
    :return: list
    """
    if not isinstance(dic, dict) or not isinstance(tmp_list, list):  # 对传入数据进行格式校验
        return 'argv[1] not an dict or argv[-1] not an list '
    if key in dic.keys():
        tmp_list.append(dic[key])  # 传入数据存在则存入tmp_list
    for value in dic.values():  # 传入数据不符合则对其value值进行遍历
        if isinstance(value, dict):
            get_target_value(key, value, tmp_list)  # 传入数据的value值是字典,则直接调用自身
        elif isinstance(value, (list, tuple)):
            _get_value(key, value, tmp_list)  # 传入数据的value值是列表或者元组,则调用_get_value
    return tmp_list


def _get_value(key, val, tmp_list):
    for val_ in val:
        if isinstance(val_, dict):  
            get_target_value(key, val_, tmp_list)  # 传入数据的value值是字典,则调用get_target_value
        elif isinstance(val_, (list, tuple)):
            _get_value(key, val_, tmp_list)   # 传入数据的value值是列表或者元组,则调用自身

优化

后来在写用例生成过程中,发现重复的断言描述信息较多,大多数数据返回其实都是标准的json,所以将整个返回的json放到断言数据中:

continue_run_flag = True


def assert_json(base, juge, contain=(), reg=()):
    # 返回值,不符合预期时会设置为False
    flag = True
    for key, value in base.items():

        # 不进行断言的数据不进一步处理
        if key not in juge:
            continue

        if key in COMMON_RE_CHECK:

            if not re.match(COMMON_RE_CHECK[key], base[key]):
                flag = False
                logger.error("字段[{}]使用通用的正则匹配,不符合预期:预期正则匹配【{}】== 【{}】".format(key, COMMON_RE_CHECK[key], juge[key]))
            else:
                logger.warning("字段【{}】使用通用字段的正则匹配, 符合预期".format(key))
            continue

        if key in contain:
            if str(value) not in juge[key]:
                flag = False
                logger.error("字段[{}]不符合预期:预期【{}】包含 【{}】".format(key, juge[key], value))
                continue
            logger.info("字段[{}]符合预期:预期[{}] 包含 [{}]".format(key, juge[key], value))
            continue

        if key in reg:
            if not re.match(juge[key], value):
                flag = False
                logger.error("字段[{}]不符合预期:预期正则匹配【{}】== 【{}】".format(key, value, juge[key]))
                continue
            logger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))
            continue
        if isinstance(value, str) or isinstance(value, int):
            if juge[key] != value:
                logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))
                flag = False
                continue
        elif isinstance(value, dict):
            assert_json(value, juge[key], contain=contain, reg=reg)
        elif isinstance(value, list):
            for i, v in enumerate(value):
                if isinstance(value, str) or isinstance(value, int):
                    if v != juge[key][i]:
                        logger.error("字段[{}]不符合预期:预期【{}】!= 实际【{}】".format(key, value, juge[key]))
                    else:
                        logger.info("字段[{}]断言成功:预期[{}]== 实际[{}]".format(key, value, juge[key]))
                elif isinstance(value, dict):
                    assert_json(value[i], juge[key][i], contain=contain, reg=reg)
                else:
                    assert_json(value[i], juge[key][i], contain=contain, reg=reg)

        else:
            pass

        logger.info("字段[{}]符合预期: 预期[{}]== 实际[{}]".format(key, value, juge[key]))

    # 失败是否继续执行,默认为TRUE
    if not continue_run_flag:
        assert flag

    return flag

调用:

rsp = requests.get('http://localhost:8800/get', params={"name": "bingo", "age": 18}).json()
assert_json(rsp, {
    "args": {
        "name": "bingo"
    },
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Cache-Control": "max-age=259200",
        "Host": "httpbin.org",
        "User-Agent": "python-requests/2.27.1",
        "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
    },
    "req_param": [
        {
            "name": "bingo",
            "age": "18"
        }
    ],
    "origin": r"",
    "url": "http://httpbin.org/get?name=bingo"
},
contain=(), reg=("X-Amzn-Trace-Id", "origin",))

日志效果:

2022-05-05 14:25:49.967 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:149 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 14:25:49.968 | INFO     | __main__:assert_json:173 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 14:25:49.969 | WARNING  | __main__:assert_json:133 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[req_param]符合预期: 预期[[{'age': '18', 'name': 'bingo'}]]== 实际[[{'name': 'bingo', 'age': '18'}]]
2022-05-05 14:25:49.969 | INFO     | __main__:assert_json:173 - 字段[url]符合预期: 预期[http://httpbin.org/get?name=bingo]== 实际[http://httpbin.org/get?name=bingo]

封装

将方法简单封装到调用类中:

class HttpBin:
    def __init__(self):
        self.continue_run_flag = True  # 失败是否继续执行
        self.base_url = 'http://localhost:8800'
        self.base_param = {"local_class": self.__class__.__name__}

    def get(self, param):
        path = "/get"
        param.update(self.base_param)
        self.rsp = requests.get(self.base_url + path, params=param)
        self.ans = self.rsp.json()
        logger.info(json.dumps(self.rsp.json(), indent=4))
        return self

    def set(self, param):
        path = "/set"
        param.update(self.base_param)
        self.rsp = requests.get(self.base_url + path, params=param)
        self.ans = self.rsp.json()
        logger.info(json.dumps(self.rsp.json(), indent=4))
        return self

    def assert_statusCode(self, result_code):
        """
        :param result_code: 包含关系断言
        :return: bool <= self.rsp.resultinfo
        """
        # 返回值,不符合预期时会设置为False
        flag = True

        if int(result_code) != self.rsp.status_code:
            logger.error(f"返回状态码[result_code]不符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")
            flag = False
        else:
            logger.info(f"返回状态码[result_code]符合预期:预期【{result_code}】!= 实际【{self.rsp.status_code}】")

        if not self.continue_run_flag:
            assert flag

        return self

    def assert_json_body(self, base, juge, contain=(), reg=()):
    ...

用例调用

# 如果仅仅断言状态码
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200)

# 级连调用多个接口,使用同一个初始化数据
HttpBin().get({"name": "bingo", "age": 18}).assert_statusCode(200).\
        set({"name": "he", "age": 18}).assert_statusCode(200).assert_json_body(
        juge={
            "args": {
                "name": "bingo"
            },
            "headers": {
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate",
                "Cache-Control": "max-age=259200",
                "Host": "httpbin.org",
                "User-Agent": "python-requests/2.27.1",
                "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
            },
            "req_param": [
                {
                    "name": "he",
                    "age": "18"
                }
            ],
            "origin": r"",
            "url": "http://httpbin.org/set?name=bingo"
        }, contain=(), reg=("X-Amzn-Trace-Id", "origin",)
    )

运行效果:

2022-05-05 19:39:36.951 | INFO     | __main__:assert_statusCode:53 - 返回状态码[result_code]符合预期:预期【200】!= 实际【200】
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[bingo]== 实际[bingo]
2022-05-05 19:39:36.951 | INFO     | __main__:assert_json_body:117 - 字段[args]符合预期: 预期[{'name': 'bingo'}]== 实际[{'name': 'bingo'}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept]符合预期: 预期[*/*]== 实际[*/*]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Accept-Encoding]符合预期: 预期[gzip, deflate]== 实际[gzip, deflate]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Cache-Control]符合预期: 预期[max-age=259200]== 实际[max-age=259200]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[Host]符合预期: 预期[httpbin.org]== 实际[httpbin.org]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[User-Agent]符合预期: 预期[python-requests/2.27.1]== 实际[python-requests/2.27.1]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:93 - 字段[X-Amzn-Trace-Id]断言成功:预期[Root=1-62734553-430db0707e1a3656043cd165]== 实际[Root=\d-\w{8}-\w{24}]
2022-05-05 19:39:36.952 | INFO     | __main__:assert_json_body:117 - 字段[headers]符合预期: 预期[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=1-62734553-430db0707e1a3656043cd165'}]== 实际[{'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Cache-Control': 'max-age=259200', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.27.1', 'X-Amzn-Trace-Id': 'Root=\\d-\\w{8}-\\w{24}'}]
2022-05-05 19:39:36.953 | WARNING  | __main__:assert_json_body:77 - 字段【origin】使用通用字段的正则匹配, 符合预期
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[age]符合预期: 预期[18]== 实际[18]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[name]符合预期: 预期[he]== 实际[he]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[req_param]符合预期: 预期[[{'age': '18', 'local_class': 'HttpBin', 'name': 'he'}]]== 实际[[{'name': 'he', 'age': '18'}]]
2022-05-05 19:39:36.953 | INFO     | __main__:assert_json_body:117 - 字段[url]符合预期: 预期[http://httpbin.org/set?name=bingo]== 实际[http://httpbin.org/set?name=bingo]

小结

  • 可以作为独立函数使用,也可以和所有的接口一起封装,灵活度、复用度高
  • 用例极简且逻辑清晰
  • 支持断言失败,用例继续执行(开关控制),方便一次性发现所有的差异
  • 极易编写的统一断言(从日志获取轻松获取
    • 用例运行日志,断言数据字段清晰明确
    • 可统一化处理新版本修改字段,无需修改每个用例
    • 支持正则匹配
    • 支持**包含匹配 **
    • 支持错误码直接断言
  • 集中初始化原始常用数据,不同业务适配简单,重写初始化方法即可

进阶

上面的方法虽然使用日志的方法记录了所有的差异,但是面对大json对比的时候,很难直接标记出具体的差异位置。在做现网引流对比测试的时候就出现了这样的需求,从现网拉取的账户数据可能存在几百个子账户,每个子账户有20多个属性字段,怎么准确标记他们在新旧系统的写操作后不一致的情况成为了一个小卡点。

话不多说,思路:利用列表可变特性和生成器关键字yield特性递归分解json,生成一个固定的数组,最终比较数组中的数据

代码:

def recurse(d, prefix=None, sep='.'):
    if prefix is None:
        prefix = []
    for key, value in d.items():
        if isinstance(value, dict):
            yield from recurse(value, prefix + [key])
        elif isinstance(value, list):

            for i, v in enumerate(value):
                if isinstance(v, dict):
                    yield from recurse(v, prefix + [key, f"${i}"])

                # 兼容 包含数字的类型
                elif isinstance(v, int) or isinstance(v, str):
                    yield sep.join(prefix + [key, str(value)])  # 会嵌套上value
        else:
            # print(key)
            yield sep.join(prefix + [key, str(value)])  # 会嵌套上value

效果:

print(json.dumps(list(recurse({
            "args": {
                "name": "bingo"
            },
            "headers": {
                "Accept": "*/*",
                "Accept-Encoding": "gzip, deflate",
                "Cache-Control": "max-age=259200",
                "Host": "httpbin.org",
                "User-Agent": "python-requests/2.27.1",
                "X-Amzn-Trace-Id": r"Root=\d-\w{8}-\w{24}"
            },
            "req_param": [
                {
                    "name": "bingo",
                    "age": "18"
                },
                {
                    "name": "he",
                    "age": "19"
                },
                {
                    "name": "detector",
                    "age": "20"
                }
            ],
            "origin": r"",
            "url": "http://httpbin.org/set?name=bingo"
        })), indent=4))
# 输出
[
    "args.name.bingo",
    "headers.Accept.*/*",
    "headers.Accept-Encoding.gzip, deflate",
    "headers.Cache-Control.max-age=259200",
    "headers.Host.httpbin.org",
    "headers.User-Agent.python-requests/2.27.1",
    "headers.X-Amzn-Trace-Id.Root=\\d-\\w{8}-\\w{24}",
    "req_param.$0.name.bingo",
    "req_param.$0.age.18",
    "req_param.$1.name.he",
    "req_param.$1.age.19",
    "req_param.$2.name.detector",
    "req_param.$2.age.20",
    "origin.",
    "url.http://httpbin.org/set?name=bingo"
]

总结

项目实践中总会遇到这样那样的需求,每个方法都有适用的场景,直接高效解决问题是第一要务。

  • json查找数据
  • json数据用例封装对比
  • json数据转化

 资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/848010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力

森海塞尔为 CUPRA 首款纯电轿跑 SUV – CUPRA Tavascan 注入音频魅力 音频专家森海塞尔携手富有挑战精神的 CUPRA&#xff0c;雕琢时代新贵车型&#xff0c;打造畅快尽兴的驾驶体验 全球知名音频专家森海塞尔与以颠覆传统、充满激情、不甘现状而闻名的汽车品牌 CUPRA 展开合作…

awk案例练习

目录 一、awk练习 1.1筛选ip地址 1.2字段去重 1.3次数统计 1.4统计TCP连接状态 1.5处理字段缺失的数据 1.6筛选给定时间范围内的日志 一、awk练习 1.1筛选ip地址 ifconfig命令查看IP 利用awk进行筛选 ifconfig | awk BEGIN{RS""}NR2{print $6} RS指定输入记…

找工作的才是大爷?面试了一个工作4年的测试工程师,一问连自动化基础都不清楚,还反过来怼我....

我们公司也开始大量招人了&#xff0c;我这次是公司招聘的面试官之一&#xff0c;主要负责一些技术上的考核&#xff0c;这段时间还真让我碰到了不少奇葩求职者 昨天公司的HR小席刚跟我吐槽&#xff1a;这几个星期没有哪天不加班的&#xff01;各种招聘网站上的消息源源不断&a…

干货分享 | TSMaster图形模块功能详解(三)—— 以CAN信号为例

TSMaster图形模块功能详解第二章节中&#xff0c;我们主要分享了显示模式、图形设置、信号跳转与波形窗口移动、信号波形缩放4大模块的操作教程。 本章节在上一篇基础上&#xff0c;继续介绍TSMaster图形模块功能第11-15模块的教程。 本文目录&#xff1a; 11、适配 11.1 水平…

基于JavaWeb的家居电子商城管理系统

家居电子商城HOMEECMS 大二下的JavaWeb小学期课程写的一个家居电子商城管理系统&#xff0c;没有spring框架&#xff0c;纯servlet&#xff0c;线上家居电子商城系统&#xff0c;主要实现了用户注册&#xff0c;登录、浏览&#xff0c;查看家居商品信息&#xff0c;购物车&…

元宇宙3D数字虚拟客服打造年轻化、数字化营销新品牌

融合了元宇宙、AI和云计算等技术的虚拟数字人&#xff0c;成为元宇宙数字内容交互的载体&#xff0c;将现实世界中的人与虚拟数字世界的场景、模型及产品链接起来&#xff0c;特别是为电力企业打造的电力元宇宙平台&#xff0c;带来营销宣传多重好处的同时&#xff0c;树立了数…

什么?200?跨域?

情景复现 今天我遇到了一件很奇怪的事情就是&#xff0c;当我请求后端网关&#xff0c;然后通过网关去请求相应的服务&#xff0c;都进行了跨域处理 但是&#xff0c;奇怪的是我在请求的时候&#xff0c;回来的响应码是200&#xff0c;但是报错了&#xff0c;报的还是200的同…

基于微服务架构的智慧工地云平台系统源码

Spring Cloud 智慧工地源码&#xff0c;智慧建造源码 系统概述&#xff1a; 智慧工地云平台系统&#xff0c;依托计算机技术、物联网、云计算、大数据、人工智能、VR&AR等技术相结合&#xff0c;对建筑工地中的管理人员、施工人员、车辆/物料、施工设备、视频监控等数据有…

模仿火星科技 基于cesium+角度测量+高度测量+可编辑

1. 创建提示窗&#xff1a; 启动Cesium应用&#xff0c;地图场景将打开&#xff0c;欢迎您进入编辑模式。 在屏幕的一角&#xff0c;一个友好的提示窗将呈现&#xff0c;随着您的操作&#xff0c;它会为您提供有用的信息和指导。 2. 绘制面积&#xff1a; 轻轻点击鼠标左键&a…

华为OD机试真题 Java 实现【疫情扩散时间计算】【2023 B卷 200分】,附详细解题思路

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

【Python学习笔记】Matplotlib画图

Matplotlib画图 一、基本函数1. plt.plot2. plt.text3. 设置刻度4. 设置坐标5.设置网格 二、绘制折线图三、绘制散点图四、绘制柱状图五、绘制双坐标图 一、基本函数 1. plt.plot 参数功能选项color改变折线颜色‘r’,‘g’,‘b’,‘black’,‘gray’marker改变数据点的形状‘…

固态硬盘 vs 机械硬盘:选择合适的存储方案

随着计算机的快速发展&#xff0c;各种硬件组件如CPU、显卡以及制作工艺都取得了长足的进步&#xff0c;但是磁盘的发展相对较为缓慢&#xff0c;这也导致了磁盘性能在一定程度上限制了计算机的整体性能。为了解决这个问题&#xff0c;固态硬盘应运而生。 那么&#xff0c;我们…

(亲测)制作linux启动U盘

下载软碟通 https://cn.ultraiso.net/xiazai.html 点击文件中的打开&#xff0c;选中Linux系统 插入U盘&#xff0c;点击启动中的写入硬盘映像 点击之后会自动选中U盘。界面如下&#xff1a; 点击写入&#xff0c;点击后U盘会格式化&#xff0c;所以U盘里面有重要东西的话…

中国金融四十人论坛:2023年第二季度宏观政策报告(附下载)

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 • 运行环境&#xff1a;外部环境方面&#xff0c;全球经济景气回落&#xff0c;会酸交作仍在收秀。内部环演方百&#xff0c;公共支出进一步旅爱&#xff0c;真交利本显考上开&#xff0c;社酸塔这创…

neo4j终端操作

1】进入容器 (base) xiaokkkxiaokkkdeMacBook-Pro ~ % docker exec -it 77ed5fe2b52e /bin/bash 2】启动、停止neo4j root77ed5fe2b52e:/var/lib/neo4j/bin# ./neo4j start Neo4j is already running (pid:7). Run with --verbose for a more detailed error message.root7…

项目实战 — 消息队列(6){内存数据管理}

目录 一、设计数据结构 二 、实现管理方法 &#x1f345; 1、实现交换机管理 &#x1f345; 2、实现队列管理 &#x1f345; 3、实现绑定管理 &#x1f388;插入绑定操作 &#x1f388;删除绑定 &#x1f345; 4、进行消息管理 &#x1f345; 5、发送消息到指定队列 &a…

英文音频转文字app让音频秒变文字

小明是一名大学生&#xff0c;他在上大学的时候经常需要通过听老师讲课来获取知识。但是&#xff0c;他发现自己很难在听课的同时完整地记录下老师所讲的内容。于是&#xff0c;他开始寻找音频转文字手机软件有哪些。经过一段时间的探索&#xff0c;他找到了三款不错的软件&…

我开源的 c#+wpf 模仿网易云音乐播放器

MusicApp 介绍 gitee地址&#xff1a;https://gitee.com/liu_guo_feng/music-app 我开源的 c#wpf 模仿网易云音乐播放器 项目页面功能完成列表 首页(待完善) 每日推荐音乐 歌单详情 带播放列表 歌词页(待完善) 换肤功能(待完善) 系统托盘 … 预览 仅供学习使用 不作任何商业用…

查看单元测试用例覆盖率新姿势:IDEA 集成 JaCoCo

1、什么是 IDEA IDEA 全称 IntelliJ IDEA&#xff0c;是 Java 编程语言开发的集成环境。IntelliJ 在业界被公认为最好的 Java 开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE 支持、各类版本工具(git、SVN 等)、JUnit、CVS 整合、代码分析、 创新的 GUI…

Python入门自学进阶-Web框架——39、redis、rabbitmq、git——2

RabbitMQ的exchange&#xff0c;即交换机有不同的类型&#xff1a; 1.direct Exchange(直接交换机) 匹配路由键&#xff0c;只有完全匹配消息才会被转发 2.Fanout Excange&#xff08;扇出交换机&#xff09; 将消息发送至所有的队列 3.Topic Exchange(主题交换机) 将路由按模…