Spring Boot 整合 Mockito:提升Java单元测试的高效实践

引言

在Java开发领域,Spring Boot因其便捷的配置和强大的功能而受到广泛欢迎,而Mockito作为一款成熟的单元测试模拟框架,则在提高测试质量、确保代码模块间解耦方面扮演着至关重要的角色。本文将详细介绍如何在Spring Boot项目中整合Mockito,以及Mockito的概念、功能点、优势及实际应用案例。

一、Mockito概念

Mockito是一个面向Java开发者的模拟框架,它的核心目标是**通过创建和配置模拟对象**(Mock Objects)来替代真实依赖项,以便在单元测试中有效地隔离被测代码。在Spring Boot应用程序中,Mockito可用于模拟DAOs、Services、Repositories以及其他依赖服务,使得测试仅针对单一的业务逻辑进行验证,而无需启动数据库、网络请求等实际资源。

为什么写单元测试?

  1. 验证功能正确性
  • 单元测试允许开发者针对代码的最小可测试单元(如类、方法)逐一验证它们是否按预期工作,确保每个独立组件的功能正确无误。
  1. 隔离问题定位
  • 当系统出现问题时,单元测试能快速定位具体哪个模块出现了故障,避免因多个模块相互影响而导致的诊断困难。
  1. 支持持续集成/持续部署(CI/CD)
  • 在CI/CD流水线中,单元测试作为构建过程的一部分,确保每次提交的新代码都不会破坏现有的功能。
  1. 促进重构和演化
  • 编写了充分的单元测试后,重构代码时就有了安全网,可以放心地修改内部结构而不必担心会影响到现有功能。
  1. 设计指导
  • TDD(测试驱动开发)提倡先编写单元测试,这有助于推动设计出更易于测试的代码,即模块化程度更高、依赖关系更清晰的设计。
  1. 文档作用
  • 单元测试实际上是另一种形式的文档,它展示了代码如何被预期使用,以及不同输入下产生的输出,是活生生的、可执行的契约。

单元测试的优点

  1. 尽早发现问题
  • 开发阶段就能发现潜在的缺陷,而不是等到集成测试或生产环境中才显现,节省了后期修正的成本。
  1. 提升代码质量
  • 通过全面覆盖边界条件、异常情况和其他关键场景,促使开发人员考虑更多的边缘用例,从而提高代码的健壮性。
  1. 可维护性
  • 有了良好的单元测试覆盖,未来的开发人员更容易理解代码行为,并有信心在修改代码时不会无意中破坏既有功能。
  1. 依赖管理
  • 使用像Mockito这样的框架可以模拟和隔离依赖项,使得测试关注于单个单元本身的行为,不受外部因素的影响。
  1. 迭代速度
  • 单元测试使得开发周期更快,因为开发人员可以迅速验证他们的更改是否有效,无需每次修改后都进行全面的手动回归测试。
  1. 信心保障
  • 经过单元测试的代码提供了额外的信心,尤其是在大型项目中,确保每个模块的质量,有助于形成稳定的软件整体。

一种测试手段,更是提升代码质量、支持敏捷开发和维护软件长期稳定性的有效工具。

二、Mockito功能点

  1. Mock对象创建: 使用Mockito的mock()函数可以轻松创建模拟对象,例如,对于一个UserMapper接口:

UserMapper userMapper = Mockito.mock(UserMapper.class);
  1. 方法行为设置: 可以通过when()方法定义模拟对象的方法调用时的预期行为,例如设置返回值或抛出异常:
// 准备测试数据和模拟行为
 when(userMapper.findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword())).thenReturn(null);

// 执行测试方法并验证期望的异常被抛出
Exception exception = assertThrows(RuntimeException.class, () -> userService.login(testLoginReq));
  1. 验证方法调用: 使用verify()函数来确保模拟对象的方法已经被正确调用:
// Verify that the method was called with the correct parameters
verify(userMapper).findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword());
  1. 参数匹配器: 提供了一系列参数匹配器,如any(), eq(), argThat()等,方便在验证时不需明确指定参数值:
verify(userMapper).findByEmail(argThat(email -> email.endsWith("@example.com")));
  1. Spies: Mockito还支持创建Spy对象,它允许对已有真实对象进行部分模拟,同时保留原有对象的功能:

UserService realUserService = new UserService();
UserServiceImpl userServiceSpy = Mockito.spy(UserServiceImpl);

三、Mockito优势

  • 隔离性:通过模拟依赖项,避免了测试之间不必要的耦合,提高了单元测试的准确性。
  • 简洁性:Mockito API设计简洁明了,使得编写和维护测试代码变得容易。
  • 深度控制:能够精细控制模拟对象的行为,包括方法调用的顺序、次数和异常处理等。
  • 文档作用:通过模拟的交互,反映了被测试代码对外部依赖的使用方式,起到一定的文档作用。

四、Spring Boot整合Mockito案例

添加POM依赖


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.2</version>
    <relativePath/><!-- lookup parent from repository -->
</parent>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

业务方法

@Service
@Slf4j(topic = "UserServiceImpl")
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;


    @Override
    public LoginUserResp login(LoginUserReq loginReq) {
        log.info("loginReq:{}", loginReq);
        User user = userMapper.findUserByUsernameAndPassword(loginReq.getUsername(), loginReq.getPassword());
        if (Objects.isNull(user)) {
            throw new RuntimeException("用户名或密码错误");
        }
        LoginUserResp loginUserResp = new LoginUserResp();
        loginUserResp.setId(0L);
        loginUserResp.setUsername(user.getUsername());
        loginUserResp.setNickName(user.getNickname());
        loginUserResp.setToken("token");
        loginUserResp.setPhone("phone");
        loginUserResp.setUserType(0);
        return loginUserResp;
    }


    @Override
    public Boolean createUser(UserAddReq userAddReq) {
        log.info("userAddReq:{}", userAddReq);
        String email = userAddReq.getEmail();
        if (Objects.isNull(email)) {
            throw new RuntimeException("邮箱不能为空");
        }
        if (!email.contains("@example.com")) {
            throw new RuntimeException("邮箱格式不正确");
        }
        userMapper.insert(userAddReq);
        return Boolean.TRUE;
    }



}

UserServiceImplTest 测试类

假设我们正在测试一个UserService类,它依赖于UserMapper。在Spring Boot测试中,可以利用@Mock注解来自动创建并替换Spring容器中的Mock对象:


@ExtendWith(MockitoExtension.class)
public class UserServiceImplTest {

    @Mock
    private UserMapper userMapper;

    @InjectMocks
    private UserServiceImpl userService;

    private User testUser;
    private LoginUserReq testLoginReq;
    private LoginUserResp expectedLoginResp;

    private UserAddReq validUserAddReq;
    private UserAddReq invalidEmailUserAddReq;
    private UserAddReq nullEmailUserAddReq;

    @BeforeEach
    public void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setUsername("testUser");
        testUser.setNickname("TestNick");

        testLoginReq = new LoginUserReq();
        testLoginReq.setUsername("testUser");
        testLoginReq.setPassword("password");

        expectedLoginResp = new LoginUserResp();
        expectedLoginResp.setId(testUser.getId());
        expectedLoginResp.setUsername(testUser.getUsername());
        expectedLoginResp.setNickName(testUser.getNickname());
        expectedLoginResp.setToken("token");
        expectedLoginResp.setPhone("phone");
        expectedLoginResp.setUserType(0);


        validUserAddReq = new UserAddReq();
        validUserAddReq.setUsername("testUser");
        validUserAddReq.setPassword("testPass");
        validUserAddReq.setEmail("test@example.com");

        invalidEmailUserAddReq = new UserAddReq();
        invalidEmailUserAddReq.setUsername("testUser");
        invalidEmailUserAddReq.setPassword("testPass");
        invalidEmailUserAddReq.setEmail("test@example");

        nullEmailUserAddReq = new UserAddReq();
        nullEmailUserAddReq.setUsername("testUser");
        nullEmailUserAddReq.setPassword("testPass");
        nullEmailUserAddReq.setEmail(null);
    }

    /**
     * 测试使用有效的凭据进行登录时,应成功登录。
     *
     * Arrange 配置测试环境:
     * 设置当使用测试请求中的用户名和密码调用 userMapper.findUserByUsernameAndPassword 方法时,
     * 返回预设的测试用户对象。
     *
     * Act 执行动作:
     * 使用测试登录请求调用 userService.login 方法,获取实际的登录响应。
     *
     * Assert 断言结果:
     * 验证实际的登录响应不为空,并且其各个字段(用户名、昵称、令牌、电话、用户类型)与预期的登录响应相匹配。
     *
     * Verify 验证调用:
     * 验证 userMapper.findUserByUsernameAndPassword 方法确实被使用了正确的参数(测试请求中的用户名和密码)调用。
     */
    @Test
    public void whenValidCredentials_thenSuccessfulLogin() {
        // Arrange
        when(userMapper.findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword())).thenReturn(testUser);

        // Act
        LoginUserResp actualLoginResp = userService.login(testLoginReq);

        // Assert
        assertNotNull(actualLoginResp);
        assertEquals(expectedLoginResp.getUsername(), actualLoginResp.getUsername());
        assertEquals(expectedLoginResp.getNickName(), actualLoginResp.getNickName());
        assertEquals(expectedLoginResp.getToken(), actualLoginResp.getToken());

        // Verify that the method was called with the correct parameters
        verify(userMapper).findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword());
    }


    /**
     * 测试登录服务时,使用无效的用户名和密码应该导致登录失败。
     * 这个测试用例验证当提供的用户名和密码不匹配任何已知用户时,login方法是否抛出运行时异常。
     */
    @Test
    public void whenInvalidCredentials_thenLoginFailure() {
        // 准备测试数据和模拟行为
        when(userMapper.findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword())).thenReturn(null);

        // 执行测试方法并验证期望的异常被抛出
        Exception exception = assertThrows(RuntimeException.class, () -> userService.login(testLoginReq));

        // 验证抛出的异常消息是否匹配预期
        assertEquals("用户名或密码错误", exception.getMessage());

        // 验证userMapper的findUserByUsernameAndPassword方法是否被正确调用
        verify(userMapper).findUserByUsernameAndPassword(testLoginReq.getUsername(), testLoginReq.getPassword());
    }


    /**
     * 测试创建用户功能。
     * 当提供的用户信息有效时,应该成功保存用户信息并返回true。
     */
    @Test
    public void createUser_WithValidUser_ShouldPersistAndReturnTrue() {
        // 准备测试环境
        when(userMapper.insert(any(UserAddReq.class))).thenReturn(1);

        // 执行测试动作
        Boolean result = userService.createUser(validUserAddReq);

        // 验证测试结果
        assertTrue(result);
        verify(userMapper).insert(validUserAddReq);
    }



}

五、异常处理与断言
在Mockito中,可以模拟方法抛出异常,并在测试中捕获和验证:

/**
     * 测试创建用户时使用无效邮箱地址应该抛出异常的情况。
     * 该测试方法不会返回任何值,它的目的是验证当提供一个无效的邮箱地址时,
     * {@link userService.createUser(UserAddReq)} 方法是否会抛出预期的 {@link RuntimeException} 异常。
     * 
     * @param none 该测试方法不接受任何参数。
     * @return void 该测试方法没有返回值。
     * @throws RuntimeException 如果提供的用户添加请求中的邮箱地址无效,该方法将抛出异常。
     */
@Test
public void createUser_WithInvalidEmail_ShouldThrowException() {
    // 断言当尝试使用无效的邮箱创建用户时,会抛出运行时异常
    Exception exception = assertThrows(RuntimeException.class, () -> {
        userService.createUser(invalidEmailUserAddReq);
    });

    // 验证抛出的异常消息是否为预期的错误消息
    assertEquals("邮箱格式不正确", exception.getMessage());

    // 验证用户映射器的 insert 方法是否从未被调用
    verify(userMapper, never()).insert(any(UserAddReq.class));
}


/**
     * 测试创建用户时,如果邮箱为null,应该抛出异常。
     * 这个测试方法不接受任何参数,也不会返回任何值。
     * 它主要通过断言验证在尝试使用null邮箱创建用户时,是否会抛出运行时异常,并且异常的消息文本是否正确。
     */
@Test
public void createUser_WithNullEmail_ShouldThrowException() {
    // Act & Assert: 尝试使用null邮箱创建用户,并验证是否抛出了预期的运行时异常
    Exception exception = assertThrows(RuntimeException.class, () -> {
        userService.createUser(nullEmailUserAddReq);
    });

    assertEquals("邮箱不能为空", exception.getMessage()); // 验证异常消息是否正确
    verify(userMapper, never()).insert(any(UserAddReq.class)); // 验证用户映射器的insert方法是否从未被调用
}

五、统计单元测试覆盖率

一、单元测试覆盖率概念

单元测试覆盖率是指程序中被执行的单元测试所覆盖的源代码行数或分支数占总行数或分支数的比例。通常分为行覆盖率、分支覆盖率、语句覆盖率、方法覆盖率等多种度量维度。理想的覆盖率并非追求100%,而是力求覆盖所有关键路径和边界条件,以最大程度地暴露潜在错误。

二、单元测试覆盖率的重要性

  1. 保证代码质量:高覆盖率意味着更多的代码逻辑经过了直接或间接的验证,有助于减少因未测试代码引入的缺陷。
  2. 推动重构与优化:覆盖率数据可以帮助识别冗余或难以测试的代码段,进而推动代码结构的改进。
  3. 持续集成与持续部署:在CI/CD流程中,设定合理的覆盖率阈值,可以作为构建是否通过的门槛,防止低质量代码流入生产环境。

三、主流覆盖率统计工具

  1. JaCoCo:JaCoCo是一款适用于Java字节码的开源覆盖率工具,它支持无缝集成到Maven、Gradle构建工具和Eclipse、IntelliJ IDEA等IDE中。对于Spring Boot应用,可以通过JaCoCo插件轻松获取和报告单元测试覆盖率。
<!-- Maven中JaCoCo配置示例 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

四、Spring Boot项目中实现覆盖率统计

在Spring Boot项目中,JaCoCo可通过以下步骤实现单元测试覆盖率统计:

  1. 添加JaCoCo相关依赖至构建文件(如上述Maven配置所示)。
  2. 运行单元测试,JaCoCo会在运行时注入代理类收集覆盖率数据。
  3. 测试完成后,JaCoCo会自动生成覆盖率报告,通常位于target/site/jacoco/index.html路径下,打开即可查看详细的覆盖率详情。

此外,在持续集成环境下,可以结合SonarQube等代码质量管理平台,将JaCoCo生成的覆盖率报告导入,实时监控和管理项目的测试覆盖率。

五、本地启用覆盖率

  • 在运行/调试配置对话框中,找到你想要运行的单元测试配置或者创建一个新的JUnit运行配置。
  • 在配置详情页中,找到“Code Coverage”选项卡。

image.png
单元测试报告如下
image.png

六、结论

统计单元测试覆盖率是一项基础且必要的软件工程实践,它能够直观反映测试的质量和全面性。通过合理选择和配置覆盖率工具,配合良好的单元测试策略,开发者能够在不断迭代和演进的软件项目中保持高质量的代码标准,从而降低系统风险,保障产品质量。

六、总结

综上所述,Mockito与Spring Boot的整合为Java开发者提供了一套完整的解决方案,使得单元测试更为精准、高效,从而确保了代码质量、降低了维护成本,并促进了项目的持续集成与交付。通过合理运用Mockito的各项功能,开发者能够编写出高度可信赖且易于维护的单元测试代码。


相关联文章链接: 深入解析与实践Mockito:Java单元测试的强大助手

Git项目地址-对应的project:springboot-mockito-study


欢迎大家一键三连,如果发现文章中有错误或遗漏的地方,欢迎大家指正!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

千锤百炼算法系列之动态规划

题外话 这段时间,我必须把算法弄明白 这篇直接讲解动态规划所有细节! 前面那篇 千锤百炼之每日算法(一)-CSDN博客 也有关于动态规划的讲解,也非常详细 很简单,我成尊不就是了?!!! 正题 动态规划 这里我们主要是让大家明白什么是动态规划,怎么用动态规划解题 我就不用…

手动给docusaurus添加一个搜索

新版博客用docusaurus重构已经有些日子了&#xff0c;根据docusaurus的文档上也申请了Algolia,想一劳永逸的解决博客的搜索问题。但是流水有意&#xff0c;落花无情。 algolia总是不给我回复&#xff0c;我只能对着algolia的申请页面仰天长叹。 正常情况的申请 按照docusaur…

社区论坛小圈子小程序源码系统:自定义小程序管理社区圈子软件圈子系统系统开发-做社区圈子丨圈子论坛社区交友系统开源版小程序源码丨

简述 移动互联网的快速发展&#xff0c;微信小程序作为一种新型的应用形态&#xff0c;已经深入到人们的生活中。特别是对于社区论坛类应用&#xff0c;小程序版本可以更好地满足用户快速、便捷获取信息的需求。下面给大家分享一款社区论坛小圈子小程序源码系统。 在这个信息…

跨境电商MercadoLibre(美客多)平台预约号操作流程自动化系统

目录 一、前置配置准备 1. 安装Chrome插件 2. 添加预约配置 二、开始使用 MercadoLibre&#xff08;美客多&#xff09;于2021年10月18号上线了新预约入仓系统&#xff0c;在MercadoLibre美客多平台上&#xff0c;新入仓预约系统是一项非常重要的功能&#xff0c;它可以帮助…

2024华中杯数学建模挑战赛选题建议及各题思路来啦!

大家好呀&#xff0c;华中杯数学建模开始了&#xff0c;来说一下初步的选题建议吧&#xff1a; 首先定下主基调&#xff0c; 本次华中杯推荐选择C题目。难度方面A&#xff1e;B&#xff1e;C&#xff0c;A是优化类题目&#xff0c;难度较高&#xff0c;建议参考23国赛A优秀论…

STM32G431RBT6移植FreeRTOS

引言&#xff1a; 本文专门为参加了蓝桥杯嵌入式赛道的同学准备&#xff0c; 大家可能会有这样一个问题&#xff0c; 比完赛之后&#xff0c; 对于像继续使用STM32G431RBT6学习FreeRTOS的&#xff0c; 发现网上的教程使用的板子基本上都是F1和F4的&#xff0c; 其实呢&#xff…

《八》QSplitter拆分器以及QDockWidget窗口详解

QSplitter简介 QSplitter拆分器允许用户通过拖动子部件之间的边界来控制它们的大小。 单个拆分器可以控制任意数量的小部件。QSplitter的典型用法是创建几个小部件&#xff0c;并使用insertWidget()或addWidget()添加它们。 常用方法 默认情况下&#xff0c;QSplitter会动态…

甘特图是什么?如何利用其优化项目管理流程?

甘特图是项目管理软件中十分常见的功能&#xff0c;可以说每一个项目经理都要学会使用甘特图才能更好的交付项目。什么是甘特图&#xff1f;甘特图用来做什么&#xff1f;简单来说一种将项目任务与时间关系直观表示的图表&#xff0c;直观地展示了任务进度和持续时间。 一、甘特…

【k8s】:kubectl 命令设置简写启用自动补全功能

【k8s】&#xff1a;kubectl 命令设置简写&启用自动补全功能 1、设置kubectl命令简写2、启用kubectl自动补全功能 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; Kubernetes&#xff08;K8s&#xff09;是一个强大的容器编排平台&#…

【话题】程序员如何搞副业,简单探讨下

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读小5的系列文章&#xff0c;这是《话题》系列文章 目录 背景前提条件打造私域广结朋友平台 技能转化为价值1. 副业途径2. 如何开展3. 未来趋势与建议4. 挑战与策略5. 规划与发展 文章推荐 背景 程序员不仅拥有将抽象概念转化…

极海APM32F003F6U7通过AEC-Q100车规级可靠性认证

行车安全是汽车行业考虑的第一要义&#xff0c;因此汽车电子MCU的可靠性尤为重要&#xff0c;极海APM32F003F6U7车规级MCU遵循AEC-Q100质量标准&#xff0c;确保汽车电子元器件在极端环境下的可靠性和稳定性&#xff0c;并顺利通过了AEC-Q100车规级可靠性认证。 关于AEC-Q100 …

Vitis HLS 学习笔记--ap_int.h / ap_fixed.h(2)-深度探究

目录 1. 前文回顾 1.1 简单背后的复杂 1.2 复杂性的来源 2. 关键代码 2.1 功能概述 2.2 关系梳理 2.3 理解构造函数二 2.4 理解HLS_CONSTEXPR 2.5 理解const volatile 3. 探究ap_int<8> c&#xff1b;经历了什么 4. 在调试中查看 1. 前文回顾 在《Vitis HLS…

基于springboot实现厨艺交流平台系统项目【项目源码+论文说明】

基于SpringBoot实现厨艺交流平台系统演示 摘要 使用旧方法对厨艺交流信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在厨艺交流信息的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时…

[算法] 动态规划

对这个算法的原有印象就是非常难理解&#xff0c;而且怎么都感觉这个算法名称有些误导&#xff1b;或者是要引申着看&#xff1f;因为里面的动态是怎么个动态&#xff1f; 这里的动态是指每一次的计算结果会影响下一次&#xff0c;或者再次的运算效率&#xff0c;也就是说下一次…

瀑布流组件(vue2)

文档连接&#xff1a;clz 加载状态、行数 可以自行控制&#xff0c;目前只支持vue2 实现效果&#xff1a;

华为手机无法弹出wifi上网认证页面处理

华为手机无法弹出wifi上网认证页面 连wifi后跳到上图界面卡住&#xff0c;不跳转到单位的上网认证界面。 打开手机的设置应用&#xff0c;点击上面的WLAN选项。 点击上面的更多WLAN设置选项。 关闭WLAN安全检测就可以正常弹出上网认证界面&#xff0c; 正常弹出上网认证界面&a…

【RAR技巧】rar压缩包的三种加密方法

文件压缩成rar压缩包后&#xff0c;想要保护文件内容不被他人随意解压&#xff0c;我们可以给rar压缩包设置加密&#xff0c;今天分享3种方法设置rar文件加密方法。 方法一&#xff1a;加密 最简单的加密方法&#xff0c;就是在加密文件时输入想要设置的密码&#xff0c;完成…

栈和队列-介绍与实现(超级详解-C语言)

栈 栈的介绍 栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…

Mac中隐私安全性设置-打开任何来源

文章目录 **Mac中隐私安全性设置-打开任何来源**一、目的二、打开方式 Mac中隐私安全性设置-打开任何来源 一、目的 从外部下载的软件频繁打不开&#xff0c;需要从隐私安全性中重新选择一下&#xff1b;默认Mac隐藏了任何来源 二、打开方式 打开终端&#xff0c;输入一下命…

配置BFD

目录 原理概述 实验目的 实验内容 实验拓扑 1.基本配置 2.配置OSPF路由协议 3.配置VRRP协议 4.配置BFD 原理概述 为了减小设备故障对网络业务造成的影响&#xff0c;提高网络的可用性&#xff0c;网络设备需要能够尽快检测到与相邻设备之间的通信故障&#xff0c;以便及…