单元测试实战(五)普通类的测试

为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。

本文中的测试均基于JUnit5。

单元测试实战(一)Controller 的测试

单元测试实战(二)Service 的测试    

单元测试实战(三)JPA 的测试    

单元测试实战(四)MyBatis-Plus 的测试

​​​​​​​单元测试实战(五)普通类的测试

单元测试实战(六)其它

概述

普通类或曰POJO的测试,是最简单的一种情况,大多数情况下只使用JUnit即可。万一有不易实例化的外部依赖,也可以用Mockito的@Mock来模拟。这类测试一般应脱离Spring上下文来进行。

需要的话,在每个测试之前应清理/重置测试数据,一般为方法参数或待测类实例;断言应主要检查待测类的行为是否符合预期。

依赖

大多数普通类测试只依赖JUnit,但作为一般实践,我们通常也带上Spring Boot自己的测试工具集。

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

示例

以下是一个BigDecimal的包装类,实现了一些BigDecimal的最佳实践;该类是我们的待测试类:

package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Objects;/*** BigDecimal的包装类,封装了以下实践:* <li>不允许null值(使用工厂方法创建实例时会直接报错)。</li>* <li>避免double构造传参造成精度丢失。</li>* <li>封装equals,强制使用compareTo来比较。</li>* <li>封装格式化输出(补零对齐,默认两位)。</li>* <li>封装加减乘除,尤其除法,强制使用BigDecimal的三参方法避免无限小数报错。</li>* <li>直观的判断正数、负数、零的方法。</li>* <br/>** 本类设计为不可变对象,非读方法(加减乘除、取反、四舍五入等)都会产生新对象。** @author ioriogami**/
public class DecimalWrapper implements Comparable<DecimalWrapper> {/*** 默认精度,保留两位。*/public static final int DEFAULT_SCALE = 2;private final BigDecimal decimal;private DecimalWrapper(BigDecimal decimal) {Objects.requireNonNull(decimal);this.decimal = decimal;}// 以下工厂方法/*** 通过一个BigDecimal对象构造DecimalWrapper实例。* @param decimal 本wrapper代表的BigDecimal对象*/public static DecimalWrapper of(BigDecimal decimal) {return new DecimalWrapper(decimal);}/*** 通过一个String实例构造DecimalWrapper实例。* @param s 字符串数值*/public static DecimalWrapper of(String s) {return of(new BigDecimal(s));}/*** 通过一个double实例构造DecimalWrapper实例。* @param d double数值*/public static DecimalWrapper of(double d) {return of(new BigDecimal(String.valueOf(d)));}/*** 通过一个float实例构造。* @param f float数值*/public static DecimalWrapper of(float f) {return of(new BigDecimal(String.valueOf(f)));}// 以下一般接口/*** 获取底层的BigDecimal对象*/public BigDecimal toBigDecimal() {return decimal;}/*** 获取double值。*/public Double toDouble() {return decimal.doubleValue();}@Overridepublic String toString() {return decimal.toPlainString();}@Overridepublic int hashCode() {return decimal.hashCode();}@Overridepublic boolean equals(Object obj) {if (obj instanceof DecimalWrapper) {DecimalWrapper other = (DecimalWrapper) obj;return decimal.compareTo(other.decimal) == 0;}return false;}@Overridepublic int compareTo(DecimalWrapper that) {if (that == null) return 1;return this.decimal.compareTo(that.decimal);}// 以下格式化输出/*** 四舍五入保留两位。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round() {return round(DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 四舍五入保留i位。* @param scale 精度,即i,保留几位。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale) {return round(scale, RoundingMode.HALF_UP);}/*** m舍n入保留i位。** @param scale 精度,即i,保留几位。* @param mode 舍入策略(m和n)。* @return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale, RoundingMode mode) {return of(decimal.setScale(scale, mode));}/*** 获得四舍五入保留两位(强制补0)后的字符串。*/public String format() {return new DecimalFormat("0.00").format(decimal.setScale(DEFAULT_SCALE, RoundingMode.HALF_UP));}/*** 获得四舍五入保留i位(强制补0)后的字符串。* @param scale 精度,即i,保留几位。*/public String format(int scale) {return format(scale, RoundingMode.HALF_UP);}/*** 获得m舍n入保留scale位(强制补0)后的字符串。不会影响本身的值。* @param scale 精度,即i,保留几位。* @param mode 舍入策略(m和n),若为null则默认四舍五入。* @return 格式化后的字符串。*/public String format(int scale, RoundingMode mode) {if (scale <= 0) throw new IllegalArgumentException("精度必须大于0");if (mode == null) mode = RoundingMode.HALF_UP;StringBuilder buff = new StringBuilder("0.");for (int i = 0; i < scale; i++) {buff.append("0");}return new DecimalFormat(buff.toString()).format(decimal.setScale(scale, mode));}// 以下加减乘除、取反/*** 加法。*/public DecimalWrapper add(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行加法运算。");return of(decimal.add(other.decimal));}/*** 减法。*/public DecimalWrapper subtract(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行减法运算。");return of(decimal.subtract(other.decimal));}/*** 乘法。*/public DecimalWrapper multiply(DecimalWrapper other) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行乘法运算。");return of(decimal.multiply(other.decimal));}/*** 除法。*/public DecimalWrapper divide(DecimalWrapper other) { // 使用三参除法,避免结果为无限小数时报错。return divide(other, DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 除法,指定精度和舍入策略。*/public DecimalWrapper divide(DecimalWrapper other, int scale, RoundingMode mode) {if (other == null) throw new IllegalArgumentException("操作数为null,无法进行除法运算。");if (scale <= 0) throw new IllegalArgumentException("精度必须大于0");if (mode == null) mode = RoundingMode.HALF_UP;return of(decimal.divide(other.decimal, scale, mode));}/*** 取反。*/public DecimalWrapper negate() {return of(decimal.negate());}/*** 判断是否零值,不管到底是0.0、0.00还是0.0000..*/public boolean isZero() {return decimal.signum() == 0;}/*** 判断是否正数。*/public boolean isPositive() {return decimal.signum() == 1;}/*** 判断是否正数。*/public boolean isNegative() {return decimal.signum() == -1;}
}

以下是对DecimalWrapper进行测试的测试类:

package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class DecimalWrapperTest {@Testvoid testConstructBigDecimal() {DecimalWrapper d = DecimalWrapper.of(new BigDecimal("1.11"));assertEquals("1.11", d.format());}@Testvoid testConstructDouble() {DecimalWrapper d = DecimalWrapper.of(1.11);assertEquals("1.11", d.format());}@Testvoid testConstructString() {DecimalWrapper d = DecimalWrapper.of("1.11");assertEquals("1.11", d.format());}@Testvoid testConstructFloat() {DecimalWrapper d = DecimalWrapper.of(1.1f);assertEquals("1.10", d.format());}@Testvoid testConstructNullParam() {try {DecimalWrapper.of((String) null);fail("should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((Double) null);fail("Should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((Float) null);fail("Should not get here!");} catch (NullPointerException npe) {}try {DecimalWrapper.of((BigDecimal) null);fail("Should not get here!");} catch (NullPointerException npe) {}}@Testvoid testComparison() {DecimalWrapper d1 = DecimalWrapper.of(1.1);DecimalWrapper d2 = DecimalWrapper.of(1.2);DecimalWrapper d3 = DecimalWrapper.of(1.0);assertTrue(d1.compareTo(d2) < 0);assertTrue(d2.compareTo(d1) > 0);assertTrue(d3.compareTo(d1) < 0);assertTrue(d3.compareTo(d2) < 0);DecimalWrapper d4 = DecimalWrapper.of("1.00");assertTrue(d3.compareTo(d4) == 0);DecimalWrapper d5 = null;assertTrue(d3.compareTo(d5) > 0);}@Testvoid testToDecimal() {DecimalWrapper d1 = DecimalWrapper.of(1.11);assertEquals(d1.toBigDecimal(), new BigDecimal("1.11"));}@Testvoid testToDouble() {DecimalWrapper d1 = DecimalWrapper.of(1.11);assertEquals(d1.toDouble(), 1.11);}@Testvoid testEquals() {DecimalWrapper d1 = DecimalWrapper.of(1.12345);DecimalWrapper d2 = DecimalWrapper.of(1.12345f);DecimalWrapper d3 = DecimalWrapper.of("1.12345");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12345"));assertEquals(d1, d2);assertEquals(d2, d3);assertEquals(d3, d4);}@Testvoid testRoundDefault() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385f);DecimalWrapper d3 = DecimalWrapper.of("1.12385");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12385"));assertEquals(d1.round(), DecimalWrapper.of("1.12"));assertEquals(d2.round(), DecimalWrapper.of("1.12"));assertEquals(d3.round(), DecimalWrapper.of("1.12"));assertEquals(d4.round(), DecimalWrapper.of("1.12"));}@Testvoid testRound() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385f);DecimalWrapper d3 = DecimalWrapper.of("1.12385");DecimalWrapper d4 = DecimalWrapper.of(new BigDecimal("1.12385"));assertEquals(d1.round(3), DecimalWrapper.of("1.124"));assertEquals(d2.round(3), DecimalWrapper.of("1.124"));assertEquals(d3.round(3), DecimalWrapper.of("1.124"));assertEquals(d4.round(3), DecimalWrapper.of("1.124"));assertEquals(d4.round(3, RoundingMode.DOWN), DecimalWrapper.of("1.123"));}@Testvoid testFormat() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);assertEquals(d1.format(), "1.12");assertEquals(d1.format(3), "1.124");assertEquals(d1.format(3, RoundingMode.DOWN), "1.123");}@Testvoid testAdd() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("2.24770"), d1.add(d2));DecimalWrapper d3 = DecimalWrapper.of(0);assertEquals(DecimalWrapper.of("1.12385"), d3.add(d2));assertTrue(d3.add(d3).isZero());}@Testvoid testSubtract() {DecimalWrapper d1 = DecimalWrapper.of(2.24770);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("1.12385"), d1.subtract(d2));DecimalWrapper d3 = DecimalWrapper.of(0); // change to: 0.0assertEquals(DecimalWrapper.of("1.12385"), d2.subtract(d3));assertTrue(d2.subtract(d2).isZero());assertTrue(d3.subtract(d3).isZero());}@Testvoid testMultiply() {DecimalWrapper d1 = DecimalWrapper.of(1.12385);DecimalWrapper d2 = DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of("1.2630388225"), d1.multiply(d2));DecimalWrapper d3 = DecimalWrapper.of(0.00);assertTrue(d2.multiply(d3).isZero());DecimalWrapper d4 = DecimalWrapper.of(-1);assertEquals(DecimalWrapper.of("-1.12385"), d1.multiply(d4));}@Testvoid testDivide() {DecimalWrapper d1 = DecimalWrapper.of(10.0);DecimalWrapper d2 = DecimalWrapper.of(3);assertEquals(DecimalWrapper.of(3.33), d1.divide(d2));DecimalWrapper d3 = DecimalWrapper.of(0);assertTrue(d3.multiply(d1).isZero());try {d1.divide(d3);fail("divide by zero, should not get here!");} catch (Exception e){}}@Testvoid testNegate() {DecimalWrapper d1 = DecimalWrapper.of(-1.12385);assertTrue(d1.isNegative());assertFalse(d1.isZero());assertFalse(d1.isPositive());DecimalWrapper d2 = d1.negate();assertTrue(d2.isPositive());assertFalse(d2.isZero());assertFalse(d2.isNegative());assertTrue(d1.add(d2).isZero());}}

测试类说明:

测试类的代码和被测类的代码都比较直观,不需做过多说明。

在该类中,我们为被测类的每个公共方法都创建了测试方法,且只用到了JUnit的@Test和Assertions;如果需要事前准备和事后清理工作,还可以加上@BeforeEach、@AfterEach、@BeforeAll、@AfterAll等方法。

第37行的testConstructNullParam是负面测试,测试当传入null值是不允许的,会抛出NullPointerException。

第101行的testRoundDefault、第113行的testRound,是对同一个方法的不同情况(精度)的测试。

私有方法的测试

私有方法不需要测试。对私有方法的测试,应通过调用它的公有方法的测试来进行。直接测试私有方法往往是一种代码“坏味道”。

虽然但是,有时候确实想给私有方法写一个测试,也不是做不到:

package com.aaa.api.auth.filter;import com.aaa.sdk.utils.Utils;
import org.junit.jupiter.api.Test;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static org.junit.jupiter.api.Assertions.*;
class AuthNFilterTest {@Testvoid testExtractUsernameFromToken() throws InvocationTargetException, IllegalAccessException {String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...";AuthNFilter filter = new AuthNFilter();Method m = Utils.doGetMethod(AuthNFilter.class, "extractUsernameFromToken", String.class);//long start = System.currentTimeMillis();String user = (String) m.invoke(filter, token);//System.out.println("User is " + user + ", used " + (System.currentTimeMillis() - start) + " ms");assertEquals("ioriogami", user);}
}

第15行,我们new了一个待测试对象。

第16行,我们用反射获取了AuthNFilter类的extractUsernameFromToken私有方法(该方法接受一个String类型的参数),然后在第18行对其进行调用。

 如需对构造方法、私有方法、静态方法、final类和方法进行mock,可以使用powermock提供的增强版Mockito:PowerMockito。

总结

普通类的测试以JUnit简单测试为主,一般不需要Spring上下文。

每一个public方法都有至少一个测试;对不同的情况/分支,建议有多个测试;最好也有负面测试。当然,一个完美的测试类应该测试每个方法的正面行为、负面行为、边缘情况;并应尽可能覆盖所有分支。但在有限的时间内,我们应识别最重要的方面进行测试。

单元测试针对单个类,原则上测试类与被测类一对一。当然也可以针对一组紧密相关的类/接口编写单元测试,比如pagewindow是一个实现无界分页的小模块,由几个接口和类组成,它的单元测试就是针对这一组类的。

Mock的原理是采用字节码生成的方式对要mock的类进行sub-class,然后生成这个子类的对象,以此达到对其行为进行订制的目的。显然,这种方式会受到Java继承机制的限制:静态类/方法、final类/方法、构造器、私有方法等都不能继承,因此就难以订制。

Mockito在2.1.0中加入了对final类/方法的支持。见 https://www.cnblogs.com/yuluoxingkong/p/14813558.html。

powermock提供的增强版Mockito:PowerMockito 则对这些方面提供了全面的支持。

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

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

相关文章

Pod详解

Pod详解 1 .Pod介绍 1.1 Pod结构 每个Pod中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类&#xff1a; 用户程序所在的容器&#xff0c;数量可多可少 Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个&#xff1a; 可以以它为…

小米集团收入增长失速已久:穿越寒冬,雷军的路走对了吗?

撰稿|行星 来源|贝多财经 11月20日&#xff0c;小米集团&#xff08;HK:01810&#xff0c;下称“小米”&#xff09;发布了截至2023年9月30日的第三季度业绩公告。 财报显示&#xff0c;在智能手机出货量下行、平均售价下跌的背景下&#xff0c;小米逆势而上&#xff0c;实现…

创建用户报错:ORA-65096: 公用用户名或角色名无效

题主的Oracle版本是最新的Oracle 21 描述&#xff1a; 1、在命令行工具 给Oracle创建用户&#xff0c;create user c##用户名identifed by 密码&#xff0c;报错&#xff1a;【ORA-65096: 公用用户名或角色名无效】 2、在navicat创建用户&#xff0c;提示如下&#xff1a; 解…

Windows系统如何安装与使用TortoiseSVN客户端,并实现在公网访问本地SVN服务器

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统&#xff0c;它与Apache Subversion&#xff08;SVN&#xff09;集成在一起&#xff0c;提供了一个用户友好的界面&#xff0c;方便用…

并行与分布式计算 第8章 并行计算模型

文章目录 并行与分布式计算 第8章 并行计算模型8.1 并行算法基础8.1.1 并行算法的定义8.1.2并行算法的分类8.1.3算法的复杂度 8.2 并行计算模型8.2.1 PRAM (SIMD-SM)模型8.2.3 BSP (MIMD-DM)模型8.2.4LogP&#xff08;MIMD-DM&#xff09;模型 并行与分布式计算 第8章 并行计算…

java疫情期间社区出入管理系统-计算机毕业设计源码21295

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对疫情期间社区出入管理等问题&#xff0c;对…

【算法挨揍日记】day21——64. 最小路径和、174. 地下城游戏

64. 最小路径和 64. 最小路径和 题目描述&#xff1a; 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 解题思路&#xff1a; 状态表示&…

硬技能之上的软技巧(二)

在硬技能的基础上&#xff0c;如何运用其他软技巧来提升个人能力和职业发展。在之前的讨论中&#xff0c;我们提到了硬技能和软技巧的基本概念&#xff0c;以及如何运用一些软技巧来提升个人能力和职业发展。 本篇文章将进一步探讨其他软技巧&#xff0c;包括批判性思维、自我…

geemap学习笔记011:可视化遥感影像随时间的变化

前言 本节主要是介绍 .ts_inspector 工具&#xff0c;它是可以可视化遥感影像随时间的变化&#xff0c;与先前文章中介绍的.split_map差别在于&#xff0c;它可以加载时间序列数据。 1 导入库 !pip install geemap #安装geemap库 import ee import geemapgeemap.show_youtub…

蔚来「换电」赚钱养家,长安首家进场站台

作者 | 张祥威 编辑 | 德新 蔚来的「换电」业务开始赚钱养家。 11月21日下午&#xff0c;蔚来宣布与长安汽车签署了换电业务的合作协议&#xff0c;双方将在换电网络建设与共享、换电车型研发等方面展开深入合作&#xff0c;并在推动建立换电电池标准、建立高效的电池资产管…

909-2014-T3

文章目录 1.原题2.算法思想3.关键代码4.完整代码5.运行结果 1.原题 有n个顶点的无向图&#xff0c;使用邻接矩阵作为存储结构。为减少存储空间&#xff0c;使用数组按照行主映射方式仅保存下三角矩阵。请给出映射公式&#xff0c;并编写算法计算给定顶点的度。叙述算法思想并用…

软件测试面试题总结--基础面经

1 、软件的含义 程序、数据及相关文档的完整集合。 2、测试与调试的区别是什么&#xff1f; 测试是由测试人员来进行&#xff0c;主要目标是发现、报告和跟踪缺陷。 调试是由开发人员进行&#xff0c;主要目标是定位缺陷位置&#xff0c;分析缺陷原因&#xff0c;修复缺陷。…

vue年季度月联动筛选(el-cascader实现)

默认显示当年当季当月 <label class"font-weight">时间范围</label> <el-cascaderplaceholder"请选择":options"timeOption"filterableclearablechange-on-selectv-model"timeRange":props"{emitPath: true}&quo…

python 对图像进行聚类分析

import cv2 import numpy as np from sklearn.cluster import KMeans import time# 中文路径读取 def cv_imread(filePath, cv2_falgcv2.COLOR_BGR2RGB): cv_img cv2.imdecode(np.fromfile(filePath, dtypenp.uint8), cv2_falg) return cv_img# 自定义装饰器计算时间 def…

服务器 jupyter 文件名乱码问题

对于本台电脑&#xff0c;autodl服务器&#xff0c;上传中文文件时&#xff0c;从压缩包名到压缩包里的文件名先后会出现中文乱码的问题。 Xftp 首先是通过Xftp传输压缩包到Autodl服务器&#xff1a; 1、打开Xftp&#xff0c;进入软件主界面&#xff0c;点击右上角【文件】菜…

QTableView/QTableWidget设置单元格字体颜色及背景色

1.QTableView设置单元格字体颜色及背景色 QStandardItem * pItem new QStandardItem("AAA"); pItem->setBackground(QBrush(Qt::blue)); // 设置背景色 pItem->setForeground(QBrush(Qt::red)); // 设置字体颜色 2.QTableWidget设置单元格字…

Go语言中string与byte转换

简介 string与byte的转换是最常见的一种&#xff0c;通常我们会使用强转方式&#xff0c;但其实还有另一种更加高效的方式&#xff0c;本文会演示两种转换方式。 普通转换 func main() {fmt.Println([]byte("abcd"))fmt.Println(string([]byte{1, 2, 3})) }输出 […

Hadoop学习笔记:运行wordcount对文件字符串进行统计案例

文/朱季谦 我最近使用四台Centos虚拟机搭建了一套分布式hadoop环境&#xff0c;简单模拟了线上上的hadoop真实分布式集群&#xff0c;主要用于业余学习大数据相关体系。 其中&#xff0c;一台服务器作为NameNode&#xff0c;一台作为Secondary NameNode&#xff0c;剩下两台当…

(一)RISC-V 指令集及寄存器介绍

1. RISC-V指令集介绍 RISC-V 念作 “risk-five”&#xff0c;代表着 Berkeley 所研发的第五代精简指令集。 该项目 2010 年始于加州大学伯克利&#xff08;Berkeley&#xff09;分校&#xff0c;希望选择一款 ISA用于科研和教学。经过前期多年的研究和选型&#xff0c;最终决定…

【C++】string类的介绍与使用

&#x1f9d1;‍&#x1f393;个人主页&#xff1a;简 料 &#x1f3c6;所属专栏&#xff1a;C &#x1f3c6;个人社区&#xff1a;越努力越幸运社区 &#x1f3c6;简 介&#xff1a;简料简料&#xff0c;简单有料~在校大学生一枚&#xff0c;专注C/C/GO的干货分…