单元测试总结,一文全通

🍅 视频学习:文末有免费的配套视频可观看

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快

一、何为单测

测试有黑盒测试和白盒测试之分,黑盒测试顾名思义就是我们不了解盒子的内部结构,我们通过文档或者对该功能的理解,指定了相应的输入参数,然后判断得出的结果是否正确。普通的用户、开发、QA都可以进行黑盒测试。

白盒测试与之相反,需要了解到内部的实现细节,一般是由开发人员自己来进行的,是基于对代码逻辑结构、各个关联方法了解基础上进行的。

白盒测试主要有 2 种

  • 静态代码分析:Findbugs、Sonarqube
  • 动态测试:单元测试

单元测试属于白盒测试里面的动态测试

二、单测的意义

2.1 解决问题的成本

测试金字塔,是单测中一张经典的图片。测试级别简单可以简单分为下面三类,详细的话可以归结为:单元测试、接口测试、集成测试、系统测试、验收测试。

如果发现问题,在金字塔越底层的阶段,解决问题的速度是越快的。

  • 本地开发环境发现问题:看几眼代码或者 debug 下就能定位出来了。
  • 生产环境发现问题:找日志(可能还没有输出),查数据库(可能没权限)。本地能复现的话还好,复现不了的话干着急。


2.2 维护系统的稳定性

我代码已经写好了挺久了,线上也运行一段时间了,还有必要补充单测吗?感觉单测写了一堆并没有发现问题,不知道价值点在哪。

  1. 校验你当前方法的正确性。
  2. 长时间保证你这个方法的稳定性,在往后需求的变更开发中,可能其他功能点影响到了这个方法,此时你的单测能很快帮你检查出来。
  3. 单测能够在你项目需要重构的时候,勇敢大步的往前走。与其反反复复修改问题,系统摇摇欲坠的,不如多花点时间优化代码,写写单测。

2.3 单测与持续集成的融合

单测 + CICD = 自动化测试

每次打包的时候自动跑单测用例,有问题快速反馈。没问题的代码才可以触发部署到对应的环境中。避免测试不足的代码提交到相关环境,导致服务用不了,测试人员一顿恼火。

三、单测拦路虎

1.框架繁多

新手对单测的框架没有意识,自己可能引用了一种, 其它框架 sdk 里面包含了另一种,比如 spring 的 framework 可能本身版本也五花八门,junit4 junit5 都有,使用的时候没注意乱用。

用了 junit5 写的用例,然后用 junit4 的 @Ignore 语法要去忽略这个单测,显然不行,因为在 junit5 对应的语法是 @Disabled

@Injectable @MockMethod @Mock @Test … 迷茫

2.缺少理论的实践

3.对单测的理念认同不够,赶鸭子上架,内心其实是抵触的。

4.用例泛泛而写,没有遵守用例核心三步骤:mock 数据 -> 方法触发 -> 结果校验

5.为了覆盖率而写的单测

6.为了证明你方法是对的而写的单测(单测后面补的,单测里面方法触发了就算写完了,不是抱着验证的态度对所有结果进行充分细致的校验)

7.代码逻辑太复杂单测太难写

可以在写单测的过程,推动部分方法的重构。如果是新代码可以用 TDD(测试驱动开发) 理念,先写单测再写业务代码,这样实现起来的业务代码比较能做到高内聚,低耦合。

8.业务压力太大,单测太耗时

单测代码编写时间:业务代码编写时间 = 2~3:1,所以如果公司决定了写单测就同时也要给与这部分的时间。不能即催着业务上线,又催着单测达标,特别是前期在对单测还不够熟悉的基础上。

9.单测维护成本

单测也是需要维护的,case 多了后会发现,一有业务调整,不单单业务代码要调,单测也要调整,否则 case 会失败。

四、单测框架说明

1.Jmock

  • 网站:jMock - An Expressive Mock Object Library for Java
  • 不能对静态方法Mock

2.Mockito

  • 官方主页 http://code.google.com/p/mockito/
  • 不能对静态方法Mock

3.JMockit

  • http://jmockit.github.io/
  • 功能强大,可以 mock 静态方法

4.EasyMock

  • 官方主页 http://www.easymock.org/

5.testNg

  • TestNG可以进行单元测试,功能测试,端到端测试,集成测试等。
  • 需要一个额外的xml配置文件,配置测试的class、method甚至package

6.Spock

  • https://tech.meituan.com/tags/spock.html
  • BDD 的单测写法,Groovy 语法,快速,但是跟我们 java 的编码习惯不大一样。无法 mock 静态方法。

7.TestableMock

  • 文档:https://alibaba.github.io/testable-mock/#/zh-cn/doc/setup
  • TestableMock 和 JMockit 底层一致,使用的是 “运行时字节码修改” 技术,在单元测试启动时就扫描测试类和被测类的字节码,完成 Mock 方法的替换。

8.junit4

  • Java领域内最为流行的单元测试框架

9.junit5

  • JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
  • JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入。
  • JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行。
  • JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎。

10.Springtest

https://www.cnblogs.com/itplay/p/10101260.html

兼容多种测试引擎,便捷傻瓜,但是有下面几个问题

  • 可能存在多次启动 spring 容器
  • 一次 case 可能加载很多不需要的 bean ,导致速度很慢

11.总结
框架非常多,选择框架的时候可以从下面几个点考虑:
12.语法好写吗,文档全吗
13.能 mock 静态类,静态方法吗

  • 推荐 = junit5 + ( jmockit | testableMock),有冒险精神的可以尝试 Spock,速度快但是静态方法的 mock 需要借助其它工具。

Fastjunit = junit5 + jmockit + 测试工具集

单测框架总类繁多,本人很多都没有了解到位,以上总结仅为一家之言,兼听则明。

五、最佳实践

5.1. 理论知识要记牢

- 用例要轻量,执行速度要够快
- 执行过后没有痕迹
- 不依赖特点环境,随处都可以执行
- 校验要全面

5.2. 测试代码模板

单测的代码跟业务代码一样,需要易于阅读,方便维护。

再复杂的用例都要清晰得看出下面 3 个步骤
1. 上下文设置:参数模拟,mock 无用服务
2. 触发测试用例执行
3. 结果断言
/**
 * Given 给定上下文【初始化数据,Mock 外部调用】
 */
new Expectations(EsClient.class) {
    {
        EsClient.createDoc(withInstanceOf(SimpleDocVo.class), withInstanceOf(PipelineJobJunit.class));
        result = "{}";
        times = 1;
    }
};
/**
 * 执行测试代码
 */
RestResponse restResponse = callBackController.junitCallBak(jenkinsJunitVo);

/**
 * Assert 要足够细致
 */
Assertions.assertThat(restResponse).hasFieldOrPropertyWithValue("code", 0);

5.3 TDD 测试驱动开发

好的代码编写测试用例的时候是比较顺畅的,如果写单测的时候觉得目标代码很难测试,这时候大概率是目标代码编写不合理,需要优化重构下。另一方面,如果在写业务代码的时候先写好单测框架,此时能反向推动你写成比较好的代码。

5.3.1 松散代码

业务逻辑平铺在一个方法里面,此时你的单测不好关注主流程,也很难 mock 其它无用的东西(因为比较多)。此时为了让我们的单测好写,可以反向推动业务代码朝着高内聚低耦合的方向重构。

下面红框中的逻辑可以抽出来,主流程就清晰很多,用例也好写很多。

5.3.2 不稳定的代码

此方法里读取当前系统时间并根据该值返回结果。Datetime.now 是一个隐藏的动态变量,整个方法的输出结果依赖于 datetime 的时间。

public static string GetTimeOfDay()
{
  DateTime time = DateTime.Now;
  if (time.Hour >= 0 && time.Hour < 6>= 6 && time.Hour < 12>= 12 && time.Hour < 18 xss=removed>= 0 && dateTime.Hour < 6>= 6 && dateTime.Hour < 12>= 12 && dateTime.Hour < 18 xss=removed xss=removed xss=removed> StringUtil.isNotEmpty(simpleDocVo.getId()));

      Assertions.assertThat(document)
              .hasFieldOrPropertyWithValue("pipelineJobId", jenkinsJunitVo.getUapJobId())
              .hasFieldOrPropertyWithValue("status", jenkinsJunitVo.getStatus())
              .hasFieldOrPropertyWithValue("allCoverage", jenkinsJunitVo.getAllCoverage())
              .hasFieldOrPropertyWithValue("newCoverage", jenkinsJunitVo.getNewCoverage())
              .hasFieldOrPropertyWithValue("testRun", jenkinsJunitVo.getTestRun())
              .hasFieldOrPropertyWithValue("testFailure", jenkinsJunitVo.getTestFailure())
              .hasFieldOrPropertyWithValue("testSkipped", jenkinsJunitVo.getTestSkipped());
  }
};

5.4造数据

你还在一个个属性的添加吗?

@Test
public void webhookTestWebhook() {
  OtptestWebhookQueryDTO dto = new OtptestWebhookQueryDTO();
  dto.setApp("uap");
  dto.setEnv("test");
  dto.setJobId("xxx");
  dto.setVersion("v2.2");
  xxx
}

http://fastjunit.kubeclub.cn/test-basic/dataProvider/

Fastjunit 的数据生成器,任意给个 Bean 对象,自动的根据字段属性帮你随机产生相关数据。也支持数组对象的随机生成。可以节约不少时间。

5.5参数化测试

多种分支场景,使用参数化的测试可以让你的用例更简单。

@ParameterizedTest(name = "{0} + {1} = {2}")
@CsvSource({
              "0,    1,   1",
              "1,    2,   3",
              "49,  51, 100",
              "1,  100, 101"
})
void add(int first, int second, int expectedResult) {
      Calculator calculator = new Calculator();
      assertEquals(expectedResult, calculator.add(first, second),
                      () -> first + " + " + second + " should equal " + expectedResult);
}

5.6 数据库测试 - H2

H2 是一个内存数据,H2 仅仅只支持简单标准的 SQL 语法,如果各厂商特有的数据库引擎的特殊函数,可以使用 H2Function 扩展。

Fastjunit 同样对 H2 进行了一些封装:http://fastjunit.kubeclub.cn/db/h2/

5.7 并行测试

CICD 融入单测的过程,可能导致构建速度变慢,此时如果你的测试是并行的话,能在一定程度提高执行的速度。

  • Junit5-parallel:
  • Surefire-parallel :http://maven.apache.org/surefire/maven-surefire-plugin/examples/fork-options-and-parallel-execution.html

5.8 IDEA 快捷键

多了解些快捷键,在单测的过程中执行一些批量操作还是挺有效率的。如 bean 十几个、几十个属性,要批量赋值,批量校验的一些场景。

5.9 单测的范围

5.10 单测报告 - Jacoco

http://fastjunit.kubeclub.cn/test-basic/jacoco-report/

六、Jmockit 简单说明

6.1 示例

class ExampleTest {
   @Tested ServiceAbc tested;
   @Injectable DependencyXyz mockXyz;

   @Test
   void doOperationAbc(@Mocked AnotherDependency anyInstance) {
      new Expectations() {{
         anyInstance.doSomething(anyString); result = 123;
         AnotherDependency.someStaticMethod(); result = new IOException();
      }};

      tested.doOperationAbc("some data");

      new Verifications() {{ mockXyz.complexOperation(true, anyInt, null); times = 1; }};
   }
}
  1. 实例化和属性注入:@Tested 自动实例化 ServiceAbc 对象,并把 @Injectable DependencyXyz 属性自动注入到 tested 里面。
  2. 模拟期望:Expectations 内部的匿名方法会实现对象的模拟和期望。
 // anyInstance 对象的 doSomething 方法被调用的时候将返回 123
 // 收到的参数需要是任意的字符类型 anyString ,万一收到一个 int,就不会返回 123 了
 anyInstance.doSomething(anyString); result = 123;

6.2 @Capturing

@Mocked 一般是 mock 具体的对象,像一些接口或者基类,我们只知道具体的实现类,这种场景可以用 @Capturing。(例如:像一些权限校验,AOP 代理自动生成的场景)

 //权限类,校验用户没有权限访问某资源
 public interface IPrivilege {
    /**
     * 判断用户有没有权限
     * @param userId
     * @return 有权限,就返回true,否则返回false
     */
    public boolean isAllow(long userId);
}
@Test
public void testCaputring(@Capturing IPrivilege privilegeManager) {
// 加上了JMockit的API @Capturing,
// JMockit会帮我们实例化这个对象,它除了具有@Mocked的特点,还能影响它的子类/实现类
new Expectations() {
    {
        // 对IPrivilege的所有实现类录制,假设测试用户有权限
        privilegeManager.isAllow(testUserId);
        result = true;
    }
};
// 不管权限校验的实现类是哪个,这个测试用户都有权限
Assert.assertTrue(privilegeManager1.isAllow(testUserId));
Assert.assertTrue(privilegeManager2.isAllow(testUserId));
}

6.3 参数的灵活匹配

在录制和验证阶段,一个对模拟方法或构造方法的调用参数做灵活的匹配。

1.any

最不严格的参数匹配,当然每个方法的参数都有类型的,还是要给定个恰当的参数类型。

new Expectations() {{
   abc.voidMethod(anyString, (List&lt;?&gt;) any);
}};

2.with

// 不为空即可
abc.voidMethod("str", (List&lt;?&gt;) withNotNull());

// 需要是什么类型,需要包含 xyz 字符
abc.stringReturningMethod(withSameInstance(item), withSubstring("xyz"));

// 前缀需要是 abc
mock.doSomething(anyInt, true, withPrefix("abc"));

// 更多查看接口文档

6.4 调用次数约束/验证

// 该方法最少被调用 2 次
abc.voidMethod(); minTimes = 2;

// 被调用 1~5 次
abc.stringReturningMethod(); minTimes = 1; maxTimes = 5;

// 最多被调用 1 次
abc.anotherVoidMethod(3); maxTimes = 1;

6.5 从调用方法中捕捉参数,并对参数进一步验证

new Verifications() {{
 double d;
 String s;
 mock.doSomething(d = withCapture(), null, s = withCapture());

 assertTrue(d > 0.0);
 assertTrue(s.length() > 1);
}};

七、结尾

单测相关的意义开头已经讲了,这边不重复总结,补充下下面 2 点。

1.麻烦事

  • 业务代码的改动单测要跟着改动,不要之前写了单测,后面业务调整导致已有单测失败了,就简单的给 ignore 了。
  • 要一开始就规划好单测,不要写好业务代码,后续补单测。已经稳定的代码,补单测的意义不大。

2.收获

  • 提升开发素养:不做 CRUD 程序员,单测的工作中会让你深入了解各个代码甚至中间件的实现逻辑,深入底层了解内部实现。

同时,在这我为大家准备了一份软件测试视频教程(含面试、接口、自动化、性能测试等),就在下方,需要的可以直接去观看。

【2024最新版】Python自动化测试15天从入门到精通,10个项目实战,允许白嫖。。。

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

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

相关文章

分析Profiler Timeline中的算子序列,通过寻找频繁项集的办法,得到TOPK可融合的算子序列

分析Profiler Timeline中的算子序列,通过寻找频繁项集的办法,得到TOPK可融合的算子序列 1.相关链接2.代码【仅分析带通信算子的Pattern】3.在实际工程中发现 [all_gather, matrix_mm_out]频率最高4.[Ascend MC2](https://gitee.com/ascend/MindSpeed/blob/master/docs/features…

微软关闭中国所有线下店,并不影响全球第一

​关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 微软没有被时代淘汰&#xff0c;时代也没有告别微软!中国市场对微软可有可无&#xff0c;即便没有中国市场&#xff0c;微软市值也在全球前三&#xff0c;这是事实!a 5月中旬&#xff0c;微软azure解散中国分部…

【C语言入门】初识C语言:掌握编程的基石

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言入门 &#x1f4d2;1. 选择…

LLMs之gptpdf:gptpdf的简介、安装和使用方法、案例应用之详细攻略

LLMs之gptpdf&#xff1a;gptpdf的简介、安装和使用方法、案例应用之详细攻略 目录 gptpdf的简介 1、处理流程 第一步&#xff0c;使用 PyMuPDF 库&#xff0c;对 PDF 进行解析出所有非文本区域&#xff0c;并做好标记&#xff0c;比如: 第二步&#xff0c;使用视觉大模型&…

错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 python ping

报错提示&#xff1a;错误 [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试 用python做了一个批量ping脚本&#xff0c;在windows专业版上没问题&#xff0c;但是到了windows服务器就出现这个报错 解决方法&#xff1a;右键 管理员身份运行 这个脚本 …

使用 PCA 可视化数据的分类能力

使用 PCA 探索数据分类的效果&#xff08;使用 Python 代码&#xff09; 「AI秘籍」系列课程&#xff1a; 人工智能应用数学基础人工智能Python基础人工智能基础核心知识人工智能BI核心知识人工智能CV核心知识 主成分分析 (PCA) 是数据科学家使用的绝佳工具。它可用于降低特征…

年轻人为什么那么爱喝奶茶?

作者 | 艾泊宇 为什么年轻人那么爱喝奶茶&#xff1f;答案很简单&#xff1a;对他们来说&#xff0c;奶茶之于年轻人&#xff0c;正如白酒之于中年人。 奶茶不仅仅是一种饮料&#xff0c;它已经演化成一种文化现象&#xff0c;代表着温暖和爱的象征&#xff0c;甚至在某种程度上…

【机器学习】机器学习与电商推荐系统的融合应用与性能优化新探索

文章目录 引言第一章&#xff1a;机器学习在电商推荐系统中的应用1.1 数据预处理1.1.1 数据清洗1.1.2 数据归一化1.1.3 特征工程 1.2 模型选择1.2.1 协同过滤1.2.2 矩阵分解1.2.3 基于内容的推荐1.2.4 混合推荐 1.3 模型训练1.3.1 梯度下降1.3.2 随机梯度下降1.3.3 Adam优化器 …

热备路由HSRP与VRRP

一、什么是HSRP HSRP&#xff08;Hot Standby Router Protocol&#xff09;是Cisco的专有协议&#xff0c;用于实现网络中路由器的冗余和故障转移。通过HSRP&#xff0c;可以将多台路由器组成一个“热备份组”&#xff0c;形成一个虚拟路由器。在这个组内&#xff0c;只有一个…

【自适应滤波系列四】回声消除(Acoustic Echo Cancellation, AEC)信号模型及其本质

什么是回声 关于回声的产生与传播,凌逆战(https://www.cnblogs.com/LXP-Never)大佬在其博客中阐述得很详细,可以去看他的博客,下面部分图片来源于其博客 回声就是声音信号经过一系列反射之后,又听到了自己讲话的声音,这就是回声。一些回声是必要的,比如剧院里的音乐回声…

Character.ai因内容审查流失大量用户、马斯克:Grok-3用了10万块英伟达H100芯片

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 1、爆火AI惨遭阉割&#xff0c;1600万美国年轻人失恋&#xff1f;Character.ai被爆资金断裂 美国流行的社交软件Character.ai近期对模型进行大幅度内容审查&#xff0c;导致用户感到失望并开始流失。…

2024阿里云大模型自定义插件(如何调用自定义接口)

1&#xff0c;自定义插件入口 2&#xff0c;插件定义&#xff1a;描述插件的参数 2.1&#xff0c;注意事项&#xff1a; 2.1.1&#xff0c;只支持json格式的参数&#xff1b;只支持application/JSON&#xff1b;如下图&#xff1a; 2.1.2&#xff0c;需要把接口描述进行修改&a…

使用RNN模型构建人名分类器

使用RNN模型构建人名分类器 1 项目需求和实现分析 短文本分类问题 2 数据处理三部曲 场景1&#xff1a;数据处理三部曲示意图 场景2&#xff1a;三个字母onehot编码形状分析 3 构建RNN模型 4 构建训练函数并进行训练 - 有关模型、损失函数、优化器三者在pytorch中的表示 5…

U-Net在影像组学:医学图像分割中的研究进展|顶刊速递·24-07-04

小罗碎碎念 本期文献主题&#xff1a;U-Net在影像组学/医学图像分割中的研究进展 U-Net对于研究影像组学的老师/同学&#xff0c;应该非常熟悉&#xff0c;今天这期推文就是想系统的盘一盘它在医学图像分析中的应用情况。六篇文献最新的是上个月刚发表的&#xff0c;最旧的也是…

java入门-基础语法(运算符)

运算符是对变量、字面量进行运算的 符号 &#xff08;一&#xff09;基本的算术运算符、符号做连接符 &#xff08;1&#xff09;基本运算符&#xff1a;(加)&#xff0c; - &#xff08;减&#xff09;、 * &#xff08;乘&#xff09;、 / &#xff08;除&#xff09;、%&…

DP:背包问题----0/1背包问题

文章目录 &#x1f497;背包问题&#x1f49b;背包问题的变体&#x1f9e1;0/1 背包问题的数学定义&#x1f49a;解决背包问题的方法&#x1f499;例子 &#x1f497;解决背包问题的一般步骤&#xff1f;&#x1f497;例题&#x1f497;总结 ❤️❤️❤️❤️❤️博客主页&…

什么是分库分表?它有哪些实现类型?

假如你正在使用关系型数据库开发一款健康类系统。业务发展很好&#xff0c;系统有很多活跃的新老用户&#xff0c;这些用户会和平台的医生团队进行交互&#xff0c;每天可能会生成数万甚至数十万级别的业务数据。这样的话&#xff0c;随着数据量越来越大&#xff0c;系统中的某…

Java项目:基于SSM框架实现的游戏攻略网站系统分前后台【ssm+B/S架构+源码+数据库+毕业论文+任务书】

一、项目简介 本项目是一套基于SSM框架实现的游戏攻略网站系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…

静态方法与实例方法的区别

静态方法与实例方法的区别 1、静态方法&#xff08;Static Methods&#xff09;1.1 调用方式1.2 访问权限 2、实例方法&#xff08;Instance Methods&#xff09;2.1 调用方式2.2 访问权限 3、总结 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1…

使用 Smart-doc 记录 Spring REST API

如果您正在使用 Spring Boot 开发 RESTful API&#xff0c;您希望让其他开发人员尽可能容易地理解和使用您的 API。文档是必不可少的&#xff0c;因为它为将来的更新提供了参考&#xff0c;并帮助其他开发人员与您的 API 集成。很长一段时间以来&#xff0c;记录 REST API 的方…