JUnit4在Eclipse中的使用
测试是项目开发中很重要的一环。实际上,建议的项目前期编写过程是:构思-> 编写单元测试代码-> 编写接口->编写实现类-> 测试实现类->编写主类...。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),点击下一步:
这里介绍一下可选的方法存根(4):
- setUpBeforClass():注解@BeforeClass,在运行测试类的最开始运行一次,用作测试准备(读取文件、链接数据库等)。
- tearDownAfterClass():注解@AfterClass,在运行测试类的最后运行一次,用作测试清理(释放资源)。
- setUp():注解@Befor,在运行每个要测试方法前运行,有多少个要测试的方法就运行多少次,用作测试方法准备。
- tearDown():注解@After,在运行每个要测试方法后运行,有多少个要测试的方法就运行多少次,用作测试方法清理。
最后,选择要测试的方法,点击完成:
在生成的测试类中,会对每个选中的方法生成一个测试方法(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:按测试方法名(字典顺序)升序执行。(如果是自定义顺序,推荐这种)
虽然可以改变测试顺序,但一个好的测试类不应该依赖于测试顺序!
示例测试结果:
插入成功,但查询结果与插入值不同。
找到原因,是Student类中没有equals函数,所以assertEquals比较的是查询结果与插入值是否引用了同一个对象。修改后测试通过。