Java单元测试实战

news2024/10/25 21:30:02

简介

在开发中,发现很多人并不理解什么是单元测试,容易和集成测试混淆,所以专门写一章来讲解,再拓展一下如果获得代码测试覆盖率。我们通常可以将测试分为两大类,一种是集成测试,一种是单元测试。

  • 集成测试:对功能的整体测试,要完整依赖功能的所有代码、组件。比如获得城市详情的功能,不论是从界面点击测试、Postman接口测试、启动服务后代码运行接口,实际上都属于集成测试,运行需要依赖服务启动、数据库操作,完整的运行所有代码
  • 单元测试:对功能单元的测试,这个单元通常是类的方法,一个功能由一个或多个方法调用完成

单元测试

Maven依赖

以下是示例项目springboot-restful的Maven依赖

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>springboot</groupId>
    <artifactId>springboot-restful</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-restful</name>
    
    <!-- Spring Boot 启动父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <mybatis-spring-boot>1.2.0</mybatis-spring-boot>
        <mysql-connector>8.0.19</mysql-connector>
    </properties>

    <dependencies>

        <!-- Spring Boot Web 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot Mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-spring-boot}</version>
        </dependency>

        <!-- MySQL 连接驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector}</version>
        </dependency>
    </dependencies>
</project>

以下是单元测试Maven依赖

<project>
        <!-- Spring Boot Test 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!-- Mockito -->
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.12.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

相关依赖的作用

  • spring-boot-starter-test:MockMvc类需要,用于模拟Http调用测试Controller接口

  • junit:@Before、@Test注解需要,@Before用于在每次测试前执行代码,@Test用于运行单元测试用例,及断言验证

  • mockito-core:@InjectMocks注解、@Mock注解、Mockito类使用,用于Mock调用及断言验证

示例业务

CityDao用于访问数据库操作

public interface CityDao {

    List<City> findAllCity();

    City findById(@Param("id") Long id);

    Long saveCity(City city);

    Long updateCity(City city);

    Long deleteCity(Long id);
}

CityService用于定义业务逻辑接口

public interface CityService {

    /**
     * 获取城市信息列表
     */
    List<City> findAllCity();

    /**
     * 根据城市 ID,查询城市信息
     */
    City findCityById(Long id);

    /**
     * 新增城市信息
     */
    Long saveCity(City city);

    /**
     * 更新城市信息
     */
    Long updateCity(City city);

    /**
     * 根据城市 ID,删除城市信息
     */
    Long deleteCity(Long id);
}

CItyServiceImpl用于实现业务逻辑接口

@Service
public class CityServiceImpl implements CityService {

    @Autowired
    private CityDao cityDao;

    @Override
    public List<City> findAllCity(){
        return cityDao.findAllCity();
    }

    @Override
    public City findCityById(Long id) {
        City city = cityDao.findById(id);
        if(city == null) {
            City defaultCity = new City();
            defaultCity.setId(0L);
            defaultCity.setProvinceId(0L);
            defaultCity.setCityName("默认城市");
            defaultCity.setDescription("默认城市");
            return defaultCity;
        }else {
            if(city.getId() < 0) {
                City defaultCity = new City();
                defaultCity.setId(-1L);
                defaultCity.setProvinceId(-1L);
                defaultCity.setCityName("省份");
                defaultCity.setDescription("省份");
                return defaultCity;
            }
        }
        return city;
    }

    @Override
    public Long saveCity(City city) {
        return cityDao.saveCity(city);
    }

    @Override
    public Long updateCity(City city) {
        return cityDao.updateCity(city);
    }

    @Override
    public Long deleteCity(Long id) {
        return cityDao.deleteCity(id);
    }
}

CityRestController用于提供对外接口

@RestController
public class CityRestController {

    @Autowired
    private CityService cityService;

    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.GET)
    public City findOneCity(@PathVariable("id") Long id) {
        return cityService.findCityById(id);
    }

    @RequestMapping(value = "/api/city", method = RequestMethod.GET)
    public List<City> findAllCity() {
        return cityService.findAllCity();
    }

    @RequestMapping(value = "/api/city", method = RequestMethod.POST)
    public void createCity(@RequestBody City city) {
        cityService.saveCity(city);
    }

    @RequestMapping(value = "/api/city", method = RequestMethod.PUT)
    public void modifyCity(@RequestBody City city) {
        cityService.updateCity(city);
    }

    @RequestMapping(value = "/api/city/{id}", method = RequestMethod.DELETE)
    public void modifyCity(@PathVariable("id") Long id) {
        cityService.deleteCity(id);
    }
}

Service单元测试

建议将以下类方法静态引入,这样可以简化写法,不需要指明那个类

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.mockito.Mockito.*;

以下为CityService类的单元测试,通过Mockito、junit不需要启动整个应用进行快速测试,将结合代码逐行讲解

public class CityServiceTest {
    
    /**
    * @InjectMocks属于mockito-core包
    * @InjectMocks注解用于标识被测试类,会将@Mock、@Spy注入到被测试类中
    * 如果标识的是一个类,则会创建该类的实例;如果被标识的是一个接口,需要手动new该接口的实现
    **/
    @InjectMocks
    private CityService componentService = new CityServiceImpl();
    
    /**
    * @Mock属于mockito-core包
    * @Mock注解用于标识测试类中被依赖的类,会创建一个该类或接口的代理类,不会调用真实的类代码
    * 我们可以应用这种特性,在测试方法时,让被依赖类根据不同参数返回预期的值
    **/
    @Mock
    private CityDao cityDao;

    /**
    * @Before属于junit包
    * @Before注解用于在@Test标识的测试用例运行前执行代码
    **/
    @Before
    public void setUp() {
        //MockitoAnnotations类属于mockito-core包
        //openMocks(this)作用是根据@InjectMocks、@Mock注解生成测试代理类
        //结合@Before注解就是每次运行测试用例之前都会重置测试代理类
        MockitoAnnotations.openMocks(this);
    }

    /**
     * @Test属于junit包,用于作为测试用例运行
     * 验证根据ID获得城市
     * 正常情况
     *
     * @see org.spring.springboot.service.CityService#findCityById(Long)
     */
    @Test
    public void findCityById_NormalTest() {
        //定义cityDao.findById(10L) Mock返回的数据
        City city = new City();
        city.setId(10L);
        city.setProvinceId(10L);
        city.setCityName("正常城市");
        city.setDescription("正常介绍");

        //设置当执行CityService被测试方法,依赖的cityDao在输入10时
        //返回city(id=10,provinceId=10,cityName=正常城市,description=正常介绍)
        //这也就是常说的Mock
        when(cityDao.findById(10L)).thenReturn(city);

        //真实执行测试用例,如果debug会发现findCityById的代码被真实执行了
        //而cityDao则生成了一个代理类,返回预期的city实体
        City cityResult = componentService.findCityById(10L);

        //Assert类属于junit包,设置断言,所有的预期都应该满足
        //断言 返回实体不为NULL
        Assert.assertNotNull(cityResult);
        //断言 返回实体ID为10
        Assert.assertEquals(cityResult.getId(), (Long) 10L);
        //断言 返回实体名称为正常城市
        Assert.assertEquals(cityResult.getCityName(), "正常城市");
        //全写是Mockito.verify,由于我们静态引入所以可以简写
        //断言 cityDao.findById()方法被调用了1次
        verify(cityDao, times(1)).findById(any());
    }

    /**
     * 这是另一个测试用例,用于验证当无法查到数据,返回默认城市的逻辑是否符合预期
     * 验证根据ID获得城市为null时
     * 返回默认城市
     *
     * @see org.spring.springboot.service.CityService#findCityById(Long)
     */
    @Test
    public void findCityById_DefaultTest() {
        //设置当执行CityService被测试方法,依赖的cityDao在输入100时
        //返回null
        //此时根据代码逻辑,会创建一个cityName=默认城市的类,并返回
        //这也就是常说的Mock
        when(cityDao.findById(100L)).thenReturn(null);

        //真实执行测试用例
        City cityResult = componentService.findCityById(100L);

        //设置断言,所有的预期都应该满足
        //断言 返回实体不为NULL
        Assert.assertNotNull(cityResult);
        //断言 返回实体ID为0
        Assert.assertEquals(cityResult.getId(), (Long) 0L);
        //断言 返回实体名称为默认城市
        Assert.assertEquals(cityResult.getCityName(), "默认城市");
        //断言 cityDao.findById()方法被调用了1次
        verify(cityDao, times(1)).findById(any());
    }

    /**
    * 这是另一个测试用例,用于验证当id小于0时,返回数据为省份的逻辑是否符合预期
    **/
    @Test
    public void findCityById_LessThan0Test() {
        //定义cityDao.findById(-10L) Mock返回的数据
        //这里我们只定义了id=-10,因为我们知道代码逻辑预期只对id等于负数进行判断,没有使用其他值,所以可以简化
        City city = new City();
        city.setId(-10L);
        
        //设置当执行CityService被测试方法,依赖的cityDao在输入100时
        //返回city(id=-1,provinceId=-1,cityName=省份,description=省份)
        //此时根据代码逻辑,会创建一个cityName=省份的类,并返回
        //这也就是常说的Mock
        when(cityDao.findById(-10L)).thenReturn(city);

        //真实执行测试用例
        City cityResult = componentService.findCityById(-10L);

        //设置断言,所有的预期都应该满足
        //断言 返回实体不为NULL
        Assert.assertNotNull(cityResult);
        //断言 返回实体ID为-1
        Assert.assertEquals((long) cityResult.getId(), -1L);
        //断言 返回实体名称为省份
        Assert.assertEquals(cityResult.getCityName(), "省份");
        //断言 cityDao.findById()方法被调用了1次
        verify(cityDao, times(1)).findById(any());
    }
}

Controller单元测试

以下为CityRestController类的单元测试,通过Mockito、junit、MockMvc不需要启动整个应用进行快速测试。可以看到这里多使用了MockMvc,这是用于模拟Http调用Controller层代码,会真实的请求到Controller中的代码,将结合代码逐行讲解。

public class CityRestControllerTest {

    /**
    * @InjectMocks属于mockito-core包
    * @InjectMocks注解用于标识被测试类,会将@Mock、@Spy注入到被测试类中
    * 如果标识的是一个类,则会创建该类的实例;如果被标识的是一个接口,需要手动new该接口的实现
    **/
    @InjectMocks
    private CityRestController cityRestController;

    /**
    * @Mock属于mockito-core包
    * @Mock注解用于标识测试类中被依赖的类,会创建一个该类或接口的代理类,不会调用真实的类代码
    * 我们可以应用这种特性,在测试方法时,让被依赖类根据不同参数返回预期的值
    **/
    @Mock
    private CityService cityService;

    /**
    * MockMvc属于spring-boot-starter-test包
    * MockMvc用于模拟Http请求,它会真实的调用Controller对应方法,并对响应设置断言
    **/
    private MockMvc mvc;
    
    /**
    * @Before属于junit包
    * @Before注解用于在@Test标识的测试用例运行前执行代码
    **/
    @Before
    public void setup() {
        //MockitoAnnotations类属于mockito-core包
        //openMocks(this)作用是根据@InjectMocks、@Mock注解生成测试代理类
        //结合@Before注解就是每次运行测试用例之前都会重置测试代理类
        MockitoAnnotations.openMocks(this);
        //构建cityRestController对象的模拟Http代理类
        mvc = MockMvcBuilders.standaloneSetup(cityRestController).build();
    }

    /**
     * 用于验证通过Controller接口,调用CityService正常的情况
     * 验证根据ID获得城市逻辑正常
     *
     * @see org.spring.springboot.controller.CityRestController#findOneCity(Long)
     */
    @Test
    public void findOneCity_normalTest() throws Exception {
        //定义接口的Mock返回的数据
        City city = new City();
        city.setId(1L);
        city.setProvinceId(1L);
        city.setCityName("泰安");
        city.setDescription("泰山");

        //设置当执行CityRestController被测试方法,依赖的cityService在1时
        //返回city(id=1,provinceId=1,cityName=泰安,description=泰山)
        //这也就是常说的Mock
        when(cityService.findCityById(1L)).thenReturn(city);

        //执行并设置断言
        //GET 请求/api/city/1
        //设置json请求
        MvcResult mvcResult = mvc.perform(get("/api/city/1")
                .contentType(MediaType.APPLICATION_JSON))
                //断言 Http响应码是200
                .andExpect(status().isOk())
                //断言 Http返回body是相同实体
                .andExpect(content().json("{\"id\":1,\"provinceId\":1,\"cityName\":\"泰安\",\"description\":\"泰山\"}"))
                .andReturn();
    }

}

代码覆盖率

我们看很多开源项目时,会看到一个小图标表示代码覆盖率70%,含义是单元测试走过代码行占总有效代行数的百分比,有效代码行是指去除换行等无意义代码。

IDEA代码覆盖率步骤

通过IDEA自带代码覆盖率检测,打开单元测试类,如图所示。
在这里插入图片描述
点击Run…with Coverage,如图所示。
在这里插入图片描述
在IDEA右侧会显示Coverage框,里面会显示整个项目的代码覆盖率,你可以找到你测试的类,查看每个类的测试覆盖率,以我们刚刚的单元测试为例。CityRestController单测代码覆盖率为22%,如图所示。
在这里插入图片描述
CityService单测代码覆盖率为80%,点击可以进一步查看那些地方没有走到,绿色代码单测走过,红色代表未走过,灰色代表无效代码,如图所示。
在这里插入图片描述
可以通过不断补充单元测试,提高代码覆盖率。

思考

我们使用发问的形式,在看作者回答前可以自己先思考

通过Mock编写单元测试的优点是什么?

  • 可以不依赖于环境重复运行单元测试
  • 每一次改动发布,都可以运行之前的所有单测,保障改动对之前功能兼容
  • 重构代码的重要保障,《重构改善既有代码的设计》中的核心其实就是面向测试编程,优化代码的实现,但保持结果一致的预期,可以避免优化引入大量BUG

自己造Mock数据又返回自己验证是否是有效测试,如果调用方法不返回这种数据,甚至抛出异常呢?
我们单测的目的是目标方法逻辑代码正确性,通过控制变量的形式,假设被Mock方法始终正常,这种情况下,被测试逻辑代码符合预期,则说明逻辑代码是正确的,而Mock方法的正确性则由另一个单测保障。

这个例子会调用数据库,那数据库操作方法的正确性该如何保障呢?
如果使用Mybatis-plus这种框架,由框架的单测保障。使用Mybatis映射文件,可以通过启动一个H2内存数据库,执行SQL验证正确性。如果是NoSQL,可以考虑通过启动本地容器的方式验证,不过一般情况下优先保障逻辑代码的正确性,数据库出现异常是比较少的情况。

那假设是RPC调用呢,又该如何单元测试?
对于消费者,可以对RPC接口Mock来单元测试。RPC接口内的逻辑正确,由服务提供者的单元测试保障。

示例项目代码

以上讲解知识点的示例项目源码也已上传,可找到项目单元测试直接运行。

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

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

相关文章

5G与智慧文旅的融合发展:推动旅游业转型升级与可持续发展

随着5G技术的飞速发展和广泛应用&#xff0c;其与智慧文旅的融合发展正成为推动旅游业转型升级与可持续发展的重要力量。5G技术以其高速率、低时延、大连接的特性&#xff0c;为智慧文旅注入了新的活力&#xff0c;助力旅游业实现更高效、更智能、更绿色的发展。本文将深入探讨…

Bert Encoder和Transformer Encoder有什么不同

前言&#xff1a;本篇文章主要从代码实现角度研究 Bert Encoder和Transformer Encoder 有什么不同&#xff1f;应该可以帮助你&#xff1a; 深入了解Bert Encoder 的结构实现深入了解Transformer Encoder的结构实现 本篇文章不涉及对注意力机制实现的代码研究。 注&#xff1a;…

快来查看!你的简历亮点在哪里?还有精美模板等你来下载!

一、个人简历写作指南 编写个人简历是展示自己专业技能、工作经历和教育背景的重要方式。以下是一些个人简历写作的指南&#xff0c;希望对你有所帮助&#xff1a; 1. 简明扼要 简洁清晰&#xff1a;简历内容应该简明扼要&#xff0c;突出重点信息&#xff0c;避免冗长。易读性…

J8 - Inception v1算法

目录 理论知识Inception卷积计算 模型结构模型实现inception 结构GoogLeNet模型打印模型结构 模型效果总结与心得体会 理论知识 GoogLeNet首次出现就在2014年的ILSVRC比赛中获得冠军&#xff0c;最初的版本为InceptionV1。共有22层深&#xff0c;参数量5M。 可以达到同时期VGG…

FreeROTS day2

总结DMA空闲中断接收数据的使用方法 首先要要选择串口然后配置串口的参数&#xff0c;配置MDA通道选择接受数据&#xff0c;配置空闲中断&#xff0c;定义一个数据接收的容器&#xff0c;启动MDA传输当串口收到数据时MDA将数据传输到容器中,MDA会一直检测是否有数据当有数据并…

Node 旧淘宝源 HTTPS 过期处理

今天拉取老项目更新依赖&#xff0c;出现 urlshttps%3A%2F%2Fregistry.npm.taobao.org%2Fegg-logger%2Fdownload%2Fegg-logger-2.6.1.tgz: certificate has expired 类似报错。即使删除 node_modules 重新安装&#xff0c;问题依然无法解决。 一、问题演示 二、原因分析 1、淘…

泰克P6139B TektronixP6139B无源探头

特征&#xff1a; 500 MHz 探头带宽 探头尖端的大输入阻抗 10 MOhm&#xff0c;8 pF 补偿范围&#xff1a;8 pF 至 18 pF 电缆长度&#xff1a;1.3M 10X 衰减系数 300 V CAT II 输入电压 用于探测小几何电路元件的紧凑型探头 用于增强被测设备可见性的小型探头主体 可更换的探…

leetcode 3.6

Leetcode hot 100 一.矩阵1.旋转图像 二.链表1. 相交链表2.反转链表3.回文链表4.环形链表5.环形链表 II 一.矩阵 1.旋转图像 旋转图像 观察规律可得&#xff1a; matrix[i][j] 最终会被交换到 matrix [j][n−i−1]位置&#xff0c;最初思路是直接上三角交换&#xff0c;但是会…

CTP-API开发系列之五:SimNow环境介绍

CTP-API开发系列之五&#xff1a;SimNow环境介绍 CTP-API开发系列之五&#xff1a;SimNow环境介绍SimNow模拟测试环境第一套第二套登录关键字段可视化终端常见问题 CTP-API开发系列之五&#xff1a;SimNow环境介绍 如果你要研发一套国内期货程序化交易系统&#xff0c;从模拟测…

AI嵌入式CanMV-K230项目(1)-简介

文章目录 前言一、嘉楠的产品体系二、开发板介绍三、应用领域总结 前言 前一些列文章我们介绍了K210的使用方法&#xff0c;近期嘉楠科技发布了最新一版的K230芯片&#xff0c;下面我们来了解下这款芯片&#xff0c;后续我们将介绍该款芯片开发板的使用方法。 一、嘉楠的产品体…

ant-desgin charts双轴图DualAxes,柱状图无法立即显示,并且只有在调整页面大小(放大或缩小)后才开始显示

摘要 双轴图表中&#xff0c;柱状图无法立即显示&#xff0c;并且只有在调整页面大小&#xff08;放大或缩小&#xff09;后才开始显示 官方示例代码 在直接复制&#xff0c;替换为个人数据时&#xff0c;出现柱状图无法显示问题 const config {data: [data, data],xFiel…

Cobalt Strike 4.9.1(已更新,文章图片没换)

Cobalt Strike 4.9.1 1. 工具介绍1.1. 工具添加1.2. 工具获取 2. 工具使用2.1. 添加权限并运行2.2. 连接服务端2.3. 连接成功 3. 安全性自查 1. 工具介绍 CS 是Cobalt Strike的简称&#xff0c;是一款渗透测试神器&#xff0c;常被业界人称为CS神器。Cobalt Strike已经不再使用…

类和对象周边知识

再谈构造函数 前几期我们把六个默认成员函数一一说明后&#xff0c;构造函数还有一些周边知识。 初始化列表 我们在没有了解初始化列表的时候一般都是使用构造函数初始化或者在声明哪里给予缺省值&#xff0c;那么为什么好药存在初始化列表呢&#xff1f;是因为①.有些值必须…

GAN 网络的损失函数介绍代码

文章目录 GAN的损失函数介绍1.L1 losses2.mse loss3.smooth L14.charbonnier_loss5.perceptual loss (content and style losses)6.Gan损失7.WeightedTVLoss8.完整代码方便使用,含训练epoch代码。 GAN的损失函数介绍 1.L1 losses pixel_opt: type: L1Loss loss_weight: 1.0 r…

Linux Ubuntu部署SVN服务端结合内网穿透实现客户端公网访问

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

2024.3.7

大端存储&#xff1a;高存低&#xff0c;低存高&#xff1b; 小端存储&#xff1a;高存高&#xff0c;低存低&#xff1b; sizeof 用于获取数据类型或变量的大小&#xff0c;strlen 用于获取字符串的长度。 不能改变常量字符串&#xff0c; char *arr"hello"; *ar…

外汇天眼:伦敦金属交易所宣布新的高级领导任命

伦敦金属交易所&#xff08;LME&#xff09;今日宣布了多项高级领导职务任命和组织设计变更。 LME的任命将于2024年4月1日生效。 苏珊斯莫尔被任命为总法律顾问&#xff0c;负责监督LME及LME Clear的法律职能。斯莫尔女士将于6月加入&#xff0c;并将向LME及LME Clear的首席执…

JEDEC标准介绍及JESD22全套下载

JEDEC标准 作为半导体相关的行业的从业者&#xff0c;或多或少会接触到JEDEC标准。标准对硬件系统的设计、应用、验证&#xff0c;调试等有着至关重要的作用。 JEDEC&#xff08;全称为 Joint Electron Device Engineering Council&#xff09;是一个电子组件工程标准制定组织…

2024【问题解决】Github 2024无法克隆git clone自从签了2F2安全协议之后

项目场景:ping通Github但没法clone–502 问题描述 提示:ping通Github但没法clone--502: 例如:git clone https://gitclone.com/l.git/*** $ git clone https://github.com/darrenpig/Yocto Cloning into Yocto_tutorial... fatal: unable to access https://gitclone.co…

计划任务和日志

一、计划任务 计划任务概念解析 在Linux操作系统中&#xff0c;除了用户即时执行的命令操作以外&#xff0c;还可以配置在指定的时间、指定的日期执行预先计划好的系统管理任务&#xff08;如定期备份、定期采集监测数据&#xff09;。RHEL6系统中默认已安装了at、crontab软件…