使用 JUnit 测试 Java 应用程序 课堂笔记(一)

目录

一.基本概念

二.初识JUnit

三.JUnit应用

测试中缺少jar包解决办法(非常常见的问题):​

四.代码覆盖测试 Jacoco使用

五.桩模块和模拟对象


一.基本概念

1.1 软件测试在开发无缺陷软件中扮演重要角色。

2.1 软件测试生命周期 (STLC):用于测试软件以提高其质量的系统先进方法。

2.2 STLC过程:需求分析、测试规划、测试环境搭建、测试用例开发、测试执行(缺陷修正)、测试收尾。

2.3 软件测试包括许多 测试策略 和 级别。

软件测试的两种测试策略:

黑盒测试:不能访问代码,测试整体功能,不测试代码语句分支。

白盒测试:需要访问代码,测试代码总的每个语句分支。

软件测试的多种级别:

①单元测试:方法/类/模块等测试的最小单元,要确保无缺陷且可重用

②集成测试:将各种单元测试组合在一起进行的测试。

③系统测试:采用黑盒测试,通过输入输出测试整个系统。

④回归测试:修改后的应用程序被再次测试。

⑤验收测试:给客户演示的测试。

⑥性能测试:检查应用在普通/负荷场景下的响应时间。

3.如何将IDE用于单元测试?

①命令提示符:复杂,需要安装JDK,配置Java环境。

②IDE,简单,Netbeans、Eclipse中添加测试所需的jar文件。

4.关于命令提示符的实例:

①通过cmd检测是否安装jdk:

②更改命令根目录:

③编译源程序,并向测试程序中添加jar包:

④运行,即将测试程序作为运行核心类的参数进行运行:

⑤修改源文档参数,重新编译测试类,运行:

⑥整体结果:

⑦命令符执行时的文件夹展示:

二.初识JUnit

1.Junit强调了在编码前先测试代码的想法,主要用于单元测试

2.框架架构:

3.框架架构元素介绍:①测试用例:测试最小单元。

②测试套件:如果有多个测试类并且希望自动逐个执行这些类。

③测试:测试用例的集合。

④测试运行器:执行测试时,测试运行器在后台执行,用于显示测试结果(通过or不通过)。

⑤测试结果。

4.测试用例构成:输入、事件、期望响应。

三.JUnit应用

1. JUnit批注

@Test:标注的方法作为测试类,运行之前会先构建类的实例,之后再执行标注的方法。

@Before/After:公共方法,无返回值,在每个测试用例前后执行一次。

@BeforeClass/AfterClass:公共方法,应声明为static,无返回值,在所有测试用例前后执行一次。

导入写好的程序及测试程序:

如果新建测试类,则程序和测试程序应在两个包:

测试文件的书写要素见图:

如何运行测试程序?不是运行程序哦:

测试中缺少jar包解决办法(非常常见的问题):

before/beforeclass的区别:

同一构造函数可能构造不同的:

同一构造函数可能构造不同的:

测试结果的可能性:

2.0容忍值、断言、标记

2.1 关于断言:

             JUnit Assert 类(断言)中提供 静态方法 以验证期望结果和实际结果。

2.2 容忍值举例:

3.0 向测试用例中添加行为

3.1 抛出异常

异常分类:

技巧:Ctrl+/表示全部注释

//在Test中添加expected表示希望抛出的异常类型
//timeout属性 表示设置超时时间
@Test(expected = ArithmeticException.class,timeout=3000)
	public void testDivide() {
		Calculator instance = new Calculator();
           ...}

3.2 不具备完全功能 需要忽略

@Ignore("temporarily ignore")   //这是忽略注解
@Test
	public void testAdd2() {
	...}

3.3要通过指定计时器来检查代码优化(见异常分类)

4.0 在JUnit中测试多个测试

4.1 参数化测试(用户在单个测试用例中作为参数传递和运行多组输入和期望值)

import ...

@RunWith(Parameterized.class)  //2.批注测试类
public class CalculatorTest {
	private int a;
	private int b;
	private int expected;

	//3.创建构造函数用于接收测试数据集,通过构造函数将测试数据实例化到测试对象里
	public CalculatorTest(int a, int b, int expected) {
		this.a = a;
		this.b = b;
		this.expected = expected;
	}
	
	@Test  //4.测试用例会使用测试数据集,是通过构造函数传过来的
	public void test() { 
		Calculator instance = new Calculator();
		int actual = instance.add(a,  b);
		assertEquals(expected, actual);
	}
	
	@Parameters  //1.批注一个 公共静态 函数,返回数组作为测试数据集
	public static Collection<Integer[]> getParameters(){
		Integer [][] inputArr;
		inputArr = new Integer[][] {{1,2,3}, {-1,2,1}, {2,2,3}, {4,0,4}};
		return Arrays.asList(inputArr);
	}
}

4.2 测试套件(不同类的多个单元测试组合在一起执行)

//创建测试套件,批注测试类,使用这个注解固定搭配
@RunWith(Suite.class) 
@Suite.SuiteClasses({FactorialTest.class, ReverseNumberTest.class,CalculatorTest.class})
//上面这个注解中输入需要同时测试的类,作为测试套件的一部分
public class CalculatorTestSuite {    	//若声明为测试套件,则测试类中的测试用例不会执行
	//@Ignore
	@Test
	public void test() {
		fail("Not yet implemented");
	}
}

5.1 灵活表达式assertThat()

5.1.1.灵活表达式使用场景举例:

5.1.2.灵活表达式和其他表达式区别:

5.1.3.在 assertThat 语句中提供一组预定义函数,称为 Hamcrest 匹配器。

import static org.hamcrest.Matchers.*;
...
//第一行的Matchers.*,防止我们每个匹配器都要重新import
//注意一定要添加 hamcrest1.3库!右键项目属性 → 寻找+
public class HamcrestTest {
	private String email;
	private List<String> courseNames;
	private String registerNo;
	private int age;
	
	@Before
	public void setUp() {       //通过before初始化一些对象的值
		email = "[email protected]";	
		courseNames = new ArrayList<String>();
		courseNames.add("Using java");
		courseNames.add("Let us go");		
		registerNo = "Rg12121";		
		age = 23;
	}


	//核心匹配器
	//选中下面的方法名部分点击运行即可测试单个测试用例
	@Test
	public void testDescribe() {
		//assertThat():断言,灵活表达式,(实际值,期望值);
        //containsString():实际的邮件地址是否包含指定的字符串。
		Assert.assertThat(email,containsString("@test.com"));
	}
	//总是能够匹配成功的匹配器anything()
	@Test
	public void testAnything() {
		Assert.assertThat("abc", anything());
	}
	//is(equalTo(x))的简写,让测试语言更具有可读性
	@Test
	public void testIs() {
		Assert.assertThat("fff", is("fff"));
	}
	

	//逻辑匹配器
	//allof():保证实际值,满足给定的所有条件
	@Test
	public void testAllOf() {
		Assert.assertThat("fff", allOf(containsString("f"),containsString("ff")));
	}
	//anyOf():保证满足所给的至少一个条件即可
    //describedAs("testAnyOf":...:这个用来增加文本的可读性
	@Test
	public void testAnyOf() {
		Assert.assertThat("fff", describedAs("testAnyOf",anyOf(containsString("f"), containsString("bbb"))));
	}
	//no():实际字符串不包括给定的字符
	@Test
	public void testNot() {
		Assert.assertThat("fff", not(containsString("a")));
	}
	

	//对象匹配器
	//相当一object.t.equals(java.lang.Object)
	@Test
	public void testEqualTo() {
		//equalTo():两个值相等
		Assert.assertThat("fff", equalTo("fff"));
	} 
	//toString方法匹配
	@Test
	public void testHasToString() {
		Assert.assertThat("fff", hasToString("fff"));
	} 
	//instanceOf:断言前面的这个变量是后面类的一个实例
	@Test
	public void testInstanceOf() {
		Assert.assertThat(email, instanceOf(String.class));
	}
	//notNullValue
	@Test
	public void testNotNullValue() {
		assertThat(email, notNullValue());
	}
	//nullValue
	@Test
	public void testNullValue() {
		assertThat(null, is(nullValue()));
	}
	//sameInstance:对象比较的一种方式 ,比较的是两个对象是否指向同一个地址;
    //equalto()也可以比较对象,比较的是值;
	@Test
	public void testSameInstance() {
		Object obj1 = new Object();    
		Object obj2 = obj1;   //把ob1指向的地址赋值给了ob2
		assertThat(obj1, is(sameInstance(obj2)));
	}
	

	//文本匹配器
	//equalToIgnoringCase:忽略大小写
	@Test
	public void testEqualToIgnoringCase() {
		assertThat("This is STRING", is(equalToIgnoringCase("this IS string")));
	}
	//equalToIgnoringWhiteSpace:忽略空格比较,如果有多个空格会被当做一个空格来处理,不是零个
	//containsString:包含某个字符串
	@Test
	public void testEqualToIgnoringWhiteSpace() {
		assertThat("This is STRING", is(equalToIgnoringWhiteSpace("This is STRING    ")));	}
	@Test
	public void testContainsString() {
		Assert.assertThat(email, containsString("@test.com"));
	}
	//endsWith:以什么字符串结尾
	@Test
	public void testEndsWith() {
		assertThat(email, endsWith("test.com"));
	}
	//startsWith:以什么字符串开始
	@Test
	public void testStartWith() {
		assertThat(registerNo, startsWith("Rg"));
	}
	

	//集合匹配器,区分三种应用情况
	//array,数组元素,每一个元素都要满足指定匹配器,第一个对应第一个,第二个对应第二个....
	@Test
	public void testArray() {
		Assert.assertThat(new String[] {"abc","ddd"}, array(startsWith("a"), containsString("dd")));
	} 
	//hasItem:用于集合list,集合中至少有一个元素满足规则
	@Test
	public void testHasItem() {
		Assert.assertThat(Arrays.asList("abc","fff"), hasItem(startsWith("a")));
	} 
	//hasItemInArray,数组中至少有一个元素符合我们的规则
	@Test
	public void testHasItemArray() {
		Assert.assertThat(new String[] {"abc","ddd"}, hasItemInArray(startsWith("a")));
	} 

	
	//数字比较匹配器
	//closeTo(double operand,double error)
	//实际数字和期望数字比较的误差在指定容忍范围之内
	@Test
	public void testCloseTo() {
		Assert.assertThat(2.5, closeTo(5.12,4.0)); //实际值,期望值,容忍值
	} 
	//greaterThan:给定数字 大于 指定数字
	@Test
	public void testGreaterThan() {
		assertThat(4, greaterThan(3));
	}
	//greaterThanOrEqualTo 大于等于
	@Test
	public void testGreaterThanOrEqualTo() {
		assertThat(3, greaterThanOrEqualTo(3));
	}
	//lessThan 小于
	@Test
	public void testLessThan() {
		assertThat(3, is(lessThan(4)));
	}
	//lessThanOrEqualTo 小于等于
	@Test
	public void testLessThanOrEqualTo() {
		assertThat(3, is(lessThanOrEqualTo(3)));
	}
	
	//hamcrest:灵活表达式混合使用的实例
	//practise
	@Test
	public void testMix() {
		String[] datas = new String[] {"abcddd", "def", "hur"};
		
		assertThat(datas, hasItemInArray(  //数组中至少有一个满足里面的任意一个条件
				anyOf(
						containsString("bc"),
						startsWith("dfd"))
				));
		//数组中至少有一个满足里面任意一个条件,其中一个条件是说同时满足这个条件里面的两个条件
		assertThat(datas, hasItemInArray(  
  				anyOf(
						allOf(
								containsString("bc"), 
								endsWith("ddd")
								),
						startsWith("dfd")
				)
				));
	}
	

四.代码覆盖测试 Jacoco使用

1.代码覆盖分析

1.1 定义:①能借助于测试跟踪代码中所有可能错误。

②识别测试用例未覆盖的那部分代码。

1.2 代码覆盖指标的类型:

语句覆盖(确保所有语 句都执行了至少一次

函数覆盖(验证所有函数是否由测试执行了一次

条件覆盖(确保if块、else块各执行一次)

类型覆盖(确保for循环至少执行一次)

1.3 语句覆盖率 = 已执行语句数 / 总语句数

1.4 代码覆盖工具类型 

EclEMMA (Eclipse中的开源插件,突出显示代码,生成覆盖报告) 

 JaCoCo(可用于Netbeans,突出显示代码,生成覆盖报告)

2.JaCoCo

2.1 Netbeans中安装Jacoco插件的方法:工具→插件:

安装完毕后,右键项目→Test with Jacoco.....会自动跳转到浏览器生成覆盖报告,IDE中也会相应显示

注意代码块颜色

红色:完全未覆盖    黄色:部分覆盖    绿色:完全覆盖

2.2 Eclipse Java7以上自带

右键项目选择如图,将自动测试:

Coverage面板打开方式:Windows→Show View→Other:

五.桩模块和模拟对象

1.桩模块

作用:B模块依赖A模块,测试B模块时A模块未完成,则用桩模块替换A模块模仿一个结果给B模块,保证测试正常执行。

不需要更改所测试的代码,是对不可用部分的代码进行虚化。

桩模块作为类创建,“桩模块”的实现,需要“接口机制”的支持。

桩模块文件结构:

//邮件验证模块 未完成相关功能
public class EmailChecker implements IEmailChecker{
	@Override
	public boolean verifyEmail(String eamil) {
		return false;
	}

//创建接口 接收邮件验证参数
public interface IEmailChecker {	
	public boolean verifyEmail(String eamil);
    ...

//注册模块 依赖于邮件验证模块 已完成
public class Registration {
    ...
	
	//注册依赖于邮箱检测,但是邮箱检测模块并没有实现
	public boolean verify(String name, String email) {
		boolean result = true;
		if(name == null || "".equals(name)) {
			result = false;
		}
		if(!emailChecker.verifyEmail(email)) {
			result = false;
		}
		return result;
	}

//桩模块 里面的类没有任何业务逻辑,仅仅是模拟未完成模块的结果,返回一个替代值,从而不影响测试
public class StubEmailChecker implements IEmailChecker{
	@Override
	public boolean verifyEmail(String email) {
		return true;
        ...

//测试代码
public class RegistrationTest {
	@Test
	public void test() {
		//首先声明了一个桩模块
		//IEmailChecker emailChecker = new StubEmailChecker();
		//如果实际验证邮箱的的模块已经完成,则去掉桩模块,引入实际模块,
        替换过程中不需要对开发代码进行任何更改
		IEmailChecker emailChecker = new EmailChecker();
		//通过注册类中的构造函数将桩模块或者完成的邮箱验证参数引入进来
		Registration register = new Registration(emailChecker);
		//赋值
		String email = "[email protected]";
		String name = "abc";
		//对注册模块进行单元测试
		boolean result = register.verify(name, email);
        //验证
		assertTrue(result);
	}

2.模拟对象

作用:测试一个依赖于未实现的外部服务的模块。

	@Test
	public void test() {
		
		//创建普通模拟对象,该对象代表邮箱验证模块
		IEmailChecker emailChecker = EasyMock.createMock(IEmailChecker.class);
		//通过注册类中的构造函数将邮箱验证对象引入进来,模拟邮箱验证模块正常执行
		Registration register = new Registration(emailChecker);
		//设定期望,期望值的返回值是true,调用次数是一次,except静态方法只能调用一次
		//如果email的值不同,即email与预期值不符,会抛出异常
		EasyMock.expect(emailChecker.verifyEmail("[email protected]")).andReturn(true).times(1);
		//注册期望方法EasyMock.replay(emailChecker);emailChecker示例是EasyMock创建的
		//如果【EasyMock.replay(emailChecker);】没有执行的话,会怎么样?为什么?
		//缺少了“注册”,那么我们的“设置的期望值”根本不会起作用(我们姑且对运行说参数错误的情况不提),
		//也就是说,EasyMock根本没有模拟我们的“期望”
		EasyMock.replay(emailChecker);	
		String email = "[email protected]";
		String name = "abc";
		//对已经完成的模块进行单元测试boolean result = register.verify(name, email);
		//我们其实没有创建EmailChecker的实例
		//当boolean result = register.verify(name, email);这句话执行的时候,
		//EasyMock是根据我们EasyMock.expect()设置的期望值来返回结果的,
		//并且在EasyMock.expect中,我们定义了是设置的verifyEmail()函数的传入参数。
		//所以EasyMock会“模拟”输出,所以并不是真正的实例化了一个“实现了IEmailChecker”的对象
		boolean result = register.verify(name, email);
		assertTrue(result);
		//验证期望方法
		EasyMock.verify(emailChecker);
	}	
}

3.使用Mockito执行测试

步骤:添加Mockito依赖,创建模拟对象,验证方法执行。

	@Test
	public void test() {
		//创建 mock 模拟对象 ,mock是一个静态方法,用于接收希望为其创建模拟对象的类或接口
		IEmailChecker emailChecker = mock(IEmailChecker.class);
		Registration register = new Registration(emailChecker);
		
		//期望接收一个函数,返回true
		when(emailChecker.verifyEmail("[email protected]")).thenReturn(true);
		String email = "[email protected]";
		String name = "abc";
		boolean result = register.verify(name, email);
		assertTrue(result);
		//在最后验证你是不是最少执行一次呢
		verify(emailChecker, atLeast(1)).verifyEmail("[email protected]");
	}
	

发布了29 篇原创文章 · 获赞 21 · 访问量 1666

猜你喜欢

转载自blog.csdn.net/Lyrelion/article/details/104523315