JUnit4在Eclipse中的使用

时间:2024-06-08 22:06:32

测试是项目开发中很重要的一环。实际上,建议的项目前期编写过程是:构思-> 编写单元测试代码-> 编写接口->编写实现类-> 测试实现类->编写主类...。JUnit是一个使用广泛的、用于编写和运行可重复的测试的Java测试框架。这里不多介绍背景,直接说用法了。

1.JUnit测试用例的创建

首先,在pom.xml(Maven)中添加依赖:

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

然后,在src/test/java下的包中新建>JUnit测试用例,在下图所示界面中选择“新建JUnit4测试”(1),填写类名(2)和要测试的类(3),选择要创建的方法存根(4),点击下一步:

JUnit4在Eclipse中的使用

这里介绍一下可选的方法存根(4):

  • setUpBeforClass():注解@BeforeClass,在运行测试类的最开始运行一次,用作测试准备(读取文件、链接数据库等)。
  • tearDownAfterClass():注解@AfterClass,在运行测试类的最后运行一次,用作测试清理(释放资源)。
  • setUp():注解@Befor,在运行每个要测试方法前运行,有多少个要测试的方法就运行多少次,用作测试方法准备。
  • tearDown():注解@After,在运行每个要测试方法后运行,有多少个要测试的方法就运行多少次,用作测试方法清理。

最后,选择要测试的方法,点击完成:

JUnit4在Eclipse中的使用

在生成的测试类中,会对每个选中的方法生成一个测试方法(testXxx)。如果选中了“创建终态方法存根”,则生成的测试方法都带有final修饰符;如果选中了“为生成的测试方法创建任务”,则生成的测试方法中都会出现//TODO标识。

2.测试类的编写

(1)JUnit4中的注解

  • @BeforeClass:该方法在所有测试方法运行之前,运行一次。方法必须是public static void。
  • @AfterClass:该方法在所有测试方法运行完毕后,运行一次。方法必须是public static void。
  • @Before:该方法在每个@Test运行之前都要运行。方法必须是public void。
  • @After:该方法在每个@Test运行之后都要运行。方法必须是public void。
  • @Test:表示该方法是测试方法。方法必须是public void。@Test可以有两个参数:expected,timeout,分别对应异常测试和超时测试。
  • @Ignore:对测试类或@Test测试方法使用,表示暂时忽略该测试。JUnit执行结果会报告忽略数量。

通过步骤1生成的测试类,会在setUpBeforeClass、tearDownAfterClass、setUp、tearDown、testXxx方法前自动添加相应注解。

(2)Assert类

Assert类是JUnit中提供的一个工具类,这个类中有大量的静态方法进行断言的处理。在各测试方法中,在调用要测试的方法后,通过Assert类中的方法判断运行结果是否符合预期。

一般为了方便使用Assert类中的静态方法,在测试类中对Assert类中的方法进行静态导入:

import static org.junit.Assert.*;

这里简单介绍几种Assert类中的常见方法(细节详见Assert(JUnit API)):

  • assertArrayEquals((错误信息,)预期结果数组,测试结果数组):断言测试结果数组与预期结果数组相等。如果不相等,抛出AssertionError,表示测试未通过。如果存在信息参数,则抛出带指定信息的AssertionError。
  • assertEquals((错误信息,)预期结果,测试结果):断言测试结果==预期结果。如果不相等,处理方式同上。
  • assertNotEquals((错误信息,)不期望的结果,测试结果):断言测试结果不等于不期望的结果。如果相等,处理方式同上。
  • assertNull((错误信息,)Object):断言对象为空。如果不为空,处理方式同上。
  • assertNotNull((错误信息,)Object):断言对象不为空。如果为空,处理方式同上。
  • assertSame((错误信息,)期望Object,测试Object):断言测试Object和期望Object引用了同一对象。如果引用了不同对象,处理方式同上。
  • assertNotSame((错误信息,)不期望的Object,测试Object):断言测试Object引用的不是不期望的Object。如果引用同一对象,处理方式同上。
  • assertTrue((错误信息,)boolean):断言判定条件(boolean)为true。如果为false,处理方式同上。
  • assertFalse((错误信息,)boolean):断言判定条件(boolean)为false。如果为true,处理方式同上。
  • assertThat((错误信息,)结果参数T,Matcher<? super T>):断言结果参数满足Matcher的条件。如果不满足,抛出带有Matcher和结果信息的AssertionError。如果存在信息参数,则抛出的AssertionError还带有错误信息。
  • fail((识别信息)):使测试不通过。如果存在信息参数,则为测试不通过加上识别信息。

当比较对象为double或float时,应加上对应类型的delta参数,即:判定比较对象相等时的最大误差。

(3)测试方法的编写

对于每个测试方法,一般都是先调用要测试的方法,然后通过Assert中的方法,判断测试结果是否符合预期。

对于每个要测试的方法,可以有3个测试方法:运行结果测试、异常测试、性能测试。三者的功能分别是测试指定方法运行结果是否符合预期、有问题时是否能正确抛出异常、能否在限定时间内完成运行,参数分别是@Test、@Test(expected=XxxException.class)、@Test(timeout=xxx)。可以根据实际需求,选择编写其中的1~3个测试方法。

注意,每个test方法中最好只测试被测类中的一种方法,尽量不要在一个test方法中调用两个以上的被测方法。

节选示例:测试向数据库添加学生信息的addStu方法和通过专业学号查询数据的query...方法。

被测试方法:

    public StudentDAOImpl() {
// TODO 自动生成的构造函数存根
apc = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = (JdbcTemplate) apc.getBean("jdbcTemplate");
} public boolean addStu(Student stu) {
// TODO 自动生成的方法存根
String name = stu.getName(), major = stu.getMajor();
int jnshuId = stu.getJnshuId();
if (name==null || major==null || jnshuId==0) {
throw new RuntimeException("姓名、专业、学号不能为空!");
}
String sql = "INSERT INTO students (name,qq,major,entrytime,gra_school,id_jnshu"
+ ",daily_url,desire,bro_jnshu,knowfrom) VALUES (?,?,?,?,?,?,?,?,?,?)";
int line = jdbcTemplate.update(sql,new Object[] {
name,stu.getQq(),major,stu.getEntryTime(),
stu.getSchool(),jnshuId,stu.getDailyUrl(),stu.getDesire(),
stu.getJnshuBro(),stu.getKnowFrom()});
return line>0?true:false;
} public Student queryStuByJnshu(String major, int jnshuId) {
// TODO 自动生成的方法存根
String sql = "SELECT id,create_at,update_at,name,qq,major,entrytime,gra_school,id_jnshu" +
",daily_url,desire,bro_jnshu,knowfrom FROM students WHERE id_jnshu=? and major=?";
Student stu = null;
try {
stu = jdbcTemplate.queryForObject(sql, new QueryStuRowMapper(),new Object[]{jnshuId,major});
} catch (EmptyResultDataAccessException e) {
// TODO 此处可做更复杂的提示动作,比如抛出异常、记录到本地文件、显示到GUI等。
System.out.println("该学生不存在!");
}
return stu;
}

其中,QueryStuRowMapper代码如下:

package cn.cage.student;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper; public class QueryStuRowMapper implements RowMapper<Student> {
/**
* 直接使用此类建立的对象时,sql语句应查询Student的所有属性。*/
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
// TODO 自动生成的方法存根
Student stu = new Student(rs.getString("name"), rs.getString("major"), rs.getInt("id_jnshu"));
stu.setId(rs.getLong("id"));
stu.setCreateTime(rs.getLong("create_at"));
stu.setUpdateTime(rs.getLong("update_at"));
stu.setQq(rs.getString("qq"));
// TODO 此处根据测试结果(时间格式)看是否需要修改
stu.setEntryTime(rs.getDate("entrytime").toString());
stu.setSchool(rs.getString("gra_school"));
stu.setDailyUrl(rs.getString("daily_url"));
stu.setDesire(rs.getString("desire"));
stu.setJnshuBro(rs.getString("bro_jnshu"));
stu.setKnowFrom(rs.getString("knowfrom"));
return stu;
}
}

测试方法:

    @BeforeClass
public static void setUpBeforeClass() throws Exception {
stuDao = new StudentDAOImpl();
} @AfterClass
public static void tearDownAfterClass() throws Exception {
stuDao = null;
} @Test
public void testAddStu() {
Student stu = new Student("王五", "Java后端工程师", 1470);
assertTrue("插入失败!",stuDao.addStu(stu));
} @Test
public void testQueryStuByJnshu() {
Student stu = new Student("王五", "Java后端工程师", 1470);
Student actual = stuDao.queryStuByJnshu("Java后端工程师", 1470);
assertEquals("插入错误,或查询方法(byJnshu)有问题!",stu, actual);
}

3.运行测试

编写完测试类和被测试类后,在测试类上右键>运行方式>JUnit测试即可。

注意:运行测试的顺序并非是按照函数的先后顺序!如果要修改运行测试的顺序,可以在class上加注解:@FixMethodOrder(*)。可以选择的参数有:

  • MethodSorters.DEFAULT:默认顺序,不可预测但固定的顺序(每次顺序都相同)。
  • MethodSorters.JVM:测试方法执行顺序为JVM返回的顺序,不固定的顺序(每次顺序可能不同)
  • MethodSorters.NAME_ASCENDING:按测试方法名(字典顺序)升序执行。(如果是自定义顺序,推荐这种)

虽然可以改变测试顺序,但一个好的测试类不应该依赖于测试顺序!

示例测试结果:

JUnit4在Eclipse中的使用

插入成功,但查询结果与插入值不同。

找到原因,是Student类中没有equals函数,所以assertEquals比较的是查询结果与插入值是否引用了同一个对象。修改后测试通过。