[软件测试·研究向] MuJava 工具遇到的问题汇总和体会

MuJava 是初学者(研究向)常常会去使用的一个工具,也是 Java 软件测试的一个老牌工具。用于为 Java 代码生成变异体和运行单元测试。但是此工具已经有十年没有更新了,这款软件可以说现在已经不能够支持对主流软件框架运行测试。但是使用它进行简单代码的测试还是可以的。

下面我将就我在研究中遇到的一些坑点和各位探讨一下。本人也是软件测试方面的一个小萌新,文章多有疏漏,尽请谅解。

一、MuJava 的配置

MuJava 需要运行在 JDK 1.8 (Java 8)环境下,仅实验性支持 JDK 9。而 MuJava 仅支持语言级别在 5 以下的代码运行测试。可以说是考古了,本人在学习时一开始也不知道它并不支持很多新的特性。

关于配置方面,我相信 CSDN 上也有其他教程,比如这篇:https://blog.csdn.net/wkw1125/article/details/51967630,我就不再啰嗦了。显然,MuJava 依赖 OpenJDK(解析代码 AST) 和 JUnit4(运行单元测试),你需要把它们的包添加到类路径或者使用系统环境变量以便于在 JDK 8 下 mujava 能够识别到依赖。

当你明白无论什么代码结构都可以使用 AST 去解释并生成中间代码时,你只需要略微知道各类突变是怎么实施的,你就可以编写一套属于自己的插桩工具。对 Java 代码进行解析的主流软件包我在以前的文章也介绍过(JavaParser)。

一般地,我们拥有 4 个目录,class 里面存放编译后的字节码(必须是 JDK8 或者更低版本编译的),result 存放生成的变异体和日志文件,src 存放源代码(.java 文件),testset 存放单元测试用例。

并且,我们已经编写好了用于运行的 bat 脚本:

比如 GenMutants.bat:

@echo off
java -Dfile.encoding=utf-8 mujava.gui.GenMutantsMain
pause

一定要注意配置好 mujava.config 配置文件(填写根路径的绝对地址),否则无法正常运行。

二、常见变异算子表

变异算子就是突变(变异)测试主要的特点,它通过人为向源代码引入缺陷,来辅助提升生成测试数据的覆盖效果和挖掘深层次的代码缺陷。下面结合 PiTest 和 MuJava 整理出常用的变异算子:

  算术运算符替换(Arithmetic Operator Replacement, AOR)

  • 将算术运算符(+, -, *, /, %)替换为其他算术运算符。
  • 例如:将 a + b 变异为 a - b。

·  关系运算符替换(Relational Operator Replacement, ROR)

  • 将关系运算符(<, <=, >, >=, ==, !=)替换为其他关系运算符。
  • 例如:将 a < b 变异为 a <= b。

·  条件边界变异(Conditional Boundary Mutation, CBO)

  • 修改条件语句的边界值。
  • 例如:将 if (a < b) 变异为 if (a <= b)。

·  逻辑运算符替换(Logical Operator Replacement, LOR)

  • 将逻辑运算符(&&, ||, !)替换为其他逻辑运算符。
  • 例如:将 a && b 变异为 a || b。

·  返回值突变(Return Values Mutation, RVM)

  • 修改方法的返回值。
  • 例如:将 return x; 变异为 return x + 1;。

·  常量替换(Constant Replacement, CR)

  • 将常量替换为其他值。
  • 例如:将 const int x = 5 变异为 const int x = 0。

·  自增自减变异(Unary Operator Insertion, UOI)

  • 添加或删除自增(++)或自减(--)运算符。
  • 例如:将 a++ 变异为 a--。

·  语句删除(Statement Deletion, SDL)

  • 删除代码中的某些语句。
  • 例如:删除 if 条件中的一个分支。

·  空返回值替换(Void Method Call, VMC)

  • 替换void方法调用。
  • 例如:将 voidMethod() 变异为不调用该方法。

·  构造函数调用变异(Constructor Call, CC)

  • 修改或替换构造函数调用。
  • 例如:将 new ObjectA() 变异为 new ObjectB()。

·  方法调用变异(Method Call, MC)

  • 修改或替换方法调用。
  • 例如:将 object.methodA() 变异为 object.methodB()。

·  取反条件变异(Negate Conditionals, NC)

  • 取反条件表达式。
  • 例如:将 if (a < b) 变异为 if (!(a < b))。

·  二元逻辑运算符替换(Binary Logical Operator Replacement, BLOR)

  • 将二元逻辑运算符替换为其他二元逻辑运算符。
  • 例如:将 a & b 变异为 a | b。

·  移位运算符替换(Shift Operator Replacement, SOR)

  • 将移位运算符替换为其他移位运算符。
  • 例如:将 a << b 变异为 a >> b。

·  赋值运算符替换(Assignment Operator Replacement, AOR)

  • 将赋值运算符替换为其他赋值运算符。
  • 例如:将 a += b 变异为 a -= b。

在生成符合实际的突变并最大限度地减少生成等价变异体方面, PiTest 这个活跃的项目做得比 MuJava 好得多。PiTset 目前正采用自定义的过滤器过滤等价变异体以避免生成变异体过程中产生大量的冗余突变。而 MuJava 只按照不同变异算子的排列组合无序生成所有的突变,并且使用者不能够选择生成的质量。PiTset 针对在软件开发中真正容易出现错误的位置生成合理的突变,而不是盲目地生成根本不可能存在或者极其难以存在的突变。

注意:1)MuJava 会检查代码是否可以编译,如果没法编译,则在遍历 AST 时就会失败;

           2)MuJava 容易产生冗余突变和等价突变体,容易产生死亡突变体(无法编译的);

           3)对于新增加的突变体,MuJava 不支持;

           4)对于一些代码,MuJava 在生成变异体时会出现失败,但日志不给出失败的具体原因。似乎在生成 AORB 等时,容易出现路径解析错误,这应该是一个 bug,可以观察到 null 出现在地址中;

三、MuJava 存在的兼容性问题

MuJava 官网给出的迷惑性解释容易造成误解,MuJava 虽然支持运行在 JDK 8 环境,但实际上对于高于语言级别 4 的代码特性都是不支持的。

1)生成 AORB 和 SDL 等少数几个变异时,有几率出现路径解析失败,导致无法生成最终的突变,MuJava 作了跳过处理,但是编号竟然不是修正的编号?;

2)对于 java 中带有 final 修饰的方法或者变量,如需纳入测试,需要手动删除该修饰符,因为此修饰符会阻止我们修改变量的值。

3)MuJava 会对循环体本身进行突变,在不需要此类变异体的情况下,需要检查变异日志(mutation_log)并予以删除;

4)代码中包含各种内部类、外部类的,均会导致 MuJava 编译失败,提示 [OJException] mujava.OpenJavaException:  can't generate parse tree。(提示:所有显示该错误的,就是代表语言级别不支持,AST 解析失败)

唯一的解决方法,就是扁平化你需要测试的代码,可以尝试合并不同类中的方法,如果用到了实体类的创建,则修改创建过程,直接使用其中的静态方法。如果不能合并,则舍弃。对于使用自定义异常处理类的,修改为 Exception 使用默认异常处理。(但我个人觉得这样处理是不科学且耗时的)

5)Mujava 只支持基本的异常处理,对于 try-with-resource 类型的异常处理(try 带有需要结束时自动释放资源的初始化语句)不支持。下面代码使用了 try-with-resource 可以修改为一般的 try...catch:

JDK5 失败:

public class Demo {

    public static void main(String[] args) {

        try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("E:\\in.txt")));

             BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("E:\\out.txt")))) {    //

            int b;

            while ((b = bin.read()) != -1) {

                bout.write(b);

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

或者 JDK9 也失败

public class Demo {

    public static void main(String[] args) throws FileNotFoundException {

        BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("E:\\in.txt")));

        BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("E:\\out.txt")));

        try (bin;bout) {

            int b;

            while ((b = bin.read()) != -1) {

                bout.write(b);

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

必须修改为 JDK4 的:

public class Demo {

    public static void main(String[] args) {

        BufferedInputStream bin = null;

        BufferedOutputStream bout = null;

        try {

            bin = new BufferedInputStream(new FileInputStream(new File("E:\\in.txt")));

            bout = new BufferedOutputStream(new FileOutputStream(new File("E:\\out.txt")));

            int b;

            while ((b = bin.read()) != -1) {

                bout.write(b);

            }

        }

        catch (IOException e) {

            e.printStackTrace();

        }

        finally {

            if (bin != null) {

                try {

                    bin.close();

                }

                catch (IOException e) {

                    e.printStackTrace();

                }

                finally {

                    if (bout != null) {

                        try {

                            bout.close();

                        }

                        catch (IOException e) {

                            e.printStackTrace();

                        }

                    }

                }

            }

        }

    }

}

也就是说,try 后面不能有语句块,只能是 { 符号。

6)代码中使用增强 for 循环的也会导致 MuJava 解析失败;需要将增强 for 循环修改为传统的 for 循环,例如:

for(Type tp : tps) {...}    // 增强 for 循环

for(int i = 0; i < tps 的大小; i++) { Type tp = tps[i]; ...}   // 一般 for 循环

7)代码中使用缺省 for 循环,或者循环的自增变量放在循环体外面的均会导致插桩出错,需要严格按照标准书写习惯;

我们都知道 for 循环的条件由三个语句组成:

for(初始语句; 迭代条件; 自增步长) {

这三个语句都可以实施突变,但是由于编码习惯的不同,MuJava 设计者可能未考虑到代码可能是这样:
a. 

int i;

for (i = 0; i < 10; i++) {

b.

int i = 0;

for (; i < 10; i++) {

c.

int i = 0;

for (;; i += 2) {

    if (条件) {

        break;

    }

}

类似这样缺省的都会导致 MuJava 出错或者没法解析 AST。

8) MuJava 误以为 double 类型的数据或者变量前面也可以使用 "~",这会导致生成错误突变体;

9)MuJava 不支持 ArrayList 等 List,可能 Set 也不支持(没试过其他几个)

10)SDL 语句删除突变会容易造成日志记录里面的行号错误。解析日志时需要格外注意。

...

由于 MuJava 存在的问题或者缺陷过多,这里就不一一列举了,反正各位只需要知道语言级别 5 及更高级别的代码它都不能正常处理就可以了。

四、浅谈如何选择待测试的类和方法

在编写变异测试(Mutation Testing)相关理论研究时,选择合适的测试类和方法对测试的全面性和有效性至关重要。以下是一些指导原则,可以帮助选择合适的测试类和待测试的方法。

1. 选择核心功能类

(1)核心功能:首先选择项目中实现核心功能的类。这些类通常包含最关键的逻辑,测试这些类能够最大限度地覆盖项目的主要功能。

(2)高复杂度类:选择那些复杂度较高的类(例如包含大量业务逻辑和条件分支的类),因为这些类更容易包含潜在的缺陷。

2. 考虑类的依赖关系

(1)依赖关系分析:使用依赖关系分析工具(例如JDepend、SonarQube)来识别依赖关系复杂的类。对这些类进行变异测试,可以有效检查它们与其他类的交互是否存在问题。(可以使用 Python 进行依赖关系分析,或者使用专业的工具,如 JDepend

(2)高耦合度类:重点测试那些与其他类耦合度高的类,因为这些类的变动可能会影响到其他多个类。(类依赖图中出度较多的类或者包,需要注意忽略对内部包的依赖

3. 多选择重要方法:

(1)识别边界条件处理方法

通过代码审查和分析测试用例,识别处理边界条件和异常MuJava 在处理包含异常处理的代码时会报错)的代码部分。选择涉及输入验证、数据解析、异常处理的关键方法进行测试。

(2)定位常用公共方法

分析项目的依赖关系图和调用图,识别使用频率较高的公共方法。选择这些常用公共方法进行变异测试,确保其稳定性。(意思就是私有方法一般不选,如果要选,则需要修改为公共方法或者用反射特性擦除权限控制符

4. 覆盖率分析

(1)代码覆盖率工具:使用代码覆盖率工具(例如JaCoCo、Cobertura)来分析哪些类和方法的测试覆盖率较低。选择这些覆盖率较低的类进行变异测试,可以提高测试覆盖率。

(2)分析覆盖率:使用变异分支测试程序进行随机化测试并统计覆盖率,重点选择难覆盖的变异分支。下表列出插桩数量和分布的需求:

指标

低级别

中级别

高级别

代码复杂度

复杂度 < 10: 每个条件分支都应插桩

复杂度 10-20: 选择性插桩主要分支和关键分支

复杂度 > 20: 重点插桩关键业务逻辑和高风险分支

代码行数

代码行数 < 100: 全面插桩,覆盖所有分支和条件

代码行数 100-500: 选择性插桩,重点放在关键逻辑和条件分支上

代码行数 > 500: 重点插桩关键业务逻辑、复杂条件分支和异常处理部分

业务逻辑重要性

辅助业务逻辑: 选择性插桩,重点覆盖关键分支和条件

核心业务逻辑: 全面插桩,确保每个分支和条件都得到测试

核心业务逻辑: 全面插桩,确保每个分支和条件都得到测试

风险评估

低风险代码: 最小插桩,只覆盖主要逻辑路径

中等风险代码: 选择性插桩,重点关注关键分支和条件

高风险代码: 全面插桩,确保所有可能的分支和条件都被测试到

从代码行数看,不同代码行数区间推荐的插桩行数如下:

筛选出的等价变异体数量:

可以看出,插桩行数在源代码行数的 1.5 ~ 2.5 倍左右

5. 业务逻辑分析

根据项目文档和业务逻辑,识别实现核心业务逻辑的类。(这里推荐使用 IDEA的翻译插件快捷查看每个方法的文档注释,里面有方法的功能介绍和测试样例).

  • 需求文档:查看项目的需求文档,找出实现核心需求的类。
  • 用户故事:根据用户故事和用例,识别实现这些功能的类。

6. 变异算子选择

(1)常见变异算子:使用常见的变异算子(例如条件边界变化、算术操作符替换、逻辑操作符替换等)生成变异体。确保这些变异体能够模拟实际可能出现的代码错误。

(2)定制变异算子:根据项目特点和特定的业务逻辑,设计定制的变异算子。这些变异算子应该能够针对项目的特定场景进行变异,从而发现更多潜在缺陷。定制变异算子是根据具体项目或应用场景的需求,设计和实现的特定变异算子。这些算子可以模拟实际开发过程中可能出现的特定错误,超越通用变异算子的范畴,提供更有针对性的测试。以下是一些定制变异算子的示例及其应用场景:

1). 特定业务逻辑变异

在一个处理财务计算的系统中,可以设计定制的变异算子来处理常见的财务错误:

  1. 舍入错误算子:在涉及金钱计算的代码中,将舍入方式从四舍五入改为向下舍入。
  2. 税率变异算子:将预定义的税率值更改为不同的值(例如从5%变为4.5%)。

2). 特定输入处理变异

对于处理特定格式输入的系统,可以设计定制的变异算子来测试输入处理的健壮性:

  1. 日期格式变异算子:将日期输入格式从YYYY-MM-DD变为DD/MM/YYYY。
  2. 编码格式变异算子:将字符串输入的编码从UTF-8变为ISO-8859-1。

示例:银行交易系统

假设我们有一个银行交易系统,以下是可能的定制变异算子示例:

1. 舍入错误算子

// 原始代码

double amount = Math.round(transactionAmount * 100.0) / 100.0;

// 变异代码

double amount = Math.floor(transactionAmount * 100.0) / 100.0;

2. 税率变异算子

// 原始代码

double tax = amount * 0.05;

// 变异代码

double tax = amount * 0.045;

3. 日期格式变异算子

// 原始代码

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

LocalDate date = LocalDate.parse(inputDate, formatter);

// 变异代码

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

LocalDate date = LocalDate.parse(inputDate, formatter);

通过定制变异算子,你可以更有针对性地测试项目中的特定逻辑,发现普通变异算子可能遗漏的问题,从而提高测试的有效性和覆盖率。

五、关于实际开发环境的建议

我个人觉得使用 PiTest、JavaParser、Faker、Mock、Soot 等工具,可能更加适应现今的实际软件审计和软件测试环节。

MuJava 工具可以作为学习研究使用,但也只能够用于一些简单代码的测试,而不能够很好地用于真正的软件测试。

Major 工具在一些方面可能做得比 MuJava 更好,但是它目前没有开源,下一篇我将介绍 Major 的使用细节。


本文出处链接:https://blog.csdn.net/qq_59075481/article/details/140999234。

本文发布于:2024.08.07,更新于:2024.08.07.

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

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

相关文章

软考-软件设计师 (计算机组成和体系结构习题)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…

优秀的行为验证码的应用场景与行业案例

应用场景 登录注册 &#xff1a; 验证码适用于App、Web及小程序等用户注册场景&#xff0c;可以抵御自动机恶意注册&#xff0c;垃圾注册、抵御撞库登录、暴力破解、验证账号敏感信息的修改&#xff0c;同时可以有效阻止撞库攻击&#xff0c;从源头进行防护&#xff0c;保障正…

ip地址冲突会影响整个网络吗

在数字化时代&#xff0c;网络已成为连接世界的桥梁&#xff0c;而IP地址则是这座桥梁上不可或缺的“门牌号”。然而&#xff0c;当这个独特的身份标识出现冲突时&#xff0c;整个网络的稳定运行将面临严峻挑战。IP地址冲突&#xff0c;这一看似微小的技术问题&#xff0c;实则…

【电路笔记】-无源衰减器

无源衰减器 文章目录 无源衰减器1、概述2、简单衰减器3、无源衰减器示例14、无源衰减器设计5、切换式衰减器6、总结无源衰减器是一种特殊类型的电气或电子双向电路,由完全电阻元件组成。 1、概述 无源衰减器基本上是两个端口电阻网络,旨在将电源提供的功率削弱或“衰减”(因…

递归深度问题和尾调用的关系

当我们在编写计算阶乘的函数&#xff0c;一般我们都会会选择使用迭代或递归的方法来实现。下面就让我们看看&#xff0c;同一个函数的两种实现方法。首先&#xff0c;是使用迭代方式实现的函数&#xff0c;我们使用循环的方式来计算阶乘&#xff1a; // 阶乘函数&#xff0c;计…

java之多线程篇

一、基本概念 1.什么是线程&#xff1f; 线程就是&#xff0c;操作系统能够进行运算调度的最小单位。它被包含在进程之中&#xff0c;是进程中的实际运作单位。简单理解就是&#xff1a;应用软件中互相独立&#xff0c;可以同时运行的功能 2.什么是多线程&#xff1f; 有了多线…

无人机之飞行控制系统篇

一、飞行控制系统组成 包括惯性测量单位、GPS接收机、气压高度计、空速计等传感器&#xff0c;以及飞控计算机、伺服作动器等设备。 二、飞行控制原理 通过传感器实时感知无人机的飞行状态&#xff0c;将数据传输给飞控计算机进行处理&#xff0c;计算机再根据预设的飞行计划和…

13-按键的元件模型创建

1.画线的时候&#xff0c;栅格切为10mil 2.放置管脚的时候&#xff0c;栅格切为100mil

开发框架DevExpress XAF v24.2产品路线图预览——增强跨平台性

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;允许同时开发ASP.NET和WinForms。XAF采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 DevExpress XAF是一…

LLaMA- Adapter V2: Parameter-Efficient Visual Instruction Model

发表时间&#xff1a;28 Apr 2023 论文链接&#xff1a;https://arxiv.org/pdf/2304.15010 作者单位&#xff1a; Shanghai Artificial Intelligence Laboratory Motivation&#xff1a;如何有效地将大型语言模型 (LLM) 转换为指令追随者最近是一个流行的研究方向&#xff0…

Linux基于centOS7【内存与OS的随谈】,进程初学【PCB】【fork】【进程排队】

冯诺依曼体系结构——存储器 存储器主要指的是内存&#xff0c;它有个特点就是掉电易失 磁盘等其它输入和输出设备 为什么要在计算机体系结构中要存在内存 我们知道&#xff0c;CPU的处理速度很快很快&#xff0c;但输入设备&#xff0c;以及输出设备&#xff0c;是相对很慢的…

sql注入靶场搭建

1.安装小皮面板&#xff08;PhpStudy&#xff09; 1.从官网下载&#xff1a;http://www.xp.cn 2、Sqli-labs环境安装 准备好sqli-labs-php7-master文件 3.安装之前确保本地没有下载mysql服务器 如果电脑下载了MySQL可以把MySQL的服务停掉 此电脑>右键>管理>服务…

QModbus例程分析

由于有一个Modebus上位机的需要&#xff0c;分析一下QModbus Slave的源代码&#xff0c;方便后面的开发。 什么是Modbus Modbus是一种常用的串行通信协议&#xff0c;被广泛应用于工业自动化领域。它最初由Modicon&#xff08;目前属于施耐德电气公司&#xff09;于1979年开发…

C++:vector容器

概览 std::vector是C标准模板库(STL)中的一种动态数组容器。它提供了一种类似于数组的数据结构&#xff0c;但是具有动态大小和更安全的内存管理。 定义和基本特性 std::vector是C标准库中的一 个序列容器&#xff0c;它代表了能够动态改变大小的数组。与普通数组一样&#x…

模拟面试题1

目录 一、JVM的内存结构&#xff1f; 二、类加载器分为哪几类&#xff1f; 三、讲一下双亲委派机制 为什么要有双亲委派机制&#xff1f; 那你知道有违反双亲委派的例子吗&#xff1f; 四、IO 有哪些类型&#xff1f; 五、Spring Boot启动机制 六、Spring Boot的可执行…

关于儿童编程语言

青少年通常会通过Scratch或Python开始学习编程。在这两种语言中&#xff0c;代码的编写&#xff08;或者在Scratch中是构建&#xff09;方式类似于英语&#xff0c;这使得初学者更容易学习。Scratch的一个重要卖点是对视觉和运动感知学习者非常友好。这些代码块按颜色编码&…

最短路问题中的bellman-ford算法

最短路问题中的bellman-ford算法 题目 如果要处理单源最短路问题当中存在负权边的&#xff0c;那么就需要用到 bellman-ford算法和SPFA算法&#xff0c;一般情况下都是用 SPFA算法&#xff0c;除了有边数限制的情况只能用bellman-ford算法&#xff0c;比如下面这种 题目 给定…

混合密度网络Mixture Density Networks(MDN)

目录 简介1 介绍2 实现3 几个MDN的应用&#xff1a;参考 简介 平方和或交叉熵误差函数的最小化导致网络输出近似目标数据的条件平均值&#xff0c;以输入向量为条件。对于分类问题&#xff0c;只要选择合适的目标编码方案&#xff0c;这些平均值表示类隶属度的后验概率&#x…

《程序猿入职必会(10) · SpringBoot3 整合 MyBatis-Plus》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Linux中DHCP服务器配置和管理

文章目录 一、DHCP服务1.1、DHCP的工作流程1.2、DHCP的工作模式1.3、dhcp的主要配置文件 二、安装DHCP服务2.1、更新yum源2.2、安装DHCP服务软件包2.3、配置DHCP服务2.4、启用DHCP服务&#xff08;解决报错&#xff09;2.4.1、查看dhcpd服务的状态和最近的日志条目2.4.2、查看与…