【单元测试】测还是不测,这是一个问题
这篇文章也可以在我的博客中查看
“册”那!!
相信大家从小就被千叮万嘱要做单元测试。然后秉承这一信念,成为了一个测试狂魔。凡有代码,测!覆盖!最终,一波操作猛如虎:467测试,0错误,0自信。
第二天。
你为了优化,颤抖着手更改了一行代码。果不其然发现牵连了1e9
个测试用例,测试结果瞬间变成满江红。
你开始怀疑自己,单元测试到底为了什么?除了增加大量工作量以外,好像没什么好处?
从此你与单元测试势不两立…………先等等!
如果你有这样的疑问,说明两个问题:
- 你的单元测试做错了
- 这篇文章会是帮助你解决问题的第一步
怎么测
在说“测什么”的时候,先解决比较简单的“怎么测”问题。
没错,反直觉的是“怎么测”比“测什么”更加简单……
选择方法
单元测试野路子不多,老师教的理论也无非只有两种:白盒测试、黑盒测试。
那么我们应该选择哪种?
太长不看:黑盒测试
从现在开始可以忘掉白盒测试这个名词了……这个东西在工程上根本不实际。
你给我等等,你一句话就把代码覆盖率说的一文不值?
其实我也没说代码覆盖率没用,我只是纯diss白盒测试。
“高代码测试覆盖率”是“代码质量高”的必要不充分条件。
白盒测试为了跑高代码覆盖率,追求走遍代码的每一个角落。
嗯。是。代码覆盖率是很高了。然后呢?这些用例意义的目的是什么?
目的仅仅是为了得到一个高覆盖率吗?
目标仅仅是刷kpi没事找事吗?
它到底验证了什么问题?
好好好,让我们祈祷这位程序员可以保持这种劲头
因为当代码重构后,可难免重新再做一轮白盒测试咯~
说到底,测试测的是软件行为,而不是代码结构。
好的测试应该在代码内部重构(行为不变)时保持不变。利益相关者、终端用户在乎的也只有软件的行为,软件内部的实现真的没人会管;而如果测试依赖于代码结构,那重构时改变了代码结构,测试也需要连带改动。
所以这不就说明,应该要用黑盒测试取代白盒测试吗?
流程
那么又该使用什么流程进行单元测试?
黑盒测试本身是很简单:给定输入,验证输出即可。
但测试目标不仅是测试通过,还应是能让大伙一目了然的知道测试干的是什么,被测试的函数又干的是什么。
为此,我建议:
- 一个函数一个测试情景
- 函数名说明该测试是什么
- 测试体中使用“准备”、“操作”、“断言”三部曲
例子:
测试一个函数,其作用是执行一个加法。
public int Addition(int a, int b) => a + b;
现在测试该函数是否正常运作。
using Xunit;[Theory]
[InlineData(1, 1, 2)]
[InlineData(-1, 1, 0)]
public void GivenTwoAddends_WhenAddition_ShouldReturnAddedValue(int addend1, int addend2, int answer)
{// Given// Two addends (in parameters this case)// Whenvar result = Addition(addend1, addend2);// ThenAssert.Equal(answer, result);
}
虽然代码是C#的,但道理是相通的。
- 一个函数代表一个测试情景,但不代表只能有一个用例。如果你想对同一操作执行多组边缘值的测试,你可以考虑复用这个函数。
- 函数名用于描述这个用例干什么,是一个适合使用自然语言描述“你在干什么”的好地方。命名规范随意,说明问题/其他人能接受即可。
- 函数体清楚分为三个部分。通常使用 Given, When, Then 或者 Arrange, Act, Assert进行示意。
依赖项
如果测试中涉及到其它依赖项怎么办?
由于单元测试只关注目前单元的表现,单元外的逻辑(即依赖项)一律视为无关项。
对于无关项,我们不关注它是否正确执行,而是假定它正确执行
因此我们可以通过Mock等手段,模拟外部依赖项,让其直接返回一个预定的结果。
当然,为了更好地使用Mock,我们需要将外部依赖项注入到待测试的模块中。
在 ASP.NET Core 中,一般实现为构造函数注入。
也就是我们需要将模块修改成依赖注入的形式。它使用到了Magic
,在构造函数中注入:
public class MysteryAddition
{private readonly Magic magic;public MysteryAddition(Magic magic) => this.magic = magic;/// <summary>/// Return a + b if not magic. Return random non-negative otherwise./// </summary>public int Addition(int a, int b){if (magic.DoMagic()){return new Random().Next();}return a + b;}
}
该函数的定义是:当Addition
函数被施加了某种魔法时,返回一个随机非负整数;否则返回a+b
的结果。
这个模块使用的外部依赖Magic
,形式如下:
public class Magic
{public bool DoMagic(){// This is a very complicated magic function// Omit 100000 lines// And pretend that it returns something.return true;}
}
我们是需要对MysteryAddition.Addition
函数进行单元测试。根据其定义,会有两个分支情况,而且返回的逻辑不同。
因此我们会写出两个用例。每个用例都会使用假的Magic
,并且假定它的不同情况:
using Moq;
using Xunit;[Theory]
[InlineData(1, 1, 2)]
[InlineData(-1, 1, 0)]
public void GivenTwoAddends_WhenAdditionWithoutMagic_ShouldReturnAddedValue(int addend1, int addend2, int answer)
{// Given// Two addends (in parameters this case) and ...// Setup mocked magic.var magic = new Mock<Magic>();magic.Setup(x => x.DoMagic()).Returns(false); // No magic :(var service = new MysteryAddition(magic.Object);// Whenvar result = service.Addition(addend1, addend2);// ThenAssert.Equal(answer, result);
}[Theory]
[InlineData(1, 1)]
[InlineData(-1, 1)]
public void GivenTwoAddends_WhenAdditionWithMagic_ShouldReturnNotNegativeValue(int addend1, int addend2)
{// Givenvar magic = new Mock<Magic>();magic.Setup(x => x.DoMagic()).Returns(true); // Let's be magical!!!var service = new MysteryAddition(magic.Object);// Whenvar result = service.Addition(addend1, addend2);// ThenAssert.True(result >= 0);
}
不难看出,我们所断言的只有函数的行为,用例完全由函数的定义进行编写。
该过程既不考虑其它模块的正确性/干扰,也不涉及函数内部的实现。
就算我不给你Addition
的代码,你也可以通过定义写出测试用例。
Mock很好用吧?它可以模拟依赖,化繁为简
但是小心,成也Mock,败也Mock……
你有没有发现,如果引入了Mock,你还需要知道这个被模拟的依赖项在函数中起到了什么作用。
黑盒似乎变得的没那么黑了?
而且更恐怖的是……
测什么
测什么单元
好,当我使用黑盒测试的原则编写了结构无关的用例后,感觉上好多了……
但感觉还是很痛苦!每次测试我都要Mock一大堆依赖项!
这是因为你测试了一切,这是单元测试最大的痛苦来源。
有些东西它本身就不适合被单元测试……
说到底,只应编写高性价比的单元测试。
进行单元测试之前,我们应该进行权衡:
- 对这个东西写测试难不难?
- 这个单元测试给我带来的效益有多大?
- 这个单元它是否扮演了重要角色?
单元测试难不难写?
烂代码导致的烂测试
如果你要测的模块跟上节提及的Addition
函数一样简单,那没什么好说的,30秒写完,性价比爆高!
但如果你面对一个意大利面一样的厚礼蟹代码模块
首先,你扪心自问:测吗?
那还用问?肯定不测。
然后,你再扪心自问:你写出这样的代码真的好吗?
……
……
……
你犹豫了,因为你发现有时候单元测试难写的原因不在于单元测试本身,而是因为被测试的代码太烂了!
但庆幸的是,这也是单元测试给你带来的“正面副作用”之一。
有没有发现,当你考虑是否进行单元测试的同时,你也在审视自己的代码。当你发现代码非常难测试的时候,可能就是一个代码质量低的警告。你可能没有遵循单一职责原则。你的万能函数做了太多事情。
所以怎么办?
重构你的代码。
将你的万能函数,万能类,一步步分割为更小的模块。
最终你将一个万能类A,分成了4个类:
A +-- B+-- C+-- D
没错,此时A成为了B、C、D三个功能模块的协调者。由于B、C、D现在都仅有一个单一职责。你现在可以很轻松地对他们分别进行单元测试。性价比爆高!测爆!
好好好,B、C、D是解决了……
那协调者A本身呢?我还要对它进行单元测试吗?
单元测试的效益
是否对协调者A进行单元测试,还是取决于性价比。但我劝你谨慎,因为:
对A进行单元测试,需要解决B、C、D三个依赖项负担。如果你重构代码时对B、C、D动刀了,那也意味着你可能需要修改测试用例的Mock B、C、D行为。
- 如果你的A类完全只是胶水代码,即缝合B、C、D三个模块的。其本身逻辑基本为0,那没有必要测试。
- 如果你的A类除了使用到B、C、D三个模块以外,其本身有非常复杂而必要的逻辑。A本身的独立逻辑占到了95%以上。没什么好说的,进行单元测试可以确保模块没有出错。
单元测试能避免日后被这坨屎山莫名背刺,四舍五入直接延年益寿。虽然你需要模拟B、C、D三个依赖项,但仍然是值得的。 - 但是也存在不少情况,处于两者之间的灰色地带,那么测还是不测,还是选部分测?见仁见智吧。
- 不测:反正也能够在集成测试中保证模块运作。而且Mock这些依赖项太麻烦了。
- 测:别跟我说集成测试。我单元测试强迫症,测完我舒服了,也有双重保险了。
- 部分测:我是和事佬。你们别吵了。挑简单的测,难的不测,不就好了?
被测单元的重要性
哥们衡量单元测试的效益不仅仅是工作量、维护性的问题,还得关注这个模块本身到底重不重要。
比如:这是一个支付模块,要是出错可能要赔几十个,甚至牢底坐穿。
那你说测不测嘛,肯定得测咯!
小结
当遇到难以测试的问题时,第一步先别责怪单元测试本身。而是问问自己,这个代码单元能不能再细分、结构优化。
当你确定已经没有优化的余地了,但感觉还是难以测试,考虑性价比:
- 这个东西用到了多少依赖项?本身又有多少自己的逻辑?
- 我重构这个代码(包括依赖项的调用变更)是否频繁?
- 我不测试会不会有严重的后果?会不会有人身安全隐患()?
思考这三个问题,自然有答案。
不要忘了,即使不单元测试,你还是会(至少应该!)编写包含该模块的集成测试来确保它正常运行。
但如果你问我的想法,经历了这段时间的捣鼓,我会回答:
不不不,我大概率不会对这种东西进行单元测试。
测单元的什么
从另一个角度看待问题。
当我确定要对这个模块进行单元测试了,我应该测它的啥?
第一节中说要进行黑盒测试。那我们就将系统看作一个黑盒,即一个一般化的物体。
我们说要测试行为,其实就是测试黑盒物体与外界的交互行为。
而行为的媒介即消息。
物体与外部的交互,可以视为消息的传达与回复。
所以整个问题变成了:我们需要针对什么消息进行单元测试?
在此之前,先看看消息本身到底是什么:
消息的流向
这个物体跟外界的沟通只有两个方向:入向消息,出向消息。
此外,物体还会有内部消息流向。
如果你要我当一次灵魂画师,我会画出下面这样的图😀:
┏━━━━━━━━┓
--[in]--> ┃ object ┃ --[out]-->┗━━━━━━━━┛↓-[self]-↑
从函数的角度来讲:
- 入向消息就是单元被调用
- 出向消息就是单元调用其它模块
- 内部消息就是:单元内部的函数调用
消息的分类
消息本身分为两类:查询(Query)和命令(Command)
- 查询是有回复(返回值),但不对系统产生影响的操作
- 比如上面的
Addition
加法函数
- 比如上面的
- 命令是无回复,但对系统产生影响的操作
- 比如数据库更新操作
当然,也存在同时为查询和命令的操作,比如Stack.Pop()
虽然如此,我们还是可以按消息的类别考察某种消息是否需要被测试。
需要测试的消息
已知:
- 消息的流向:In, Out, Self
- 消息的种类:Query, Command
好,我们直接做一个笛卡尔积,得到6种组合。
这些组合有些是需要测试的,有些是无需测试的。
魔法师Sandi Metz曾发表过一次演讲The Magic Tricks of Testing
,讲述了如何理解这6种组合在测试中的含义。
有兴趣的可以观看上面的演讲,实在受益匪浅。
我这里就不卖关子,直接剧透6种消息组合的测试操作:
Message | Query | Command |
---|---|---|
In | 1.a 检测结果 | 1.b 检测直接公共副作用 |
Self | 2.a 不要测试 | 2.b 不要测试 |
Out | 3.a 不要测试 | 3.b 确保消息送出 |
1. 模块被调用
a: 函数有返回值
直接检测调用结果。
这就是我们第一节中测试Addition
的情景
b: 如果函数无返回值
检测它的直接影响(副作用)。
最简单地,比如一个属性,有getter
和setter
:
public class Number
{private int num;public int Get() => num;public void Set(int num) => this.num = num;
}
只是显式举个例子,C#千万别这么写。写Property拉!
我们需要单元测试setter
,那我们可以调用setter
,然后使用getter
检测它是否正常运作。
2. 内部消息
你为什么需要测试它?
到底有谁在关注它?
如果外部消息行为正确,其实已经保证内部调用也正确了,所以有了上一步,这一步是冗余的。
3. 调用其它模块
a: 调用一个外部查询
这不就是我们第一节的第二个例子吗?
外部依赖项Magic
被MysteryAddition
调用了。
我们站在MysteryAddition
模块的角度,这就是一个出向消息。我们需要对这个消息,即Magic.DoMagic()
的行为进行验证吗?
没有吧。我们要单元测试的是MysteryAddition
,Magic
正不正确,关我什么事喔,你去找他咯。
但还没完,回顾那个例子
我们要让代码执行下去以测试入向信息啊
所以遇到这种情况,我们虽然不测试出向消息,但如果我们需要单元测试继续执行下去,需要模拟这个消息的返回。比如我们在上面,使用Mock返回了假的结果。
值得一提的是,生成测试替身(Test Double)的方式不只有Mock一种。
但话说到底,这种Mock仍然是十分不自然的。如果你发现设置Mock的难度非常大,建议还是遵循之前说的性价比原则:
模块细分到极致
放弃单元保明智
b: 调用一个外部命令
这种情况我们不能摆烂,我们需要确保命令信息成功送出了。
有人会有疑问:
啊?为什么?为什么要管外部依赖的状态!
但仔细想想,这其实并不是在检测外部依赖的状态,而是检测当前单元与外部依赖的衔接性。
我们单元的行为就是:当执行成功时,需要向外部依赖传出一个消息。
所以我们并没有脱离这个单元的范畴。
比如,你要测试一个发送注册邮件的函数SendVerificationEmail
,它经过处理之后,最终会调用邮件服务(一个外部依赖)发送邮件。
那么问题来了:
- 单元测试中并没有真的邮件服务
SendVerificationEmail
函数并没有返回结果- 它也不存在任何直接副作用
那怎么知道SendVerificationEmail
真的正常工作?
你说得对,在单元测试的范畴,我们没办法100%确保它正常工作。
但我们至少可以确定一个预估行为:代码应调用了邮件服务。
假设我们的外部邮件服务的接口是:
public interface IEmail
{public void Send(string address, string content);
}
我们需要单元测试的是:
public class EmailMessenger
{private readonly IEmail email;public EmailMessenger(IEmail email) => this.email = email;public void SendVerificationEmail(int userId){var address = $"{userId}@yourdomain.com";var content = $"Hello, {userId} ...";// ... more processes.email.Send(address, content);}
}
我们就需要验证IEmail.Send
仅被调用一次
[Fact]
public void GivenUserId_WhenSendVerificationEmail_ShouldReallySendIt()
{// Givenvar userId = 1;var email = new Mock<IEmail>();email.Setup(x => x.Send(It.IsAny<string>(), It.IsAny<string>()));var service = new EmailMessenger(email.Object);// Whenservice.SendVerificationEmail(userId);// Thenemail.Verify(mock => mock.Send(It.IsAny<string>(), It.IsAny<string>()), Times.Once());
}
小结
从单元的角度,消息的流向有3个方向。
其中我们只需要单元测试入向与出向消息。
- 入向消息
- 是最常规的“调用”、“预测”三部曲。
- 出向消息
- 只关注出向命令是否正常送出。
- 而出向查询,我们不测试。但有时我们需要结果让代码跑下去,给它一个既定的返回值。
总结
说实话,单元测试绝对是一个被低估的大魔王
它给人的感觉非常和善,人畜无害
但实际上笑里藏刀
打败这个大魔王的奖励非常丰厚,它可以带给我们:时间、效率、收益
但是,他没那么容易被打败
希望这篇文章成为你打败它的究极魔咒()
师傅别念了
参考资料
不仅仅是参考资料。如果你有时间,我建议你也阅读这些资料。
- Test Desiderata
- Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz
- Katrina Owen - 467 tests, 0 failures, 0 confidence - Railsberry 2013
- Justin Searls – Please don’t mock me
- Should I write unit test for controller or service layer or both of them?
- Testing a service method by Unit Test?
- Should I bother unit testing my repository layer
Bonus
如果你在想下面这些MVC中的层次是否需要单元测试,可以从上面的资料获得答案。
长话短说:
Repository
:直接集成测试,不要单元测试Controller
:直接集成测试,不要单元测试Service
:是否单元测试取决于该服务是否独立,依赖项是否少,性价比是否高
相关文章:

【单元测试】测还是不测,这是一个问题
这篇文章也可以在我的博客中查看 “册”那!! 相信大家从小就被千叮万嘱要做单元测试。然后秉承这一信念,成为了一个测试狂魔。凡有代码,测!覆盖!最终,一波操作猛如虎:467测试&…...

Global Mapper SDK 19 中文开发文档(八)
7.2.8 GM_DBUtil (1)声明 public static class GM_DBUtil (2)方法 方法描述DBGetTableList获取指定空间数据库中的表列表DBIsDatabaseFile指示输入文件是否为数据库(Esri地理数据库、Spatialite等)DBMa…...

es检索之复合检索
背景:向量检索是文本相似度检索,现在增加新的字段进行过滤,如果以filter方式进行过滤,那么最终结果不保证有topK个,甚至一个都没有,因为它是先进行topK个向量召回,再进行filter。 当然有人建议采用scriptScore方式进行检索,但此方式可能造成请求压力过大,内存消耗。 …...

09.list 容器
9、list 容器 功能: 将数据进行链式存储 链表(list)是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 链表的组成: 链表由一系列结点组成 结点的组成: 一个是存…...

速盾网络:网站用速盾cdn的好处
随着互联网的快速发展,网站的速度和稳定性成为了用户访问体验的关键因素。为了提高网站的性能和可用性,许多网站开始使用CDN(Content Delivery Network)服务。本文将介绍CDN的基本概念、工作原理以及使用CDN的好处。一、CDN的基本…...

Centos7在安装Graylog时新安装MongoDB报错端口不监听服务不启动无法运行启动失败
由于虚拟机服务器上需要安装Graylog需要安装MongoDB,尝试官网下载安装包,和yum安装均无法正常启动,折腾了好几天,重装了十几次,网上搜索了很多很多资料,均无法正常运行,百度上搜索各种文档&…...

Spark Machine Learning进行数据挖掘的简单应用(兴趣预测问题)
数据挖掘的过程 数据挖掘任务主要分为以下六个步骤: 1.数据预处理2.特征转换3.特征选择4.训练模型5.模型预测6.评估预测结果 数据准备 这里准备了20条关于不同地区、不同性别、不同身高、体重…的人的兴趣数据集(命名为hobby.csv): id,h…...

DRF从入门到精通二(Request源码分析、DRF之序列化、反序列化、反序列化校验、序列化器常用字段及参数、source、定制字段、保存数据)
文章目录 一、Request对象源码分析区分原生request和新生request新的request还能像原来的reqeust一样使用吗源码片段分析总结: 二、DRF之序列化组件序列化介绍序列化步骤序列化组件的基本使用反序列化基本使用反序列化的新增反序列化的新增删除单条 反序列化的校验序…...

Flink系列之:Upsert Kafka SQL 连接器
Flink系列之:Upsert Kafka SQL 连接器 一、Upsert Kafka SQL 连接器二、依赖三、完整示例四、可用元数据五、键和值格式六、主键约束七、一致性保证八、为每个分区生成相应的watermark九、数据类型映射 一、Upsert Kafka SQL 连接器 Scan Source: Unbounded 、Sink…...

前端与后端的异步编排(promise、async、await 、CompletableFuture)
前端与后端的异步编排 文章目录 前端与后端的异步编排1、为什么需要异步编排2、前端中的异步2.1 、Promise的使用2.1.1、Promise的基础概念2.1.2、Promise中的两个回调函数2.1.3、工具方法1、Promise.all()2、Promise.race()3、Promise.resolve() 2.2 、async 与 aw…...

python打开opencv图像与QImage图像及其转化
目录 1、Qimage图像 2、opencv图像 3、python打开QImage图像通过Qlabel控件显示 4、python打开QImage图像通过opencv显示 5、python打开opencv图像并显示 6、python打开opencv图像通过Qlabel控件显示 1、Qimage图像 QImage是Qt库中用于存储和处理图像的类。它可以存储多种…...

linux 其他版本RCU
1、不可抢占RCU 如果我们的需求是“不管内核是否编译了可抢占RCU,都要使用不可抢占RCU”,那么应该使用不可抢占RCU的专用编程接口。 读者使用函数rcu_read_lock_sched()标记进入读端临界区,使用函数rcu_read_unlock_ sched()标记退出读端临界…...

【单调栈】LeetCode:2818操作使得分最大
作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 涉及知识点 单调栈 题目 给你一个长度为 n 的正整数数组 nums 和一个整数 k 。 一开始,你的分数为 1 。你可以进行以下操作至多 k 次,目标是使你的分数最大: 选择一个之前没有选过的 非…...

uniapp 添加分包页面,配置分包预下载
为什么要分包 ? 分包即将小程序代码分成多个部分打包,可以减少小程序的加载时间,提升用户体验 添加分包页面 比较便捷的方法是使用vscode插件 uni-create-view 新建分包文件夹 以在我的页面,添加分包的设置页面为例,新建文件夹 s…...

成功案例分享:物业管理小程序如何助力打造智慧社区
随着科技的进步和互联网的普及,数字化转型已经渗透到各个行业,包括物业管理。借助小程序这一轻量级应用,物业管理可以实现线上线下服务的无缝对接,提升服务质量,优化用户体验。本文将详细介绍如何通过乔拓云网设计小程…...

Electron执行本地cmd命令
javascript执行本地cmd命令,javascript代码怎么执行_js调用本机cmd-CSDN博客 使用 Node.js 打开本地应用_nodejs启动应用-CSDN博客 笔记:nodejs脚本唤醒本地应用程序或者调用命令-CSDN博客 electron调起本地应用_electron 调用本地程序-CSDN博客 命令行打开vscode 你可以使用…...

YOLOv8改进 | 主干篇 | 利用MobileNetV3替换Backbone(轻量化网络结构)
一、本文介绍 本文给大家带来的改进机制是MobileNetV3,其主要改进思想集中在结合硬件感知的网络架构搜索(NAS)和NetAdapt算法,以优化移动设备CPU上的性能。它采用了新颖的架构设计,包括反转残差结构和线性瓶颈层&…...

MATLAB Mobile - 使用预训练网络对手机拍摄的图像进行分类
系列文章目录 前言 此示例说明如何使用深度学习对移动设备摄像头采集的图像进行分类。 在您的移动设备上安装和设置 MATLAB Mobile™。然后,从 MATLAB Mobile 的“设置”登录 MathWorks Cloud。 在您的设备上启动 MATLAB Mobile。 一、在您的设备上安装 MATLAB M…...

LangChain入门指南:定义、功能和工作原理
LangChain入门指南:定义、功能和工作原理 引言LangChain是什么?LangChain的核心功能LangChain的工作原理LangChain实际应用案例如何开始使用LangChain 引言 在人工智能的浪潮中,语言模型已成为推动技术革新的重要力量。从简单的文本生成到复…...

关键字:import关键字
在 Java 中,import关键字用于导入类或接口,使你可以在代码中使用它们而无需完全限定其名称。以下是使用import关键字的示例代码: 在上述示例中,通过使用import关键字导入了java.util.ArrayList类,这样就可以在代码中直…...

【C#】.net core 6.0 通过依赖注入注册和使用上下文服务
给自己一个目标,然后坚持一段时间,总会有收获和感悟! 请求上下文是指在 Web 应用程序中处理请求时,包含有关当前请求的各种信息的对象。这些信息包括请求的头部、身体、查询字符串、路由数据、用户身份验证信息以及其他与请求相关…...

关于redis单线程和IO多路复用的理解
首先,Redis是一个高性能的分布式缓存中间件。其复杂性不言而喻,对于Redis整体而言肯定不是只有一个线程。 我们常说的Redis 是单线程,主要是指 Redis 在网络 IO和键值对读写是采用一个线程来完成的,这也是 Redis 对外提供键值存储…...

第四十一章 XML 映射参数摘要
文章目录 第四十一章 XML 映射参数摘要 第四十一章 XML 映射参数摘要 TopicParameters启用 XML 映射。XMLENABLED 类参数将属性映射到元素或属性。XMLPROJECTION property parameter ("NONE", "ATTRIBUTE", "XMLATTRIBUTE", "CONTENT"…...

redis之五种基本数据类型
一) 字符串(String) 1 使用场景 2 编码 3 编码转换 二) List(列表) 1 使用场景 2 编码 三) Set(无序集合) 1 使用场景 2 编码 3 编码转换 四) ZSet(有序集合) 1 使用场景 2 编码 3 编码转换 五) Hash 1 使用场景 2 编码 3 编码转换 五种基本数据类型 redis…...

RocketMQ系统性学习-RocketMQ高级特性之消息大量堆积处理、部署架构和高可用机制
🌈🌈🌈🌈🌈🌈🌈🌈 【11来了】文章导读地址:点击查看文章导读! 🍁🍁🍁🍁🍁🍁dz…...

Angular 进阶之五: Signals到底用不用?
Angular 在V16的时候推出了Signals,在17正式作为主打功能之一强烈推荐,看过了各种博主的各种科普文章也没说明白,到底这东西值不值得用?毕竟项目大了,重构代码也不是闹着玩儿的。各种科普文章主要在说两点:…...

构建数字化金融生态系统:云原生的创新方法
内容来自演讲:曾祥龙 | DaoCloud | 解决方案架构师 摘要 本文探讨了金融企业在实施云原生体系时面临的挑战,包括复杂性、安全、数据持久化、服务网格使用和高可用容灾架构等。针对网络管理复杂性,文章提出了Spiderpool开源项目,…...

前端性能优化五:css和js位置
1. 精简HTML代码: ①. css链接文件尽量放在页面头部:a. css的加载不会阻塞DOM Tree的解析.b. 但会阻塞DOM Tree渲染,也会阻塞后面JS的执行.c. 将css放在任何body元素之前:(1). 可以确保在文档中解析了所有css的样式包括内联样式和外联的.(2). 减少了浏览器必须重排文档的次数.…...

苏州耕耘无忧物联网:降本增效,设备维护管理数字化转型的引领者
随着科技的快速发展和工业4.0的推动,设备维护管理已经从传统的被动式、经验式维护,转向了更加积极主动、数据驱动的维护模式。在这个过程中,苏州耕耘无忧物联科技有限公司以其深厚的技术积累和丰富的管理经验,引领着设备维护管理数…...

15个热门的开源数据可视化项目
数据可视化(即 BI仪表盘)是图形表示的数据。它涉及产生将表示的数据之间的关系传达给图像查看者的图像。这种通信是通过在可视化过程中使用图形标记和数据值之间的系统映射来实现的。该映射建立了如何在视觉上表示数据值,确定图形标记的属性(例如大小或颜色)如何以及在多大程…...