【单元测试】测还是不测,这是一个问题

这篇文章也可以在我的博客中查看

“册”那!!

相信大家从小就被千叮万嘱要做单元测试。然后秉承这一信念,成为了一个测试狂魔。凡有代码,测!覆盖!最终,一波操作猛如虎:467测试,0错误,0自信

第二天。

你为了优化,颤抖着手更改了一行代码。果不其然发现牵连了1e9个测试用例,测试结果瞬间变成满江红。
你开始怀疑自己,单元测试到底为了什么?除了增加大量工作量以外,好像没什么好处?

从此你与单元测试势不两立…………先等等!

如果你有这样的疑问,说明两个问题:

  1. 你的单元测试做错了
  2. 这篇文章会是帮助你解决问题的第一步

怎么测

在说“测什么”的时候,先解决比较简单的“怎么测”问题。
没错,反直觉的是“怎么测”比“测什么”更加简单……

选择方法

单元测试野路子不多,老师教的理论也无非只有两种:白盒测试、黑盒测试。
那么我们应该选择哪种?

太长不看:黑盒测试

从现在开始可以忘掉白盒测试这个名词了……这个东西在工程上根本不实际。

你给我等等,你一句话就把代码覆盖率说的一文不值?

其实我也没说代码覆盖率没用,我只是纯diss白盒测试。

“高代码测试覆盖率”是“代码质量高”的必要不充分条件

白盒测试为了跑高代码覆盖率,追求走遍代码的每一个角落。
嗯。是。代码覆盖率是很高了。然后呢?这些用例意义的目的是什么?
目的仅仅是为了得到一个高覆盖率吗?
目标仅仅是刷kpi没事找事吗?
它到底验证了什么问题?

好好好,让我们祈祷这位程序员可以保持这种劲头
因为当代码重构后,可难免重新再做一轮白盒测试咯~


说到底,测试测的是软件行为,而不是代码结构。

好的测试应该在代码内部重构(行为不变)时保持不变。利益相关者、终端用户在乎的也只有软件的行为,软件内部的实现真的没人会管;而如果测试依赖于代码结构,那重构时改变了代码结构,测试也需要连带改动。

所以这不就说明,应该要用黑盒测试取代白盒测试吗?

流程

那么又该使用什么流程进行单元测试?

黑盒测试本身是很简单:给定输入,验证输出即可。
但测试目标不仅是测试通过,还应是能让大伙一目了然的知道测试干的是什么,被测试的函数又干的是什么。

为此,我建议:

  1. 一个函数一个测试情景
  2. 函数名说明该测试是什么
  3. 测试体中使用“准备”、“操作”、“断言”三部曲

例子:
测试一个函数,其作用是执行一个加法。

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#的,但道理是相通的。

  1. 一个函数代表一个测试情景,但不代表只能有一个用例。如果你想对同一操作执行多组边缘值的测试,你可以考虑复用这个函数。
  2. 函数名用于描述这个用例干什么,是一个适合使用自然语言描述“你在干什么”的好地方。命名规范随意,说明问题/其他人能接受即可。
  3. 函数体清楚分为三个部分。通常使用 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一大堆依赖项!

这是因为你测试了一切,这是单元测试最大的痛苦来源。

有些东西它本身就不适合被单元测试……
说到底,只应编写高性价比的单元测试。
进行单元测试之前,我们应该进行权衡:

  1. 对这个东西写测试难不难?
  2. 这个单元测试给我带来的效益有多大?
  3. 这个单元它是否扮演了重要角色?

单元测试难不难写?

烂代码导致的烂测试

如果你要测的模块跟上节提及的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行为。

  1. 如果你的A类完全只是胶水代码,即缝合B、C、D三个模块的。其本身逻辑基本为0,那没有必要测试。
  2. 如果你的A类除了使用到B、C、D三个模块以外,其本身有非常复杂而必要的逻辑。A本身的独立逻辑占到了95%以上。没什么好说的,进行单元测试可以确保模块没有出错。
    单元测试能避免日后被这坨屎山莫名背刺,四舍五入直接延年益寿。虽然你需要模拟B、C、D三个依赖项,但仍然是值得的。
  3. 但是也存在不少情况,处于两者之间的灰色地带,那么测还是不测,还是选部分测?见仁见智吧。
    • 不测:反正也能够在集成测试中保证模块运作。而且Mock这些依赖项太麻烦了。
    • 测:别跟我说集成测试。我单元测试强迫症,测完我舒服了,也有双重保险了。
    • 部分测:我是和事佬。你们别吵了。挑简单的测,难的不测,不就好了?

被测单元的重要性

哥们衡量单元测试的效益不仅仅是工作量、维护性的问题,还得关注这个模块本身到底重不重要。
比如:这是一个支付模块,要是出错可能要赔几十个,甚至牢底坐穿。
那你说测不测嘛,肯定得测咯!

小结

当遇到难以测试的问题时,第一步先别责怪单元测试本身。而是问问自己,这个代码单元能不能再细分、结构优化。

当你确定已经没有优化的余地了,但感觉还是难以测试,考虑性价比:

  1. 这个东西用到了多少依赖项?本身又有多少自己的逻辑?
  2. 我重构这个代码(包括依赖项的调用变更)是否频繁?
  3. 我不测试会不会有严重的后果?会不会有人身安全隐患()?

思考这三个问题,自然有答案。
不要忘了,即使不单元测试,你还是会(至少应该!)编写包含该模块的集成测试来确保它正常运行。

但如果你问我的想法,经历了这段时间的捣鼓,我会回答:
不不不,我大概率不会对这种东西进行单元测试。

测单元的什么

从另一个角度看待问题。

当我确定要对这个模块进行单元测试了,我应该测它的啥?
第一节中说要进行黑盒测试。那我们就将系统看作一个黑盒,即一个一般化的物体。

我们说要测试行为,其实就是测试黑盒物体与外界的交互行为。
而行为的媒介即消息。
物体与外部的交互,可以视为消息的传达与回复。

所以整个问题变成了:我们需要针对什么消息进行单元测试?
在此之前,先看看消息本身到底是什么:

消息的流向

这个物体跟外界的沟通只有两个方向:入向消息,出向消息。
此外,物体还会有内部消息流向。

如果你要我当一次灵魂画师,我会画出下面这样的图😀:

          ┏━━━━━━━━┓
--[in]--> ┃ object ┃ --[out]-->┗━━━━━━━━┛↓-[self]-↑

从函数的角度来讲:

  • 入向消息就是单元被调用
  • 出向消息就是单元调用其它模块
  • 内部消息就是:单元内部的函数调用

消息的分类

消息本身分为两类:查询(Query)和命令(Command)

  1. 查询是有回复(返回值),但不对系统产生影响的操作
    • 比如上面的Addition加法函数
  2. 命令是无回复,但对系统产生影响的操作
    • 比如数据库更新操作

当然,也存在同时为查询和命令的操作,比如Stack.Pop()
虽然如此,我们还是可以按消息的类别考察某种消息是否需要被测试。

需要测试的消息

已知:

  1. 消息的流向:In, Out, Self
  2. 消息的种类:Query, Command

好,我们直接做一个笛卡尔积,得到6种组合。
这些组合有些是需要测试的,有些是无需测试的。

魔法师Sandi Metz曾发表过一次演讲The Magic Tricks of Testing,讲述了如何理解这6种组合在测试中的含义。

有兴趣的可以观看上面的演讲,实在受益匪浅。
我这里就不卖关子,直接剧透6种消息组合的测试操作:

MessageQueryCommand
In1.a 检测结果1.b 检测直接公共副作用
Self2.a 不要测试2.b 不要测试
Out3.a 不要测试3.b 确保消息送出
1. 模块被调用
a: 函数有返回值

直接检测调用结果。
这就是我们第一节中测试Addition的情景

b: 如果函数无返回值

检测它的直接影响(副作用)。
最简单地,比如一个属性,有gettersetter

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: 调用一个外部查询
这不就是我们第一节的第二个例子吗?
外部依赖项MagicMysteryAddition调用了。

我们站在MysteryAddition模块的角度,这就是一个出向消息。我们需要对这个消息,即Magic.DoMagic()的行为进行验证吗?
没有吧。我们要单元测试的是MysteryAdditionMagic正不正确,关我什么事喔,你去找他咯。

但还没完,回顾那个例子
我们要让代码执行下去以测试入向信息啊

所以遇到这种情况,我们虽然不测试出向消息,但如果我们需要单元测试继续执行下去,需要模拟这个消息的返回。比如我们在上面,使用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个方向。

其中我们只需要单元测试入向与出向消息。

  1. 入向消息
    • 是最常规的“调用”、“预测”三部曲。
  2. 出向消息
    • 只关注出向命令是否正常送出。
    • 而出向查询,我们不测试。但有时我们需要结果让代码跑下去,给它一个既定的返回值。

总结

说实话,单元测试绝对是一个被低估的大魔王
它给人的感觉非常和善,人畜无害
但实际上笑里藏刀

打败这个大魔王的奖励非常丰厚,它可以带给我们:时间、效率、收益
但是,他没那么容易被打败

希望这篇文章成为你打败它的究极魔咒()

师傅别念了

参考资料

不仅仅是参考资料。如果你有时间,我建议你也阅读这些资料。

  1. Test Desiderata
  2. Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz
  3. Katrina Owen - 467 tests, 0 failures, 0 confidence - Railsberry 2013
  4. Justin Searls – Please don’t mock me
  5. Should I write unit test for controller or service layer or both of them?
  6. Testing a service method by Unit Test?
  7. Should I bother unit testing my repository layer

Bonus

如果你在想下面这些MVC中的层次是否需要单元测试,可以从上面的资料获得答案。
长话短说:

  1. Repository:直接集成测试,不要单元测试
  2. Controller:直接集成测试,不要单元测试
  3. Service:是否单元测试取决于该服务是否独立,依赖项是否少,性价比是否高

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

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

相关文章

Dynamic Coarse-to-Fine Learning for Oriented Tiny Object Detection(CVPR2023待补)

文章目录 BeginningAbstract挑战方法成果 Introduction引出问题早期的work及存在的问题近期的work及存在的问题our workContribution Related Work&#xff08;paper for me&#xff09;Oriented Object DetectionPrior for Oriented ObjectsLabel Assignment Tiny Object Dete…

Opencv 入门三(视频滑动条窗口)

视频滑动条窗口源码如下&#xff1a; #include "opencv2\highgui\highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream> #include <fstream> using namespace std; int g_slider_position 0; // 滑动条的位置 …

uniapp uview1.0 页面多个upload上传、回显之后处理数据

<view class"img-title w-s-color-3 f-28 row">商品图片</view><u-upload ref"images" :header"header" :file-list"fileListImages" :action"action" name"iFile" icon-name"camera"u…

论文学习——泰森多边形法在小流域面雨量计算中的应用

文章目录 0 摘要00 引言1 研究区域概况2 泰森多边形的建立3 流域多年面降雨量分析4 典型降雨场次面雨量分析5 典型降雨日面雨量分析6 结论7 个人总结0 摘要 研究泰森多边形算法,在小流域面雨量计算中的适用性。选取3种不同降雨量实例,流域多年面降雨量、典型场次、典型日面雨…

108基于matlab的使用模拟退火 (SA) 求解并行机器调度的程序

基于matlab的使用模拟退火 &#xff08;SA&#xff09; 求解并行机器调度的程序&#xff0c;程序已调通&#xff0c;可直接运行。 108 matlab模拟退火 &#xff08;SA) (xiaohongshu.com)

如何使用支付宝的沙箱环境在本地配置模拟支付并发布至公网测试

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 前言 在沙箱环境调试支付SDK的时候&#xff0c;往往沙箱环境部署在本地&#xff0c;局限性大&#xff0c;在沙箱环境…

Redis一些常用的技术

文章目录 第1关&#xff1a;Redis 事务与锁机制第2关&#xff1a;流水线第3关&#xff1a;发布订阅第4关&#xff1a;超时命令第5关&#xff1a;使用Lua语言 第1关&#xff1a;Redis 事务与锁机制 编程要求 根据提示&#xff0c;在右侧编辑器Begin-End补充代码&#xff0c;根据…

react useMemo的使用

useMemo 是 React 提供的一个钩子&#xff08;Hook&#xff09;&#xff0c;用于优化性能&#xff0c;特别是在处理复杂的函数或计算密集型操作时。useMemo 通过记住&#xff08;缓存&#xff09;一个函数的返回值来减少不必要的重复计算。当您使用 useMemo 时&#xff0c;只有…

ping地址127.0.0.1相关学习

目录 1、ping通 127.0.0.1 是否需要网络连接 2、如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样 3、这三个IP的区别 4、网络通信中&#xff0c;IP地址是如何分配和使用 5、如何确定IP地址的网络位和主机位 1、ping通 127.0.0.1 是否需要网络连接 127.0.0.1 是本地…

MySQL是如何保证数据不丢失的?

文章目录 前言Buffer Pool 和 DML 的关系DML操作流程加载数据页更新记录 数据持久化方案合适的时机刷盘双写机制日志先行机制日志刷盘机制Redo Log 恢复数据 总结 前言 上篇文章《InnoDB在SQL查询中的关键功能和优化策略》对InnoDB的查询操作和优化事项进行了说明。但是&#…

【MyBatis学习笔记】MyBatis基础学习

MyBatis基础 MyBatis简介MyBatis特性MyBatis下载和其他持久化层技术对比 核心配置文件详解默认的类型别名 搭建MyBatis开发环境创建maven工程创建MyBatis的核心配置文件创建mapper接口创建MyBatis的映射文件通过junit测试功能加入log4j日志功能 MyBatis获取参数值的两种方式&am…

【无人机学习篇】构建mavros机载电脑连接,从机载电脑获取pixhawk数据

&#xff08;本文基于的pixhawk版本&#xff1a;6X minibase V2.2 &#xff0c;固件&#xff1a;apm&#xff09; 整个的步骤&#xff08;baseline&#xff09;&#xff1a; 具体的每一步都可以在网上查到教程&#xff0c;这里只是梳理出一个流程。并且ubantu与ros的版本也不是…

【C++多线程编程】(五)之 线程生命周期管理join() 与 detach()

在C中&#xff0c;std::thread 类用于创建和管理线程。std::thread 提供了两种主要的方法来控制线程的生命周期&#xff1a;join 和 detach。 detach方式&#xff0c;启动的线程自主在后台运行&#xff0c;当前的代码继续往下执行&#xff0c;不等待新线程结束。join方式&…

Java开发框架和中间件面试题(2)

8.说说自己对Spring MVC的了解&#xff1f; MVC是一种设计模式&#xff0c;Spring MVC是一款很优秀的MVC框架。Spring MVC可以帮助我们进行更简洁的Web层开发&#xff0c;并且它天生与Spring框架集成。SpringMVC下我们一般把后端项目分为Service&#xff08;处理业务&#xff0…

网上商城怎么做:五大关键步骤

在数字化浪潮中&#xff0c;开设网上商城已经成为商业发展的必然趋势。然而如何搭建一个功能齐全、用户体验优良的网上商城并非易事。以下为您介绍网上商城怎么做的五大关键步骤。 第一步&#xff0c;明确商业模式和定位 在开始搭建网上商城之前&#xff0c;需要清晰地定义你…

基于Java+SpringBoot+Mybaties-plus+Vue+ElementUI+Vant 电影院订票管理系统 的设计与实现

一.项目介绍 基于SpringBootVue 电影院订票管理系统 分为前端和后端。 前端&#xff08;用户&#xff09;&#xff1a; 登录后支持查看首页、电影、影院和我的信息 支持查看正在热映和即将上映的电影信息 支持购票&#xff08;需选择影院座位&#xff09;、看过&#xff08;评论…

低代码平台

什么是低代码 低代码&#xff08;Low-Code&#xff09;是一种软件开发方法&#xff0c;旨在通过最小化手动编码的工作量&#xff0c;提高应用程序的开发效率。低代码平台通常提供可视化界面、预建组件、模板和自动化工具&#xff0c;以减少编码工作&#xff0c;使非专业开发人…

java使用面向对象实现图书管理系统

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

每日一练 | 华为认证真题练习Day152

1、树型网络拓扑实际上是一种层次化的星型机构&#xff0c;易于扩充网络规模&#xff0c;但是层级越高的节点故障导致的网络问题越严重。 A. True B. False 2、路由表中某条路由信息的Proto为Direct&#xff0c;则此路由的优先级一定为0 A. True B. False 3、路由器获得路由…

vue3 使用setup语法糖之后设置页面name方法

目录 vue2的设置方法 vue3的设置方法 1.vue3兼容vue2的写法 2.vue3没有使用setup语法糖 3.vue3的setup会自动生成name 4.使用插件 设置name的用处&#xff1a;页面缓存需要识别页面的唯一name属性才可以缓存 vue2的设置方法 <script>export default {name: "…