单元测试(Junit)

news2024/11/26 10:41:47

系统—模块—子模块,子模块中不可分割的程序单元的测试,单元的粒度根据实际情况可能是 类或方法等。
面向对象编程中,最小单元就是方法。

单元测试目的是在集成测试和功能测试之前对系统可测试单元进行逐一检查和验证。

单元测试基本原则

Automatic自动化

单元测试应该是全自动执行,测试用例通常会被频繁地触发执行。
单元测试不允许使用System.out人工验证,而必须使用断言来验证

Independent独立性

用例之间不允许相互调用,也不允许执行次序的先后依赖
如下testMethod2需要调用testMethod2会导致运行效率降低

@Test
public void testMethod1(){

}

@Test
public void testMethod2(){
	testMethod1();
}

主流测试框架中,JUnit的用例执行顺序是无序的
TestNG支持测试用例的顺序执行(默认测试类用例按照字典升序执行,也可以通过XML或注解Priority方式来配置执行顺序)

Repeatable可重复性

不受外界影响,比如单元测试通常被放到持续集成中,每次有代码提交,单元测试都会被触发。

为了保证被测试模块的交付质量,需要符合BCDE原则

  • Border边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序
  • Correct正确输入并得到预期结果
  • Design与设计文档相结合
  • Error证明程序有错,如强制输入非法数据、异常流程,非业务允许输入来得到预期错误结果

Mock

弥补一些不具备的因素

  • 功能因素,比如被测试方法内部调用的功能不可用
  • 时间因素,比如双十一还没有到来,与此时间相关的功能点
  • 环境因素,支付宝政策、多端环境PC或手机
  • 数据因素,线下数据样本太小,难以覆盖真实场景
  • 其他因素,负载的依赖采用Mock方式实现

最简单的Mock方式是硬编码
最优雅的是使用配置文件
最佳方式是使用Mock框架,例如JMockit/EasyMock/JMock等

单元测试覆盖率

粗粒度

类覆盖:类中只要有方法或变量被测试用例调用或执行到
方法覆盖:测试用例执行过程中,某个方法被调用了。

细粒度

①、行覆盖:也称语句覆盖,度量可执行语句是否被执行到。
公式:执行到语句的行数/总的可执行语句行数

public class ConverageSampleMethods{
	public Boolean testMethod(int a,int b,int c){
		boolean result = false;
		if(a==1 && b==2 || c==3){
			result = true;
		}
		return result;
	}
}

以上方法有5个可执行语句和3个入参

@Test
@DisplayName("line converage sample test")
void testLineConverageSample(){
	CoverageSampleMethods coverageSampleMethods = newCoverageSampleMethods(); 
	Assertions.assertTrue(coverageSampleMethods.testMethod( 1 , 2, 0));
}

以上测试用例的行覆盖率是 100% ,但是在执行过程中 c==3 的条件判断根本没有被执行到, a!=I 并且 c!=3 的情况难道不应该测试一下吗?由此可见,行覆盖的覆盖强度并不高,但由于容易计算,因此在主流的覆盖率工具中,它依然是一个十分常见的参考指标。

②、分支覆盖:也称为判定覆盖,用来度量程序中每 个判定分支是否都被执行到。
公式:代码中被执行到的分支数/所有分支的总数

③、条件判定覆盖:要求设计足够的测试用例,能够让判定中每个条件的所有可能情况至少被执行一次 同时每个判定本身的所有可能结果也至少执行一次。

@ParameterizedTest//定义一个参数化测试
@DisplayName("Condition Decision coverage sample test result true")
@CsvSource({
	"0,2,3",
	"1,0,3",
})//通过定义一个Stirng数组来定义多次运行时的参数列表,
void testConditionDecisionCoverageTrue(int a,int b,int c){
	CoverageSampleMethods coverageSampleMethods = newCoverageSampleMethods() ; 
	Assertions assertTrue(coverageSampleMethods.testMethod(a, b, c)) ;
}

@DisplayName("Condition Decisior coverage sample test result false")
void testConditionDecisionConverageFalse(){
	CoverageSampleMethods coverageSampleMethods = newCoverageSampleMethods() ; 
	Assertions assertTrue(coverageSampleMethods.testMethod(0, 0, 0));
}

④、条件组合覆盖:是指判定中所有条件的各种组合情况都出现至少一次。

@ParameterizeTest
@DisplayName("Multiple Condition Converage sample test result true")
@CsvSource({
	"1,2,3",
	"1,2,0",
	"1,0,3",
	"0,2,3",
	"0,0,3",
})
void testMultipleConditionConverageSampleTrue(int a,int b,int c){
	CoverageSampleMethods coverageSampleMethods= new CoverageSampleMethods();
	Assertions.assertTrue(coverageSampleMethods.testMethod(a,b,c)) ;
}

@ParameterizeTest
@DisplayName("Multiple Condition Converage sample test result false")
@CsvSource({
	"1,0,0",
	"0,0,0",
	"0,2,0",
})
void testMultipleConditionConverageSampleTrue(int a,int b,int c){
	CoverageSampleMethods coverageSampleMethods= new CoverageSampleMethods();
	Assertions.assertTrue(coverageSampleMethods.testMethod(a,b,c)) ;
}

这组测试用例同时满足了( a= !, b2, c3 )为( true, true, true )、( true,true, false )、( true, false, true )、( true, false, false )、( false, true, true )、( false,true, false )、( false, false, true )、( false, false , false )这 种情况。对于一个包含了 个条件的判定 至少需要 个测试用例才可以。虽然这种覆盖足够严谨,但无疑给编写测试用例增加了指数级的工作量。

⑤、路径覆盖:要求能够测试到程序中所有可能的路径
在 testMethod 方法中,可能的路径有 a1,b2,c==3 b!=2 c! =3 a! ,c !=1 c! =3种。当存在“||”时 如果第一个条件已经为 true不再计算后边表达式的值。而当存在“&& 时,如果第一个条件已经为 false同样不再计算后边表达式的值。满足路径覆盖的测试用例如下

@ParameterizeTest
@DisplayName("Path coverage sample test result true")
@CsvSource({
	"1,2,0",
	"1,0,3",
	"0,0,3",
})
void testMultipleConditionConverageSampleTrue(int a,int b,int c){
	CoverageSampleMethods coverageSampleMethods= new CoverageSampleMethods();
	Assertions.assertTrue(coverageSampleMethods.testMethod(a,b,c)) ;
}


单元测试编写

JUnit 和TestNG 几乎始终处于市场前两位单元测试框架

JUnit常用的注解
在这里插入图片描述
测试用例的方法命名规范:

  • 传统方式test开头,testDecodeUserTokenSuccess
  • should—When,shouldSuccessWhenDecodeUserToken
//定义一个测试类并指定用例在测试报告展示的名称
@DisplayName("售票器类型测试")
public class TicketSellerTest{
	
	//定义待测类的实例
	private TicketSeller ticketSeller;

	/**
		定义在整个测试类开始前执行
		通常包括全局和外部资源(包括测试桩)的创建和初始化
	*/
	@BeforeAll
	public static void init(){

	}
	
	/**
		定义在整个测试类完成后执行的操作
		通常包括全局和外部资源的释放和销毁
	*/
	@AfterAll
	public static void cleanup(){

	}
	
	/**
		定义在每个测试用例开始前执行的操作
		通常包括基础数据和运行环境的准备
	*/
	@BeforeEach
	public void create(){
		this.ticketSeller = new TicketSeller();
	}
	
	/**
		定义在每个测试用例完成后的操作
		通常包括运行环境的清理
	*/
	@AfterEach
	public void destroy(){

	}
	
	/**
		测试用例,当车票售出后余额
		测试用例,余额不足应该报错
	*/
	@Test
	@DisplayName("售票后余票应减少")
	public void shouldReduceInventoryWhenTicketSoldOut(){
		ticketSeller.setInventory(10);
		ticketSeller.sell(1);
		assertThat(ticketSeller.getInventory()).isEqualTo(9);
	}

	@Test
	@DisPlayName("余票不足应报错")
	public void shoudThrowExceptionWhenNoEnoughInventory(){
		ticketSeller.setInventory(0);
		assertThatExceptionOfType(TicketException.class)
			.isThrownBy(()->{dicketSeller.sell(1)})
			.withMessageContaining("all ticket sold out")
			.withNoCause();
	}
	
	/**
		Disabled将金庸测试用例
		该测试用例会出现在最终的报告中,但不会被执行
	*/
	@Disabled
	@Test
	@DisplayName("有退票时余额应增加")
	public void shouldIncreateInventoryWhenTicketRefund(){
		ticketSeller.setInventory(10);
		ticketSeller.refund(1);
		assertThat(ticketSeller.getInventory()).isEqualTo(11);
	}
}

在这里插入图片描述
对于使用Maven或Gradle等命令方式运行单元测试,该注解的内容被忽略,单元测试出错
在这里插入图片描述

当测试用例比较多,为了更好组织测试的结构
推荐使用JUnit的@Nested注解表达层次关系

@DisplayName("交易服务测试")
public class TransactioServiceTest{
	
	@Nested
	@DisplayName("用户交易测试")
	class UserTransactionTest{
		
		@Nested
		@DisplayName("正向测试用例")
		class PositiveCase(){

		}

		@Nested
		@DisplayName("负向测试用例")
		class NegativeCase(){
			
		}		
	}

	@Nested
	@DisplayName("商家交易测试")
	class CompanyTransactionTest{
		
	}
}

使用分组测试,在不同场景下选择执行响应的测试用例

  • 执行很快且很重要的冒烟测试用例
  • 执行很慢但同样比较重要的日常测试用例
  • 数量很多但不太重要的回归测试用例
@DisplayName("售票器类型测试")
public class TicketSellerTest{
	
	@Test
	@Tag("fast")
	@DisplayName("售票后余票应减少")
	public void shouldReduceInventoryWhenTicketSoldOut(){

	}

	@Test
	@Tag("slow")
	@DisplayName("一次性购买20张车票")
	public void shouldSuccessWhenBuy20TicketsOnce(){

	}
}

通过标签选择执行用例类型
在Maven中可以通过配置maven-surefire-pIugin插件来实现

<build>
	<plugins>
		<plugin>
		<artifactid>maven-surefire-plugin</artifactid>
		<version>2.22.0</version>
		<configuration>
			<properties>
				<includeTags>fast</includeTags>
				<excludeTags>slow</excludeTags>
			</properties>
		</configuration>
		</plugin>
	</plugins>
</build> 

在Gradle中通过JUnit专用的junitPlatform配置来实现

junitPlatForm{
	filters{
		engines{
			include 'junit-jupiter','junit-vintage'
		}
		tags{
			include 'fast'
			exclude 'slow'
		}
	}
}

使用@TestFactory注解将数据的输入和输出与测试逻辑分开

@DisplayName("售票器类型测试")
public class ExchangeRateConverterTest{
	
	@TestFactory
	@DisplayName("时间售票检查")
	Stream<DynamicTest> oddNumberDynamicTestWithStream(){
		ticketSeller.setCloseTime(LocalTime.of(12,20,25,0));
		return Stream.of(
			Lists.list("提前购票",LocalTime.of(12,20,24,0),true),
			Lists.list("准点购买",LocalTime.of(12,20,25,0),true),
			Lists.list("晚点购票",LocalTime.of(12,20,26,0),false);
		).map(
			data -> DynamicTest.dynamicTest((String)data.get(0),()->assertThat((ticketSeller.cloudSellAt(data.get(1)))
				.ieEqualTo(data.get(2))
		);
	}
}

断言与假设

常用的断言方法
在这里插入图片描述
在这里插入图片描述

静态方法
在这里插入图片描述
除了JUnit断言,还有AssertJ流式断言

/**
	使用JUnit断言
*/
public class JUnitSampleTest{
	
	@Test
	public void testUsingJunitAssertThat(){
		//字符串判断
		String s = "abcde";
		Assertions.assertTrue(s.startsWith("ab"));
		Assertions.assertTrue(s.endsWith("de"));
		Assertions.assertEquals(5,s.length());

		//数字判断
		Integer i = 50;
		Assertions.assertTrue(i > 0);
		Assertions.assertTrue(i < 100);

		//日期判断
		Date date1 = new Date();
		Date date2 = new Date(date1.getTime() + 100);
		Date date3 = new Date(date1.getTime() - 100);
		Assertions.assertTrue(date1.before(date2));
		Assertions.assertTrue(date1.after(date3));

		//List判断
		List<String> list = Arrays.asList("a","b","c","d");
		Assertions.assertEquals("a",list.get(0));
		Assertions.assertEquals(4,list.size());
		Assertions.assertEquals("d",list.get(list.size() - 1))

		//Map判断
		Map<String,Object> map = new HashMap<>();
		map.put("A",1);
		map.put("B",1);
		map.put("C",1);
		Set<String> set = map.keySet();
		Assertions.assertEquals(3,map.size());
		Assertions.assertTrue(set.containsAll(Arrays.asList("A","B","C")));
	}
}
/**
	使用AssertJ的断言
*/
public class AssertJSampleTest{
	
	@Test
	public void testUsingAssertJ(){
		//字符串判断
		String s = "abcde";
		Assertions.assertThat().as("字符串判断:判断首尾及长度")
			.startsWith("ab").endsWith("de").hasSize(5);

		//数字判断
		Integer i = 50;
		Assertions.assertThat(i).as("数字判断")
			.isGreaterThan(0).isLessThan(100);

		//日期判断
		Date date1 = new Date();
		Date date2 = new Date(date1.getTime() + 100);
		Date date3 = new Date(date1.getTime() - 100);
		Assertions.assertThat(date1).as("日期判断:日期大小比较")
			.isBefore(date2).isAfter(date3);
	}

	//List判断
	List<String> list = Arrays.asList("a","b","c","d");
	Assertions.assertThat(list)
		.as("List的判断:首尾元素及长度").startsWith("a")
		.endsWith("d").hasSize(4);

	//Map判断
	Map<String,Object> map = new HashMap<>();
	map.put("A",1);
	map.put("B",1);
	map.put("C",1);
	Assertions.assertThat(map).as("Map的判断:长度及key值")
		.hasSize(3).containsKeys("A","B","C");
}

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

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

相关文章

MySQL表的增删改查(CRUD3约束)

这次我们开始先不复习嗷&#xff0c;等到把数据表的删除说完咱们统一&#xff0c;总结书写 1.数据表的删除&#xff1a; 语法&#xff1a; 1. 使用 DROP TABLE 语句删除单个表 基本语法&#xff1a;DROP TABLE [IF EXISTS] table_name; table_name是要删除的表的名称。IF EXIS…

go中Println和Printf的区别

Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 go中Println和Printf的区别 package mainimport ( "fmt" )//TIP To run your code, right-click the c…

【系统面试篇】进程和线程类(1)(笔记)——区别、通讯方式、同步、互斥、锁分类

目录 一、问题综述 1. 进程和线程的区别&#xff1f; 2. 进程的状态有哪些&#xff1f; 3. 进程之间的通信方式? &#xff08;1&#xff09;管道 &#xff08;2&#xff09;消息队列 &#xff08;3&#xff09;共享内存 &#xff08;4&#xff09;信号量 &#xff08…

编译安装并刷写高通智能机器人SDK

The Qualcomm Intelligent Robotics Product SDK (QIRP SDK) 高通智能机器SDK基于ROS2进行开发&#xff0c;此SDK适用于高通linux发行版本&#xff0c;QIRPSDK中提供以下内容&#xff1a; ROS 包中用于支持机器人应用程序开发的参考代码 用于评估机器人平台的端到端场景示例集…

网页版五子棋—— WebSocket 协议

目录 前言 一、背景介绍 二、原理解析 1.连接过程&#xff08;握手&#xff09; 2.报文格式 三、代码示例 1.服务端代码 &#xff08;1&#xff09;TestAPI 类 &#xff08;2&#xff09;WebSocketConfig 类 2.客户端代码 3.代码演示 结尾 前言 从本篇文章开始&am…

鸿蒙应用开发:下载功能

鸿蒙系统不断发展&#xff0c;有与安卓、iOS 形成三足鼎立之势&#xff0c;且其在智能手机、智能穿戴、车载、家居等行业领域的应用越来越广泛。作为开发者&#xff0c;如何抓住鸿蒙生态崛起的机遇&#xff0c;解决开发挑战&#xff0c;创造更好的应用体验&#xff1f;欢迎您和…

小白直接冲!BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测

小白直接冲&#xff01;BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测 目录 小白直接冲&#xff01;BiTCN-BiLSTM-Attention双向时间卷积双向长短期记忆神经网络融合注意力机制多变量回归预测效果一览基本介绍程序设计参考资料 效果一…

如何绘制产业链图谱?

绘制产业链图谱是一个系统性的工作&#xff0c;涉及到对产业的深入理解和分析。对于一般产业绘制产业图谱的步骤&#xff0c;我们可以参照以下流程&#xff1a; 1.明确目标产业链&#xff1a;确定要分析的产业链&#xff0c;比如新材料、新能源、智能制造等&#xff0c;这通常…

Pycharm,2024最新专业版下载安装配置详细教程!

先来一段官方介绍&#xff0c;PyCharm是一种PythonIDE&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外&#xff0c;该IDE提供了一些高级功能…

鸿蒙开发——进程模型与进程通信

1、进程模型 ❓ 什么是进程&#xff1f; 进程是一个正在执行的程序的实例。当我们启动一个程序时&#xff0c;操作系统会创建一个进程&#xff0c;分配给它所需的资源&#xff0c;如内存和CPU时间。每个进程至少有一个线程&#xff0c;即执行线程&#xff0c;负责执行程序的指…

SQL server 中 CROSS APPLY的使用

CROSS APPLY 是 SQL Server 中的一个操作符&#xff0c;用于将一个表表达式&#xff08;如子查询、函数等&#xff09;与外部表进行连接。CROSS APPLY 类似于 INNER JOIN&#xff0c;但它允许你在一个查询中多次引用外部表的行&#xff0c;并且可以动态地生成结果集。 基本语法…

xlwings,让excel飞起来!

excel已经成为必不可少的数据处理软件&#xff0c;几乎天天在用。python有很多支持操作excel的第三方库&#xff0c;xlwings是其中一个。 关于xlwings xlwings开源免费&#xff0c;能够非常方便的读写Excel文件中的数据&#xff0c;并且能够进行单元格格式的修改。 xlwings还…

[大模型]Diffusion扩散式生成模型

一、概述 扩散式生成模型相较于GAN网络的对抗式生成模型&#xff0c;有更高的精度&#xff0c;也更符合人类的视觉和审美罗技&#xff0c;且风格化能力更强。现行的所有Diffusion模型都是基于2020年的论文DDPM来实现的。 GAN网络通过使生成器(Generator)生成的模型尽可能的逼近…

十四届蓝桥杯STEMA考试Python真题试卷第二套第五题

来源:十四届蓝桥杯STEMA考试Python真题试卷第二套编程第五题 本题属于迷宫类问题,适合用DFS算法解决,解析中给出了Python中 map() 和列表推导式的应用技巧。最后介绍了DFS算法的两种常见实现方式——递归实现、栈实现,应用场景——迷宫类问题、图的连通性、树的遍历、拓朴排…

keil5的Debug调试时,卡在 LDR R0, =SystemInit,无法往后进行

解决办法&#xff1a;使用STM32Cube生成的工程文件时&#xff0c;勾选Use MicroLIB即可

OpenEuler 使用ffmpeg x11grab捕获屏幕流,rtsp推流,并用vlc播放

环境准备 安装x11grab(用于捕获屏幕流)和libx264(用于编码) # 基础开发环境&x11grab sudo dnf install -y \autoconf \automake \bzip2 \bzip2-devel \cmake \freetype-devel \gcc \gcc-c \git \libtool \make \mercurial \pkgconfig \zlib-devel \libX11-devel \libXext…

ai常见实验

参考链接https://arxiv.org/pdf/2410.19894 对比实验&#xff08;sota 表格&#xff09; -辅助信息可以体现 P F 等 可视化结果 &#xff08;图片形式&#xff09; 消融实验 超参数实验 &#xff08;有时候表示 有时候单独表格 看哪个参数好&#xff09; 部分消融和超参数…

【万字详文介绍】:迭代扩张卷积神经网络(IDCNN)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

Oracle OCP认证考试考点详解082系列12

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 56. 第56题&#xff1a; 题目 解析及答案&#xff1a; 关于企业管理器&#xff08;EM&#xff09;Express&#xff0c;以下哪两个陈述是…

任务:拟合曲面

3d 要拟合粗的蓝色曲面&#xff0c;剩下三条线是在3个平面的投影 1 2 3 4d