软件工程头歌使用进行单元测试
第一关
任务描述
本关任务:使用Junit对给定的计算器类(Calculator.java)进行单元测试。
相关知识
单元测试的定义
单元测试(Unit testing)是对最小的软件设计单元(模块或源程序单元)的验证工作。
在设计得好的软件系统中,每个模块完成一个清晰定义的子功能,而且这个子功能和同级其他模块的功能之间没有相互依赖关系。因此,有可能把每个模块作为一个单独的实体来测试,而且通常比较容易设计检验模块正确性的测试方案。
目的:保证每个模块作为一个单元能正确运行。
所发现的错误:往往是编码和详细设计的错误。
单元测试的测试重点
单元测试集中检测软件设计的最小单元——模块。
在单元测试期间着重从下述5个方面对模块进行测试。
1.模块接口
首先应该对通过模块接口的数据流进行测试,如果数据不能正确地进出,所有其他测试都是不切实际的。
在对模块接口进行测试时主要检查下述几个方面:参数的数目、次序、属性或单位系统与变元是否一致;是否修改了只作输入用的变元;全局变量的定义和用法在各个模块中是否一致。
2.局部数据结构
对于模块来说,局部数据结构是常见的错误来源。应该仔细设计测试方案,以便发现局部数据说明、初始化、默认值等方面的错误。
3.重要的执行通路
由于通常不可能进行穷尽测试,因此,在单元测试期间选择最有代表性、最可能发现错误的执行通路进行测试就是十分关键的。应该设计测试方案用来发现由于错误的计算、不正确的比较或不适当的控制流而造成的错误。
4.出错处理通路
好的设计应该能预见出现错误的条件,并且设置适当的处理错误的通路,以便在真的出现错误时执行相应的出错处理通路或干净地结束处理。不仅应该在程序中包含出错处理通路,而且应该认真测试这种通路。当评价出错处理通路时,应该着重测试下述一些可能发生的错误:
(1) 对错误的描述是难以理解的;
(2) 记下的错误与实际遇到的错误不同;
(3) 在对错误进行处理之前,错误条件已经引起系统干预;
(4) 对错误的处理不正确;
(5) 描述错误的信息不足以帮助确定造成错误的位置。
5. 边界条件
边界测试是单元测试中最后的也可能是最重要的任务。
软件常常在它的边界上失效。
例如,处理n元数组的第n个元素时,或做到i次循环中的第i次重复时,往往会发生错误。使用刚好小于、刚好等于和刚好大于最大值或最小值的数据结构、控制量和数据值的测试方案,非常可能发现软件中的错误。
单元测试的局限性
单元测试不能捕获程序中的每一个错误。根据定义,单元测试只测试单元自身的功能。
因此它不捕获集成错误、性能问题或其它任何系统范围的问题。
另外,要预料现实中被测程序可能接受到的输入的所有特殊情况是一项不易之事。
对于任何非平凡的软件块要测试所用的输入组合是不现实的。
Junit是什么
JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试。
Junit好处及作用
好处:
1.可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。
2.启动后,自动化测试,并判断执行结果, 不需要人为的干预。
3.只需要查看最后结果,就知道整个项目的方法接口是否通畅。
4.每个单元测试用例相对独立,由Junit 启动,自动调用。不需要添加额外的调用语句。
5.添加,删除,屏蔽测试方法,不影响其他的测试方法。 开源框架都对JUnit 有相应的支持。
作用:
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,之后再编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个main()方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,使用Junit后,可以非常简单地组织测试代码,并随时运行它们,Junit就会给出成功的测试和失败的测试,还可以生成测试报告。
Junit使用规范
一是单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试;
二是每个单元测试应当互相独立,不依赖运行的顺序;
三是测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0,null,空字符串""等情况。
Junit断言
使用JUnit进行单元测试,我们可以使用断言(Assertion)来测试期望结果,即使用断言方法判断期望值和实际值差异,返回Boolean值。这样一来,可以方便地组织和运行测试,并方便地查看测试结果。
Junit所有的断言都包含在 Assert 类中。
Assert 类中的一些有用的方法列式如下:
void assertEquals(boolean expected, boolean actual):检查两个变量或者等式是否平衡
void assertTrue(boolean expected, boolean actual):检查条件为真
void assertFalse(boolean condition):检查条件为假
void assertNotNull(Object object):检查对象不为空
void assertNull(Object object):检查对象为空
void assertSame(boolean condition):assertSame() 方法检查两个相关对象是否指向同一个对象
void assertNotSame(boolean condition):assertNotSame() 方法检查两个相关对象是否不指向同一个对象
void assertArrayEquals(expectedArray, resultArray):assertArrayEquals() 方法检查两个数组是否相等
Junit注解
Java注解(Annotation)的使用方法是”@ + 注解名” 。借助注解,我们可以在编程中通过简单的注解来实现一些功能。
JUnit要求对核心测试方法加上@Test注解,它会把带有@Test的方法识别为测试方法。除此之外,还有以下几个比较常用的注解。
@Before:有些测试在运行前需要创造几个相似的对象。在 public void 方法加该注释是因为该方法需要在 test 方法前运行。
@After:如果你将外部资源在 Before 方法中分配,那么你需要在测试运行后释放他们。在 public void 方法加该注释是因为该方法需要在 test 方法后运行。
@BeforeClass:在 public void 方法加该注释是因为该方法需要在类中所有方法前运行。
@AfterClass:它将会使方法在所有测试结束后执行。这个可以用来进行清理活动。
@Ignore:这个注释是用来忽略有关不需要执行的测试的。
Junit加注解执行过程:
beforeClass(): 方法首先执行,并且只执行一次。
afterClass():方法最后执行,并且只执行一次。
before():方法针对每一个测试用例执行,但是是在执行测试用例之前。
after():方法针对每一个测试用例执行,但是是在执行测试用例之后。
在 before() 方法和 after() 方法之间,执行每一个测试用例。
如何编写Junit测试
首先,我们将介绍一个测试类Calculator.java。
//Calculator.java
public class Calculator {
public int add(int a,int b){
return a + b;
}
}
如上方代码所示,Calculaor类有一个公共的方法add(), 它接收输入两个整数,将它们相加并返回结果。在这里,我们将测试这个方法。具体如何来做呢?
首先,创建一个测试类CalculatorTest.java。引入junit相关包,借助junit注解和断言来实现对add方法的测试,代码如下:
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class CalculatorTest {
//引入Calculator对象
Calculator calcu = new Calculator();
/*
请在下面的Begin/End内写一个测试函数,
来验证Calculator中的add函数编写是否正确
*/
/***********************Begin**************************/
@Test
public void testAdd(){
int res = calcu.add(3,4);
int actualres = 7;
assertEquals(res,actualres);
}
/************************End***************************/
}
来看一下上方的代码。首先,我们可以看到,有一个@Test的注解在 testAdd()方法的上方。 这个注解指示该方法所附着的代码可以做为一个测试用例。因此,testAdd()方法可用于测试公开方法 add()。我们再观察一个方法 assertEquals(res, actualres)。 该方法的作用是判断预期输出与实际输出是否相等。
编程要求
本关的编程任务是补全CalculatorTest类中的代码,对Calculator类进行单元测试。Calculator类是一个简易的计算器,实现了基础的加减乘除功能。同学们需要仿照上方的代码,在CalculatorTest类中补全测试函数testAdd, testSub, testMultiply, testDivide, testDivideByZero分别对计算器的加、减、乘、除功能进行测试。
注:每个测试方法各设计1个测试样例,除操作需考虑除数为0的情况。
本关涉及的Calculator类代码如下所示:
//Calculator.java
public class Calculator {
//加
public int add(int a,int b){
return a + b;
}
//减
public int sub(int a,int b){
return a - b;
}
//乘
public int multiply(int a,int b){
return a * b;
}
//除
public int divide(int a,int b) throws Exception {
if(b == 0)
{
throw new Exception("除数不能为0");
}
return a / b;
}
}
测试说明
同学们需要仿照上一小节《如何编写Junit测试》中的代码,对右侧代码中的每一个测试函数testAdd, testSub, testMultiply, testDivide, testDivideByZero分别设计一个测试样例,补全测试函数。其中,除数为0的情况在测试函数testDivdeByZero中体现。
本关卡的测试文件已进行封装,学生不可见,用于验证学生的Junit测试代码是否编写正确。
具体测试过程如下:
1.平台自动编译生成TestRunner.exe;
2.平台运行TestRunner.exe;
3.获取TestRunner.exe输出,并将其输出与预期输出对比:如果一致则测试通过,否则测试失败。
开始你的任务吧,祝你成功!
答案
//CalculatorTest.java
package step1;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import step1.Calculator;
public class CalculatorTest {
//引入Calculator对象
Calculator calcu = new Calculator();
/*
请在下面的Begin/End内补全测试函数,
来验证Calculator中的add、sub、multiply、divide函数编写是否正确
*/
/***********************Begin**************************/
@Test
public void testAdd(){
int res = calcu.add(3,4);
int actualres =7;
assertEquals(res,actualres);
}
/************************End***************************/
}
花200金币的结果
第二关:
任务描述
本关任务:使用Junit参数化测试,对简易计算器类(Calculator.java)进行单元测试。
相关知识
为了完成本关任务,你需要掌握:
1.为什么需要参数化测试
2.Junit参数化测试流程
3.如何编写参数化测试代码
为什么需要参数化测试
经过上一小节的学习,我们已经学会使用Junit注解和断言来编写简单的单元测试代码。我们在每一个测试方法中都各设计了一个测试用例。但是,假如我们想对每个测试方法设计多个测试用例,代码就会变得冗余,比较麻烦。那么,有没有什么好办法解决这个问题呢?
Junit参数化测试(Parameterized tests)很好的解决了这个问题。如果测试代码大同小异,代码结构都是相同的,不同的只是测试的数据和预期值,那么Junit的参数化测试可以派上用场了——它允许使用不同的参数多次运行同一个测试。
Junit参数化测试流程
我们遵循以下5个步骤来创建参数化测试。
1.用 @RunWith(Parameterized.class) 来注释 test 类。
2.创建一个由 @Parameters 注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合。
3.创建一个公共的构造函数,它接受和一行测试数据相等同的东西。
4.为每一列测试数据创建一个实例变量。
5.用实例变量作为测试数据的来源来创建你的测试用例。
在JUnit中,可以使用@RunWith 和@Parameters这两个注解来为单元测试传递参数。
@RunWith注解:当类被@RunWith注解修饰,或者类继承了一个被该注解修饰的类,JUnit将会使用这个注解所指明的运行器(runner)来运行测试。
@Parameters注解:然后在该类提供数据的方法上加上一个@Parameters注解,这个方法必须是静态static的,不能带参数,并且返回一个集合Collection。
如何编写参数化测试代码
我们通过一个具体示例来演示如何编写参数化测试代码。
我们继续使用上一关卡中计算器类中的add方法作为案例教学。
代码如下:
//Calculator.java
public class Calculator {
//加
public int add(int a,int b){
return a + b;
}
//减
public int sub(int a,int b){
return a - b;
}
//乘
public int multiply(int a,int b){
return a * b;
}
//除
public int divide(int a,int b) throws Exception {
if(b == 0)
{
throw new Exception("除数不能为0");
}
return a / b;
}
}
为其创建一个参数化测试类CalculatorTest2.java。
代码如下:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
//参数化测试类需用@RunWith注解修饰
@RunWith(Parameterized.class)
public class CalculatorTest2 {
private int a;
private int b;
private int expect;
@Parameterized.Parameters
//prepareData方法会将多个测试用例作为一个Collection返回
//Collection中的每个元素都会调用一遍CalculatorTest2类的构造方法
//每调用一次构造方法,都会产生一个类对象,每个类对象都会去执行testAdd()方法
//这样一来,便实现了使用不同的测试用例运行同一个测试方法的功能
public static Collection prepareData(){
Object [][] object = {{1,2,3},{-1,0,-1},{0,0,0},{-1,-2,-3}};
return Arrays.asList(object);
}
public CalculatorTest2(int a,int b,int expect){
this.a = a;
this.b = b;
this.expect = expect;
}
@Test
public void testAdd(){
Calculator calcu = new Calculator();
int res = calcu.add(a,b);
assertEquals(expect,res);
}
}
编程要求
仿照上面的方法,使用Junit参数化测试对计算器类中的multiply方法进行测试,依据要求在题目中的相应位置补全代码。
测试说明
平台会对你编写的代码进行测试:
开始你的任务吧,祝你成功!
答案
package step2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
//参数化测试类需用@RunWith注解修饰
@RunWith(Parameterized.class)
public class CalculatorTest2 {
private int a;
private int b;
private int expect;
@Parameterized.Parameters
//prepareData方法会将多个测试用例作为一个Collection返回
//Collection中的每个元素都会调用一遍CalculatorTest2类的构造方法
//每调用一次构造方法,都会产生一个类对象,每个类对象都会去执行testAdd()方法
//这样一来,便实现了使用不同的测试用例运行同一个测试方法的功能
public static Collection prepareData(){
//请仿照左侧的示例代码,在Begin/End区域内补全代码
//要求:构造4组测试用例,对Calculator中的multiply方法进行测试
/***********************Begin**************************/
Object [][] object = {{1,2,2},{-1,0,0},{0,0,0},{-1,-2,2}};
return Arrays.asList(object);
/************************End***************************/
}
public CalculatorTest2(int a,int b,int expect){
this.a = a;
this.b = b;
this.expect = expect;
}
@Test
public void testMultiply() {
Calculator calcu = new Calculator();
int res = calcu.multiply(a,b);
assertEquals(expect,res);
}
}
关键
更多推荐
所有评论(0)