单元测试利器 JUnit 4


JUnit 的最佳实践    
         您打算把单元测试代码放在什么地方呢?把它和被测试代码混在一起,这显然会照成混乱,因为单元测试代码是不会出现在最终产品中的。建议您分别为单元测试代码与被测试代码创建单独的目录,并保证测试代码和被测试代码使用相同的包名。这样既保证了代码的分离,同时还保证了查找的方便。一般建议测试的源码包名为testsrc 现在我们得到了一条 JUnit 的最佳实践:单元测试代码和被测试代码使用一样的包,不同的目录。
JUnit 4 的新功能
借助 Java 5 注释,JUnit 4 比从前更轻(量级),也更加灵活。JUnit 4 放弃了严格的命名规范和继承层次,转向了一些令人激动的新功能。下面是一份关于 JUnit 4 新功能的快速列表:
1.参数测试
2.异常测试
3.超时测试
4.灵活固件
5.忽略测试的简单方法
6.对测试进行逻辑分组的新方法
JUnit4新功能体验
首先我们需要构造一个被测试类
package jtest;
public class Calculator {
private static int result; // 静态变量,用于存储运行结果
public void add(int n) {
  result = result + n;
}
public void substract(int n) {
  result = result - n; //Bug: 正确的应该是 result =result-n
}
public void multiply(int n) {
} // 此方法尚未写好
public void divide(int n) {
  result = result / n;
}
public void square(int n) {
  result = n * n;
}
public void squareRoot(int n) {
  for (;;)
   ; //Bug : 死循环
}
public void clear() { // 将结果清零
  result = 0;
}
public int getResult() {
  return result;
}
}
体验1:测试的时候不需要以前那么复杂,一些都那么的简单
@Test
public void testAdd() {
calculator.add(2);
calculator.add(3);
assertEquals(5, calculator.getResult());
}
这样就可以完成一个完整的add方法的测试而且test这个前缀不是必须,如果你用add没有什么区别
note:
1.测试方法必须使用注解 org.junit.Test 修饰。
2.测试方法必须使用 public void 修饰,而且不能带有任何参数
记住:您的单元测试代码不是用来证明您是对的,而是为了证明您没有错。因此单元测试的范围要全面,比如对边界值、正常值、错误值得测试;
对代码可能出现的问题要全面预测,而这也正是需求分析、详细设计环节中要考虑的。
如果你的测试失败,将不会出现绿色的进步条
JUnit 将测试失败的情况分为两种:failure 和 error。Failure 一般由单元测试使用的断言方法判断失败引起,它表示在测试点发现了问题;
而 error 则是由代码异常引起,这是测试目的之外的发现,它可能产生于测试代码本身的错误(测试代码也是代码,同样无法保证完全没有缺陷),
也可能是被测试代码中的一个隐藏的bug
体验2 Fixture
何谓 Fixture?它是指在执行一个或者多个测试方法时需要的一系列公共资源或者数据,例如测试环境,测试数据等等。
在编写单元测试的过程中,您会发现在大部分的测试方法在进行真正的测试之前都需要做大量的铺垫——为设计准备 Fixture 而忙碌。
这些铺垫过程占据的代码往往比真正测试的代码多得多,而且这个比率随着测试的复杂程度的增加而递增。
当多个测试方法都需要做同样的铺垫时,重复代码的“坏味道”便在测试代码中弥漫开来。这股“坏味道”会弄脏您的代码,还会因为疏忽造成错误,应该使用一些手段来根除它。
JUnit 专门提供了设置公共 Fixture 的方法,同一测试类中的所有测试方法都可以共用它来初始化 Fixture 和注销 Fixture。
和编写 JUnit 测试方法一样,公共 Fixture 的设置也很简单,您只需要:
1.使用注解 org,junit.Before 修饰用于初始化 Fixture 的方法。
2.使用注解 org.junit.After 修饰用于注销 Fixture 的方法。
3.保证这两种方法都使用 public void 修饰,而且不能带有任何参数。
遵循上面的三条原则,编写出的代码大体是这个样子:
//初始化Fixture方法
@Before
public void init(){……}
//注销Fixture方法
@After
public void destroy(){……}
这样,在每一个测试方法执行之前,JUnit 会保证 init 方法已经提前初始化测试环境,而当此测试方法执行完毕之后,JUnit 又会调用 destroy 方法注销测试环境。
注意是每一个测试方法的执行都会触发对公共 Fixture 的设置,也就是说使用注解 Before 或者 After 修饰的公共 Fixture 设置方法是方法级别的。
这样便可以保证各个独立的测试之间互不干扰,以免其它测试代码修改测试环境或者测试数据影响到其它测试代码的准确性。

可是,这种 Fixture 设置方式还是引来了批评,因为它效率低下,特别是在设置 Fixture 非常耗时的情况下(例如设置数据库链接)。
而且对于不会发生变化的测试环境或者测试数据来说,是不会影响到测试方法的执行结果的,也就没有必要针对每一个测试方法重新设置一次 Fixture。
因此在 JUnit 4 中引入了类级别的 Fixture 设置方法,编写规范如下:
1.使用注解 org,junit.BeforeClass 修饰用于初始化 Fixture 的方法。
2.使用注解 org.junit.AfterClass 修饰用于注销 Fixture 的方法。
3.保证这两种方法都使用 public static void 修饰,而且不能带有任何参数。
类级别的 Fixture 仅会在测试类中所有测试方法执行之前执行初始化,并在全部测试方法测试完毕之后执行注销方法。代码范本如下:
//类级别Fixture初始化方法
@BeforeClass
public static void dbInit(){……}

//类级别Fixture注销方法
@AfterClass
public static void dbClose(){……}
体验三:异常测试
注解 org.junit.Test 中有两个非常有用的参数:expected 和 timeout。参数 expected 代表测试方法期望抛出指定的异常,如果运行测试并没有抛出这个异常,
则 JUnit 会认为这个测试没有通过。这为验证被测试方法在错误的情况下是否会抛出预定的异常提供了便利。
举例来说,方法 divide 如果参数为0就会抛出ArithmeticException异常测试方法 divide除0是否会抛出指定异常的单元测试方法大体如下:
@Test(expected=ArithmeticException.class)
public void divide(){
  ……
}
体验四:时间测试
注解 org.junit.Test 的另一个参数 timeout,指定被测试方法被允许运行的最长时间应该是多少,如果测试方法运行时间超过了指定的毫秒数,
则JUnit认为测试失败。这个参数对于性能测试有一定的帮助。
@Test(timeout=1000)
public void testSubstract(){
  ……
}
体验五:忽略测试方法
JUnit 提供注解 org.junit.Ignore 用于暂时忽略某个测试方法,因为有时候由于测试环境受限,并不能保证每一个测试方法都能正确运行。
如果暂时还没有实现 或者缺少某些资源
@Ignore("Multiply() Not yet implemented")
public void testMultiply() {
}
但是一定要小心。注解 org.junit.Ignore 只能用于暂时的忽略测试,如果需要永远忽略这些测试,一定要确认被测试代码不再需要这些测试方法,以免忽略必要的测试点。
体验六:测试运行器
又一个新概念出现了——测试运行器,JUnit 中所有的测试方法都是由它负责执行的。JUnit 为单元测试提供了默认的测试运行器,但 JUnit 并没有限制您必须使用默认的运行器。
相反,您不仅可以定制自己的运行器(所有的运行器都继承自 org.junit.runner.Runner),而且还可以为每一个测试类指定使用某个具体的运行器。
指定方法也很简单,使用注解 org.junit.runner.RunWith 在测试类上显式的声明要使用的运行器即可:
@RunWith(CustomTestRunner.class)
public class CalculatorTest {
……
}
显而易见,如果测试类没有显式的声明使用哪一个测试运行器,JUnit 会启动默认的测试运行器执行测试类(比如上面提及的单元测试代码)。
一般情况下,默认测试运行器可以应对绝大多数的单元测试要求;当使用 JUnit 提供的一些高级特性(例如即将介绍的两个特性)或者针对特殊需求定制 JUnit 测试方式时,
显式的声明测试运行器就必不可少了。
体验七:测试套件
在实际项目中,随着项目进度的开展,单元测试类会越来越多,可是直到现在我们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的。
为了解决这个问题,JUnit 提供了一种批量运行测试类的方法,叫做测试套件。这样,每次需要验证系统功能正确性时,只执行一个或几个测试套件便可以了。
测试套件的写法非常简单,您只需要遵循以下规则:
创建一个空类作为测试套件的入口。
使用注解 org.junit.runner.RunWith 和 org.junit.runners.Suite.SuiteClasses 修饰这个空类。
将 org.junit.runners.Suite 作为参数传入注解 RunWith,以提示 JUnit 为此类使用套件运行器执行。
将需要放入此测试套件的测试类组成数组作为注解 SuiteClasses 的参数。
保证这个空类使用 public 修饰,而且存在公开的不带有任何参数的构造函数。
@RunWith(Suite.class)
@Suite.SuiteClasses({
        CalculatorTest.class,
        SquareTest.class
        })
public class AllCalculatorTests {
}
上例代码中,我们将前文提到的测试类 CalculatorTest 放入了测试套件 AllCalculatorTests 中,在 Eclipse 中运行测试套件,
可以看到测试类 CalculatorTest 被调用执行了。测试套件中不仅可以包含基本的测试类,而且可以包含其它的测试套件,这样可以很方便的分层管理不同模块的单元测试代码。
但是,您一定要保证测试套件之间没有循环包含关系,否则无尽的循环就会出现在您的面前……。
体验八:参数化测试
参数化测试的编写稍微有点麻烦(当然这是相对于 JUnit 中其它特性而言):
为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized。
为测试类声明几个变量,分别用于存放期望值和测试所用数据。
为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。
为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。
编写测试方法,使用定义的变量作为参数进行测试。
@RunWith(Parameterized.class)
public class SquareTest {
private static Calculator calculator = new Calculator();
private int param;
private int result;
@Parameters
public static Collection<Object[]> data() {
  return Arrays.asList(new Object[][] {
  { 2, 4 },
  { 0, 0 },
  { -3, 9 },
  });
}
//构造函数,对变量进行初始化
public SquareTest(int param, int result) {
  this.param = param;
  this.result = result;
}
@Test
public void square() {
  calculator.square(param);
  assertEquals(result, calculator.getResult());
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值