【单元测试】测还是不测,这是一个问题
这篇文章也可以在我的博客中查看
“册”那!!
相信大家从小就被千叮万嘱要做单元测试。然后秉承这一信念,成为了一个测试狂魔。凡有代码,测!覆盖!最终,一波操作猛如虎: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类,这样就可以在代码中直…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...

macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...

解析“道作为序位生成器”的核心原理
解析“道作为序位生成器”的核心原理 以下完整展开道函数的零点调控机制,重点解析"道作为序位生成器"的核心原理与实现框架: 一、道函数的零点调控机制 1. 道作为序位生成器 道在认知坐标系$(x_{\text{物}}, y_{\text{意}}, z_{\text{文}}…...
ffmpeg(三):处理原始数据命令
FFmpeg 可以直接处理原始音频和视频数据(Raw PCM、YUV 等),常见场景包括: 将原始 YUV 图像编码为 H.264 视频将 PCM 音频编码为 AAC 或 MP3对原始音视频数据进行封装(如封装为 MP4、TS) 处理原始 YUV 视频…...