技术分享|单元测试推广与实战-在全新的DDD架构上进行单元测试


源宝导读:单元测试是伴随软件工程出现和发展的,怎么做大家可能各有见解。本文介绍了单元测试中的反模式,强调了可测试性的重要性,并以 DDD 架构项目的迭代进程作为示例,演示了单元测试的组织过程,展示了单元测试如何影响架构设计,进而提高交付质量。

一、背景

    为了满足日益增长的业务需求,天际-DevOps平台于近期开始了重构工作。由于重构过程对各业务场景进行了重新定义,开发过程推行基于 DDD 的编程架构,所以是推广和落地单元测试的很好时机。粮草未动,兵马先行,这里先介绍单元测试的定义、必要性,接着是引入 dotnet 单元测试相关的知识,然后以反模式示例演示可测试性概念,最后在全新项目上演示整个迭代周期中单元测试的策略与实现细节,并进行小结。

二、单元测试相关的概念、工具和技巧

2.1、单元测试的定义

    单元测试是指对软件中的最小可测试单元进行检查和验证,是最低级别的测试活动。开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。

  • 验证代码与设计相符合;

  • 跟踪需求与设计的实现;

  • 发现设计和需求中存在的缺陷;

  • 发现在编码过程中引入的错误。

2.2、单元测试的必要性

单元测试能在开发阶段发现 BUG,及早暴露,收益高,是交付质量的保证。

  • 来自微软的统计数据显示,bug在单元测试阶段被发现,平均耗时3.25小时,如果漏到系统测试阶段,要花费11.5小时。

  • 85% 的缺陷都在代码设计阶段产生,而发现 bug 的阶段越靠后,耗费成本就越高,指数级别的增高。

2.3、单元测试相关的模式、知识点和工具

Arrange-Act-Assert (AAA) 模式

    AAA(准备、执行、断言)模式是编写待测试方法的单元测试的常用方法。

    一个典型的单元测试用例如下:

[Fact]
public void Add_EmptyString_ReturnsZero()
{// Arrangevar stringCalculator = new StringCalculator();// Actvar actual = stringCalculator.Add("");// AssertAssert.Equal(0, actual);
}

NSubstitute

    该类库对自身的定位是 A friendly substitute for .NET mocking libraries,作为老牌 mock 库 moq 的替代实现。(mock 离不开动态代理,NSubstitute 依赖 Castle Core,其原理另起篇幅描述。

// Arrange(准备):Prepare
var calculator = Substitute.For<ICalculator>();// Act(执行):Set a return value
calculator.Add(1, 2).Returns(3);
Assert.AreEqual(3, calculator.Add(1, 2));// Assert(断言 ):Check received calls
calculator.Received().Add(1, Arg.Any<int>());
calculator.DidNotReceive().Add(2, 2);

使用InternalsVisible ToAttribute测试内部类

    为了避免暴露大量的实现细节、提高内聚性,开发人员应应减少 public 访问修饰符的使用。但是非公开的类和方法如何进行测试?这就是 InternalsVisible ToAttribute 的作用,我们可以在被测项目的AssemblyInfo.cs 文件中添加定义,该特性接受 assembly 名称作为参数,对其暴露内部可见性。

[assembly: InternalsVisibleTo("XXX.Tests")]

    也可以在被测试目标的项目文件 .csproj 中使用,并支持使用项目的上下文变量作为参数名。

<ItemGroup><AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"><_Parameter1>$(MSBuildProjectName).Tests</_Parameter1></AssemblyAttribute></ItemGroup>

    通过以上两种方式,单元测试项目拥有了对被测试项目中 internal 类和方法的访问能力。

扩展方法的测试

    大多数场景下扩展方法不具备可测试性,efcore 中以 Async 结尾的扩展方法,测试它们需要实现 IDbAsync QueryProvider 接口,步骤繁琐,业务实现中应注意扩展方法的可测试性。

2.4、可测试性

    可测试性的回顾仍然十分有必要,大概上可以归于以下三类。

不确定性/未决行为

// BAD
public class PowerTimer
{public String GetMeridiem(){var time = DateTime.Now;if (time.Hour >= 0 && time.Hour < 12){return "AM";}return "PM";}
}

依赖于实现:不可 mock

// BAD: 依赖于实现
public class DepartmentService
{private CacheManager _cacheManager = new CacheManager();public List<Department> GetDepartmentList(){List<Department> result;if (_cacheManager.TryGet("department-list", out result)){return result;}// ... do stuff }
}// BAD: 静态方法
public static bool CheckNodejsInstalled()
{return Environment.GetEnvironmentVariable("PATH").Contains("nodejs", StringComparison.OrdinalIgnoreCase);
}

复杂继承/高耦合代码:测试困难

    随着步骤与流程判断增加,场景组合和 mock 工作量成倍堆积,直到不可测试。

三、实战:在全新的 DDD 架构上进行单元测试

    HelloDevCloud 是一个假想的早期 devOps 产品,提供了组织(Organization)和项目(Project)管理,遵从极简的 DDD 架构,预计的项目结构如下:

$ tree -L 2
.
├── doc
├── HelloDevCloud.sln
├── README.md
├── src
│   ├── HelloDevCloud.Domain                领域对象
│   ├── HelloDevCloud.Domain.Shared
│   ├── HelloDevCloud.DomainService         领域服务
│   ├── HelloDevCloud.EntityFrameworkCore   基于 efcore 的仓储模式实现
│   ├── HelloDevCloud.Infrastructure        基础设施
│   ├── HelloDevCloud.Repositories          DbContext 与仓储
│   └── HelloDevCloud.Web                   Web 接口
└── test├── HelloDevCloud.DomainService.Tests   领域服务测试用例├── HelloDevCloud.RepositoriesTests     DbContext 与仓储测试用例└── HelloDevCloud.Web.Tests             Web 接口测试用例

    基于 DDD 分层架构不一而足,本示例用作单元测试演示。

    目前已有如下领域划分:

  1. 每个组织(Organization)都可以创建一个或多个项目(Project);

  2. 提供公共的 GitLab 用于托管代码,每个项目(Project)创建之时有 master 和 develop 分支被创建出来;

  3. 项目(Project)目前支持公共 GitLab,但预备在将来支持私有 GitLab。

3.1、需求-迭代1:分支管理

    本迭代预计引入分支管理功能:

  1. 每个项目(Project,聚合根)都能创建特定类别的分支(Branch,实体),目前支持特性分支(feature)和修复分支(hotfix),分别从 develop 分支和 master 分支签出;

  2. GitLab 有自己的管理入口,分支创建时需要检查项目和分支是否存在;

  3. 分支创建成功后将提交记录(Commit)写入分支。

前期:分析调用时序

前期:设计模块与依赖关系

  • IProjectService:领域服务,依赖IGitlabClient完成业务验证与调用;

  • IProjectRepository:项目(Project,聚合根)仓储,更新聚合根;

  • IBranchRepository:分支(Branch,实体)仓储,检查;

  • IGitlabClient:基础设施。

前期:列举单元测试用例

  • 项目领域服务:

    • 在 GitLab 项目不存在时断言失败:CreateBranch_WhenRemoteProjectNotExist_ShouldFailed()

    • 在 GitLab 分支已经存在时断言失败:CreateBranch_WhenRemoteBranchPresented_ShouldFailed()

    • 创建不支持的特性分支时断言失败:CreateBranch_UseTypeNotSupported_ShouldFailed()

    • 正确创建的分支应包含提交记录(Commit):CreateBranch_WhenParamValid_ShouldQuoteCommit()

  • 项目应用服务:

    • 在项目(Project)不存在时断言失败:Post_WhenProjectNotExist_ShouldFail()

    • 在项目(Project)不存在时断言失败:Post_WhenProjectNotExist_ShouldFail()

    • 参数合法时返回预期的分支的签出结果:Post_WhenParamValid_ShouldCreateBranch()

中期:业务逻辑实现

  1. 项目(Project )作为聚合根添加分支(Branch)作为组成。

  2. 我们总是需要在远程与本地项目、分支之前进行检查,它们由领域服务组织。

public async Task<Branch> CreateBranch(Project project, string branchName, BranchType branchType)
{var gitProject = await _gitlabClient.Projects.GetAsync(project.Gitlab.Id);// 断言远程项目存在if (gitProject == null){throw new NotImplementedException("project should existed");}// 断言远程分支不何存在var gitBranch = await _gitlabClient.Branches.GetAsync(project.Gitlab.Id, branchName);if (gitBranch != null){throw new ArgumentOutOfRangeException(nameof(branchName), "remote branch already existed");}// 获取签出分支var reference = GetBranchReferenceForCreate(branchType);var request = new CreateBranchRequest(branchName, reference);// 创建分支gitBranch = await _gitlabClient.Branches.CreateAsync(project.Gitlab.Id, request);return project.CheckoutBranch(gitBranch.Name, gitBranch.Commit.Id, branchType);
}private String GetBranchReferenceForCreate(BranchType branchType)
{return branchType switch{BranchType.Feature => Branch.Develop,BranchType.Hotfix => Branch.Master,_ => throw new ArgumentOutOfRangeException(nameof(branchType), $"Not supported branchType {branchType}"),};
}

中期:单元测试实现

  • 领域服务:测试用例见于项目源码 test/HelloDevCloud.DomainService.Tests /Projects/ProjectServiceTest.cs。

  • 应用服务:测试用例见于项目源码 test/HelloDevCloud.Web.Tests/Controllers /ProjectControllerTest.cs。

实战小结

  1. 单元测试用例体现了业务规则;

  2. 单元测试同架构一样是分层的。

3.2、需求-迭代2:支持外部 GitLab,支持分支搜索

本迭代预期添加以下内容:

  1. 支持使用外部 GitLab 上管理分支;

  2. 并支持使用名称搜索组织下的分支列表。

前期:设计模块与依赖关系

前期:列举单元测试用例

  • 项目领域服务:

  1. 使用外部 GitLab 仓库能签出分支:CreateBranch_UserExternalRepository _ShouldQuoteCommit();

  • 分支仓储:

    1. 从配置了外部仓库的项目获取分支应返回符合预期的结果:GetAllByOrganization_ ViaName_ReturnMatched

    中期:业务逻辑实现

    1. 使用新的工厂接口 IGitlabClient Factory 替换 IGitlabClient;

    1. 使用组织 Id 查询分支列表。

    public IList<Branch> GetAllByOrganization(int organizationId, string search)
    {var projects = EfUnitOfWork.DbSet<Project>();var branchs = EfUnitOfWork.DbSet<Branch>();var query = from b in branchsjoin p in projectson b.ProjectId equals p.Idwhere p.OrganizationId == organizationId && (b.Type == BranchType.Feature || b.Type == BranchType.Hotfix)select b;if (string.IsNullOrWhiteSpace(search) == false){query.Where(x => x.Name.Contains(search));}return query.ToArray();
    }
    

    中期:单元测试实现

    • 领域服务:测试用例见于项目源码 test/HelloDevCloud.DomainService.Tests/ Projects/ProjectServiceTest.cs;

    • 仓储实现:测试用例见于项目源码 test/HelloDevCloud.RepositoriesTests/ Implements/BranchRepositoryTest.cs。

        注意:仓储仍然是可测且应该进行测试的,mock 数据库查询的主要工作是 mock IQuerable<T>,但是 mock 数据库读写并不容易。好在 efcore 提供了 UseInMemory Database() 模式,无须我们再提供 FackRepository 一类实现。

    [Fact]
    public void GetAllByOrganization_ViaName_ReturnMatched()
    {var options = new DbContextOptionsBuilder<DevCloudContext>().UseInMemoryDatabase("DevCloudContext").Options;using var devCloudContext = new DevCloudContext(options);devCloudContext.Set<Project>().AddRange(new[] {new Project{Id = 11,Name = "成本系统",OrganizationId = 1},new Project{Id = 12,Name = "成本系统合同执行应用",OrganizationId = 1},new Project{Id = 13,Name = "售楼系统",OrganizationId = 2},});devCloudContext.Set<Branch>().AddRange(new[] {new Branch{Id = 101,Name = "3.0.20.4_core分支",ProjectId = 11,Type = BranchType.Feature},new Branch{Id = 102,Name = "3.0.20.1_core发版修复分支15",ProjectId = 12,Type = BranchType.Hotfix},new Branch{Id = 103,Name = "730Core自动化验证",ProjectId = 13,Type = BranchType.Feature}});devCloudContext.SaveChanges();var unitOfWork = new EntityFrameworkUnitOfWork(devCloudContext);var branchRepo = new BranchRepository(unitOfWork);var branches = branchRepo.GetAllByOrganization(1, "core");Assert.Equal(2, branches.Count);Assert.Equal(101, branches[0].Id);Assert.Equal(102, branches[1].Id);
    }
    

    ANTI-PATTERN:依赖具体实现

        支持外部 GitLab 仓库需要动态生成 IGitlabClient 实例,故在业务逻辑中根据项目(Project)设置实例化 GitlabClinet是很“自然”的事情,但代码不再具有可测试性。

        对应的实现逻辑片段如下:

    //BAD
    -        private readonly IGitLabClient _gitlabClient;
    +        private readonly IOptions<GitlabOptions> _gitlabOptions;-        public ProjectService(IGitLabClient gitlabClient)
    +        public ProjectService(IOptions<GitlabOptions> gitlabOptions){
    -            _gitlabClient = gitlabClient;
    +            _gitlabOptions = gitlabOptions;}public async Task<Branch> CreateBranch(Project project, string branchName, BranchType branchType){
    -            var gitProject = await _gitlabClient.Projects.GetAsync(project.Gitlab.Id);
    +            var gitlabClient = GetGitliabClient(project.Gitlab);
    +            var gitProject = await gitlabClient.Projects.GetAsync(project.Gitlab.Id);+        private IGitLabClient GetGitliabClient(GitlabSettings repository)
    +        {
    +            if (repository?.HostUrl == null)
    +            {
    +                return GetGitlabClient(_gitlabOptions.Value);
    +            }
    +
    +            // 如果携带了 gitlab 设置, 则作为外部仓库
    +            var gitlabOptions = new GitlabOptions()
    +            {
    +                HostUrl = repository.HostUrl,
    +                AuthenticationToken = repository.AuthenticationToken
    +            };
    +            return GetGitlabClient(gitlabOptions);
    +        }
    +
    +        private IGitLabClient GetGitlabClient(GitlabOptions gitlabOptions)
    +        {
    +            return new GitLabClient(gitlabOptions.HostUrl, gitlabOptions.AuthenticationToken);
    +        }
    +    }
    

        对于以上实现,调用 ProjectService 会真实地调用 GitlabClient,注意这引入了依赖具体实现的反模式,代码失去了可测试性。

    [Fact(Skip = "not implemented")]
    public async Task CreateBranch_UserExternalRepository_ShouldQuoteCommit()
    {var project = new Project{Gitlab = new GitlabSettings{Id = 1024,HostUrl = "https://gitee.com",AuthenticationToken = "token"}};// HOW? 
    }
    

        提问:如果需要取消 develop 分支的特殊性,允许用户自行管理,在方法 GetBranch ReferenceForCreate() 上注释掉分支判断是否完成了需求?

    private String GetBranchReferenceForCreate(BranchType branchType)
    {return branchType switch{BranchType.Feature => Branch.Develop,
    -      // BranchType.Feature => Branch.Develop,BranchType.Hotfix => Branch.Master,_ => throw new ArgumentOutOfRangeException(nameof(branchType), $"Not supported branchType {branchType}"),};
    

        可以想象大片的测试用例会挂掉,因为该方法被广泛使用并断言。由于单元测试不再成功,单元测试对业务逻辑的保护也随之消失。如果不修复单元测试,我们就无法保证其他业务不受影响。

    实战小结

    1. 良好的设计具有很好的可测试性,可测试性要求反过来会影响架构设计与领域实现;

    2. 仓储逻辑也能够进行有效的测试;

    3. 单元测试减少了回归工作量,提升了交付质量。

    四、后话

        以迭代紧张为理由在提交业务代码时候忽略单元测试的编写,是项目管理及开发人员对单元测试认识有限的体现。本文描述了定义和必要性,基于 DDD 架构进行了实践,展示了单元测试如何影响业务逻辑甚至是架构设计。

    1. 开发人员应认识和理解单元测试,熟练运用相关工具和技能;

    2. 交付质量保证应在开发阶段就由单元测试覆盖率保证;

    3. 测试先行体现了业务规则,要求逻辑自洽和场景覆盖;

    4. 可测试性要求会倒推架构合理性,避免架构劣化甚至反模式。

    ------ END ------

    作者简介

    冯同学: 研发工程师,目前负责开发云平台相关研发工作。

    也许您还想看:

    技术分享|To B复杂系统的性能测试要注意哪些?

    ERP平台的自动化测试技术实践

    更多明源云·天际开放平台场景案例与开发小知识,可以关注明源云天际开发者社区公众号:

    建模零代码之建模账号接入DevOps 账号体系

    繁星计划·上海站 迈出企业数字化升级赋能第一步

    建模零代码之业务组件的复用

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

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

相关文章

CRM学习笔记(一)

被抽调学习CRM两个礼拜&#xff0c;要回java组了&#xff0c;以后接触机会应该不多了。记录下这段时间的学习总结&#xff0c;以备不时之需。 通过微软提供的视频教程&#xff0c;基本上你想了解的一些很基础的问题&#xff0c;都能找到入口处&#xff0c;剩下的就是通过sdk和在…

Object-C中的字符串对象1-不可变字符串

2019独角兽企业重金招聘Python工程师标准>>> #import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {autoreleasepool {NSString *str1"this is string A";NSString *str2"this is string B";NSString *temp;NSCom…

linux环境下作业调度,Linux集群环境下作业调度算法的研究与实现

摘要&#xff1a;集群计算环境中的作业调度接收用户提交的作业请求,并采用适当的调度策略选择计算节点资源来运行用户作业。作业调度策略决定了整个集群系统的效率,尤其是提交计算量大的作业时,良好的作业调度可以大大加快执行速度,因此,作业调度策略是提高集群系统执行并行作业…

硬盘分区与故障排解速查手册(1)

在所有计算机配件中&#xff0c;硬盘是一个比较特殊的角色&#xff0c;它不仅关系到系统的整体性能&#xff0c;而且用户的所有资料都保存在它的身上。 那么&#xff0c;硬盘该如何初始化呢&#xff1f;遇到硬盘故障该如何解决呢&#xff1f;一、大硬盘分区与格式化 硬盘分区是…

Ant Design Blazor 发布 0.9.0,共100+人贡献!

???? 截至这个版本&#xff0c;本项目一共迎来 101 位贡献者&#xff0c;是他们成就了这个项目&#xff01;在此感谢他们慷慨的贡献&#xff01;变更记录Tabs???? 增加路由服用多标签页组件 ReuseTabs。(demo) #1704 ElderJames???? 增加 OnClose 事件与 TabTempla…

人类究竟有多喜欢看热闹?! | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅&#xff08;图源网络&#xff0c;侵权删&#xff09;

linux2.6添加新硬盘,Linux_TurboLinux11添加新硬盘方法,一.Linux的硬盘识别2.6 kernel - phpStudy...

2.6 kernel以后,linux会将识别到的硬件设备,在/dev/下建立相应的设备文件.如:sda表示第1块SCSI硬盘.hda表示第1块IDE硬盘(即连接在第1个IDE接口的Master口上).scd0表示第1个USB光驱.当添加了新硬盘后,在/dev目录下会有相应的设备文件产生.cciss的硬盘是个例外,它的设备文件在/d…

开始→运行→命令 总结大全

winver-检查Windows版本 wmimgmt.msc打开windows管理体系结构(WMI) wupdmgrwindows更新程序 wscriptwindows脚本宿主设置 write写字板 winmsd-系统信息 wiaacmgr-扫描仪和照相机向导 BR>  winchatXP自带局域网聊天 mem.exe显示内存使用情况 Msconfig.exe-系统配置实用程序…

Windbg在Managed App中设置函数断点的几种方法

本文介绍两种使用Windbg在Managed App中设置断点的方法。一种是在live Debug的时候&#xff0c;attach到了Process之后。另外一种是动态调试的时候&#xff0c;如何给几个模块的特定方法下一个断点。 使用Windbg在Native Code里面下断点是比较方便的&#xff0c;bp加上一个…

Python通过amqp消息队列协议中的Qpid实现数据通信

简介&#xff1a;这两天看了消息队列通信&#xff0c;打算在配置平台上应用起来。以前用过zeromq但是这东西太快了&#xff0c;还有就是rabbitmq有点大&#xff0c;新浪的朋友推荐了qpid&#xff0c;简单轻便。自己总结了下文档&#xff0c;大家可以瞅瞅。AMQP&#xff08;消息…

基于 Blazor 打造一款实时字幕

早先在录制视频的时候一直使用的是 obs-auto-subtitle 作为实时字幕展示功能。不过这个是以 OBS 插件的形式存在&#xff0c;不管是语言和功能上都有一定的限制。故而使用 Blazor server 实现一个。总体思路 实时字幕自然需要语音转文字的功能。考察了一些服务之后&#xff0c;…

一个数学系毕业的物理学家,是怎么拿到诺贝尔化学奖的?

全世界只有3.14 % 的人关注了青少年数学之旅2019年10月9日&#xff0c;这个“特别好”教授&#xff0c;1940年&#xff0c;“特别好”考上了耶鲁&#xff0c;1943年&#xff0c;“特别好”终于拿到数学学士学位。“特别好”特别沮丧&#xff0c;▲ “特别好”在牛津大学&#x…

U盘安装Linux挂载cd,U盘安装Ubuntu Server CD-ROM挂载失败

U盘安装 Ubuntu Server 发生Failed to copy file from CD-ROM问题解决方案使用UltraISO制做Ubuntu Server安装盘&#xff0c;在安装过程当中出现[!!] Load installer components from CD警告&#xff0c;这一步应该是安装文件检查步骤&#xff0c;没有检测到完整镜像文件而提示…

三十五例网络故障排除方法

上网时&#xff0c;我们经常会碰到这样、那样的网络故障&#xff0c;如何应付呢?今天&#xff0c;我们就针对一些常见的故障给大家分析一下! 1.故障现象:网络适配器(网卡)设置与计算机资源有冲突。   分析、排除:通过调整网卡资源中的IRQ和I/O值来避开与计算机其它资源的冲突…

遭遇“烧钱瓶颈” 优酷成本结构堪忧

从奥运结束至今&#xff0c;视频网站的短暂喧嚣终于渐归沉寂。尽管各个视频网站通过拼命烧钱而维系出的“看上去很美”的表象让很多人甚至开始盲目高呼&#xff1a;视频网站的春天已经到来&#xff0c;然而&#xff0c;从之前媒体曝出的“优酷网获千万过桥贷款疑是成本逼迫”到…

XHTML教会我的一些东西-1

第一次写博客&#xff0c;虽然以前写作文是我的强项&#xff0c;我也很能说&#xff0c;但是似乎现在这种能力正在退化。不知为什么&#xff0c;到了大学之后我就变得跟以前不一样&#xff0c;似乎是回到了小学时的我。我在大学开始变得内向、沉默、不去主动和别人交谈。因为这…

杀鸡焉用牛刀!放下Windbg,让dotnet-stack来快速定位死锁原因

我们用来分析CPU过高、死锁问题的常见方案是使用Windbg分析dump文件。但是这种方式存在一些缺点&#xff0c;比如dump文件过大难以下载&#xff0c;windbg使用过于复杂难以掌握等。这里介绍一个小工具dotnet-stack&#xff0c;帮助我们检查托管代码调用堆栈&#xff0c;快速定位…

数学中那些非常奇葩的证明

全世界只有3.14 % 的人关注了青少年数学之旅一、费马大定理证明证&#xff1a;是无理数假设是有理数&#xff0c;p和q是互素正整数那么移项得又由费马大定理可知&#xff1a;与费马大定理(Fermats last therorem)矛盾, Q.E.D. &#xff08;也可易证2的n分之一次方且n属于大于2的…

linux代码段起始地址设置,Arch Linux安装后的一些初始设置简介

配置有线网络。没网络的时候&#xff0c;可以直接设定ip应急&#xff0c;后面 netctl 才是正规设置&#xff1a;复制代码代码如下:# ip addr add 192.168.0.100/24 dev enp0s4# ip link set dev enp0s4 up# ip route add default via 192.168.0.1# echo nameserver 208.67.222.…

安装Linux后的遗留问题

一些Linux用户经常询问这样的问题&#xff1a;Linux能兼容XXX卡么&#xff1f;其实&#xff0c;Linux是一个开放性的系统&#xff0c;只要通过Linux爱好者们的努力&#xff0c;Linux可以兼容任何硬件。 一&#xff0c;声卡 首先要知道声卡的类型&#xff0c;或者是某种声卡兼容…