为生产高质量的代码,我们需要采取步骤以确保代码的功能正确性。我们必须为要实现的每个方法书写详尽的规范。对于每个方法,规范包括输入是什么(例如,参数和它们期望的值),输出是什么(返回值和副作用)。一旦一个方法或者一组方法被实现,我们必须进行恰当的测试。单元测试是一种正规技术,通过编写和运行一组单元测试,来确保代码的功能正确性。实践中,每次代码被修改,我们都要运行对应的单元测试,测试通过我们才能发布代码。
在本实验中,我们将使用JUnit工具编写和执行一组测试。 我们已经提供了一个规范,也实现了一组类。你的任务是为这些类中的一个编写一组测试,发现我们实现中的bugs,并修复。
完成本实验后,你应该能够:
- 阅读和理解方法级的规范
- 阅读和理解之前写的代码
- 创建JUnit单元测试
- 利用JUnit单元测试发现bugs并最终修复和确保方法的功能正确性
- 纠正代码中的所有bugs,以使所有测试成功通过
检查EclEmma插件是否已经安装:
- Help菜单: 选 About Eclipse Platform / Installation Details
- 你会看到已经安装的插件的列表,如果EclEmma在列表中,表示你已经完成本步骤。
如果没有安装EclEmma插件,则安装:
- Help菜单,选择Install new software
- Work with输入框:输入http://update.eclemma.org
- 从列表中选择EclEmma,点击Next
- 阅读和接受授权协议,点击Next
创建一个称为lab2的新项目
下载lab 2实现,并解压到你的本地文件系统。
将lab 2导入Eclipse工作区:
- 在Eclipse中,选择你的lab2项目
- 选择File/Import
- 选择General/File System,点击Next
- 从From Directory:导航到包含解压的lab2目录,再到src目录,点击OK。
- 选中所有Java文件的选择框。
- Into folder:导航到你的新项目,再到src目录,点击OK。
- 点击Finish。
将JUnit导入项目
- 右击lab2并选properties
- 选中Java Build Path
- 点击Libraries面板
- 如果项目被导入了:你需要选择JUnit4并点选Remove
- 点击Add Library
- 选择JUnit,点击Next
- 选择最新版本(JUnit 4)
- 点击Finish
- 点击OK
仔细阅读Item和Inventory类的代码,注意这些代码中有bugs,在本实验中,我们需要找到bugs并修复。
在lab2.zip文件中,作为例子,我们添加了一个ItemTest类:
import org.junit.Test;
import org.junit.Assert;
/**
* Testing class for Item object
*
* @author Taner Davis
* @version 20160827
*/
public class ItemTest
{
/**
* Test the empty Item constructor
*/
@Test
public void emptyItemConstructorTest ( )
{
//Use the default constructor
Item item = new Item ( ) ;
//The name should be null, and the price and weight zero
Assert.assertNull(item.getName());
Assert.assertEquals(0, item.getPrice());
Assert.assertEquals(0, item.getWeight(), 0.01);
}
/**
* Test the Item constructor with only a String parameter
*/
@Test
public void singleParameterConstructorTest()
{
//Use the single−parameter constructor
Item item = new Item ("Portal Gun");
/*
* The name should match its initial parameter,
* the price and weight should be zero
*/
Assert.assertEquals("Portal Gun", item.getName());
Assert.assertEquals(0, item.getPrice());
Assert.assertEquals(0, item.getWeight(), 0.01);
}
/**
* Test the Item constructor using a string parameter
* and a double weight
*/
@Test
public void doubleParameterConstructorTest()
{
//Use the double−parameter constructor
Item item = new Item("Battle Axe", 98.7);
/*
* The name should match its initial parameter,
* the weight should equal its initial value,
* and the price should be zero
*/
Assert.assertEquals("Battle Axe", item.getName());
Assert.assertEquals(0, item.getPrice());
Assert.assertEquals(98.7, item.getWeight(), 0.01);
}
/**
* Test full constructor and the getters
*/
@Test
public void fullConstructorTest()
{
//Use full constructor
Item item = new Item("Whip", 10.1, 80);
/*
* The name should match its initial parameter,
* and the weight and price should equal their
* initial values
*/
Assert.assertEquals(80, item.getPrice());
Assert.assertEquals(10.1, item.getWeight(), 0.01);
Assert.assertTrue(item.getName().equals("Whip"));
}
/**
* Test all mutator methods
*/
@Test
public void allMutatorsTest()
{
// Use full constructor
Item item = new Item ("Change This", 999.9, 999);
// Set name, price, and weight properties
item.setName("Scythe");
item.setPrice(125);
item.setWeight(38.3);
/*
* The name should match the parameter passed
* into the mutator methods , and the weight and
* price should equal the values passed to their
* respective mutator methods.
*/
Assert.assertEquals(125, item.getPrice());
Assert.assertEquals(38.3, item.getWeight(), 0.01);
Assert.assertTrue(item.getName().equals("Scythe"));
}
/**
* Test the String representation of an Item
*/
@Test
public void itemToStringTest()
{
Item item = new Item("MegaBuster", 27.9, 500);
Assert.assertEquals("MegaBuster, 27.9 Fantasy Units, 500 Gold\n", item.toString());
}
}
注意,根据你的操作系统和Java安装的不同,测试类顶部的导入行可能不同。如果Assert类没有定义,那么简单的做法 是先删除这两行导入语句。然后,将鼠标移动到未定义的Assert应用上,Eclipse会提示你导入正确的类。
一个单元测试文件也是一个独立的类,包含一个或者多个方法(通常遵循一种命名惯例,类似addMutatorsTest
,
singleParameterConstructorTest
,等等)。每个方法被打上一个@test
标注, 告诉编译器将该方法设置为要被执行的一个测试。
每个单元测试包含三个代码块:
- 创建一组用于测试的对象
- 调用要被测试的方法,通常要存储结果
- 一组测试方法调用返回结果的断言。每个断言表明如果代码执行正确的话必须满足的条件,一个典型的测试由多个断言构成。
在ItemTest.java的*fullConstructorTest()*中,一个item对象被创建,名称"Whip",高度和价格(金币)分别是10.1和80,。测试方法确保在对象被构造期间所有属性被正确设置。例如:
Assert.assertEquals(10.1, item.getWeight(), 0.01);
上面的测试代码通过getWeight
访问器方法查询对象的方法,并和期望值10.1比较。注意这里不能简单测试两个double值相等,而应该用支持double版本的assertEquals()
方法 - 比较两个值的差值小于0.01。如果比较为真,那么断言就pass。相反,如果两个的差值大于0.01,那么测试会失败。
assertTrue()
可以测试任意的条件,例如:
Assert.assertTrue(item.getName().equals("Master Sword"));
要求name必须和"Master Sword"完全相等(String.equals()
需要比较的字符串完全相等才返回真)。本文最后有Assert类的Java官方文档链接。
在Eclipse中,你可以通过点击(在上面的工具面板上)然后选择相应的单元测试运行。一个JUnit窗口面板会出现在界面的左边,并展示测试是成功还是失败。如果一个测试失败,你可以通过点击它以进一步查看哪一行导致了失败。失败表明你的实现类中(或者测试本身)有bug。
EclEmma会将被测试过的代码高亮,表明单元测试的覆盖情况。特别的,Eclipse会相应的高亮每一行代码:
- 绿色:该行已经被一个或者多个测试覆盖到
- 红色:该行没有被覆盖到
- 黄色:仅有部分分支(if, while, for, switch)被测试覆盖到 你的目标是让所有的类被测试覆盖并以绿色高亮。
当你写测试的时候,你不应该依赖实现来生成预期的结果,相反,你应该通过手算推导出期望值,这样,你的测试可以独立于实现。同时,编写方法实现前先写测试是一个好的实践。
该实验中你的任务是为Inventory类中的方法编写一组测试,过程如下:
创建一个新的单元测试类:
- 在package explorer中,右击Inventory.java
- 选择New/JUnit Test Case
- 选择New JUnit 4 test
- 源目录应该是lab2/src
- 名称应当是InventoryTest
- 被测试类应当是Inventory
- 点击Finish,这会创建并打开一个称为InventoryTest的新类
写一组测试以确保Inventory类的所有方法功能正确。注意在这些测试中,你需要至少创建一个Inventory对象,填充具有各种名称,高度和价格的Item对象。你编写的这些测试要覆盖类方法中的所有情况。也就是说,你要测试代码的所有可能路径(例如,每个if和else分支)。
在你执行测试时,你会发现Inventory类中的几个错误。修复这些错误,使用单元测试确保所有bugs已经被修复。
使用Eclipse生成Javadoc
- 选择Project/Generate Javadoc...
- 确保你的项目被选中(包含Driver, Item和Inventory类)
- 选择Private可见性
- 使用缺省的目标目录
- 点击Finish
在Eclipse或者你常用的浏览器中打开lab2/doc/index.html文件。 确保Javadoc中包含你的类,所有的方法包含必要的Javadoc文档。