原文地址 http://www.open-open.com/lib/view/open1462177214142.htmlhtml
咱们写单元测试,通常都会用到一个或多个单元测试框架,在这里,咱们介绍一下JUnit4这个测试框架。这是Java界用的最普遍,也是最基础的一个框架,其余的不少框架,包括咱们后面会看到的Robolectric,都是基于或兼容JUnit4的。然而首先要解决的问题是。。。java
或者换句话说,单元测试框架可以为咱们作什么呢?从最基本的开始提及,假如咱们有这样一个类:android
public class Calculator { public int add(int one, int another) { // 为了简单起见,暂不考虑溢出等状况。 return one + another; } public int multiply(int one, int another) { // 为了简单起见,暂不考虑溢出等状况。 return one * another; } }
若是不用单元测试框架的话,咱们要怎么写测试代码呢?咱们恐怕得写出下面这样的代码:框架
public class CalculatorTest { public static void main(String[] args) { Calculator calculator = new Calculator(); int sum = calculator.add(1, 2); if(sum == 3) { System.out.println("add() works!") } else { System.out.println("add() does not works!") } int product = calculator.multiply(2, 4); if (product == 8) { System.out.println("multiply() works!") } else { System.out.println("multiply() does not works!") } } }
而后咱们再经过某种方式,好比命令行或IDE,运行这个 CalculatorTest 的 main 方法,在看着terminal的输出,才知道测试是经过仍是失败。想一想一下,若是咱们有不少的类,每一个类都有不少方法,那么就要写一堆这样的代码,每一个类对于一个含有 main 方法的test类,同时 main 方法里面会有一堆代码。这样既写起来痛苦,跑起来更痛苦,好比说,你怎么样一次性跑全部的测试类呢?因此,一个测试框架为咱们作的最基本的事情,就是容许咱们按照某种更简单的方式写测试代码,把每个测试单元写在一个测试方法里面,而后它会自动找出全部的测试方法,而且根据你的须要,运行全部的测试方法,或者是运行单个测试方法,或者是运行部分测试方法等等。ide
对于上面的 Calculator 例子,若是使用Junit的话,咱们能够按照以下的方式写测试代码:函数
public class CalculatorTest { @Test public void testAdd() throws Exception { Calculator calculator = new Calculator(); int sum = calculator.add(1, 2); Assert.assertEquals(3, sum); } @Test public void testMultiply() throws Exception { Calculator calculator = new Calculator(); int product = calculator.multiply(2, 4); Assert.assertEquals(8, product); } }
每个被测试的方法( add(), multiply() ),写一个对应的测试方法( testAdd(), testMultiply() )。那JUnit怎么知道那些是测试方法,哪些不是呢?这个是经过前面的 @Test 注解来标志的,只要有这个注解,JUnit4就会当作是一个测试方法,方法名实际上是能够随意起的。固然,名字仍是应该起的更有可读性一点,让人一看就知道,这个测试方法是测试了被测的类的那个方法,或者是测试了那个功能点等等。单元测试
除了帮咱们找出全部的测试方法,而且方便运行意外,单元测试框架还帮咱们作了其余事情。在 这个系列的第一篇文章 中咱们提到,一个测试方法主要包括三个部分:测试
而一个单元测试框架,可让咱们更方便的写上面的每一步的代码,尤为是第一步和第三部。好比说,在上面的 CalculatorTest 中, testAdd() 和 testMultiply() 都有相同的setup: Calculator calculator = new Calculator(); ,若是 Calculator 还有其余的方法的话,这行代码就得重复更屡次,这种duplication是不必的。绝大多数单元测试框架考虑到了这一点,它们知道一个测试类的不少测试方法可能须要相同的setup,因此为咱们提供了便捷方法。对于JUnit4,是经过 @Before 来实现的:spa
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } @Test public void testAdd() throws Exception { int sum = mCalculator.add(1, 2); assertEquals(3, sum); //为了简洁,每每会static import Assert里面的全部方法。 } @Test public void testMultiply() throws Exception { int product = mCalculator.multiply(2, 4); assertEquals(8, product); } }
若是一个方法被 @Before 修饰过了,那么在每一个测试方法调用以前,这个方法都会获得调用。因此上面的例子中, testAdd() 被运行以前, setup() 会被调用一次,把 mCalculator 实例化,接着运行 testAdd() ; testMultiply() 被运行以前, setup() 又会被调用一次,把 mCalculator 再次实例化,接着运行 testMultiply() 。若是还有其余的测试方法,则以此类推。命令行
对应于 @Before 的,有一个 @After ,做用估计你也猜获得,那就是每一个测试方法运行结束以后,会获得运行的方法。好比一个测试文件操做的类,那么在它的测试类中,可能 @Before 里面须要去打开一个文件,而每一个测试方法运行结束以后,都须要去close这个文件。这个时候就能够把文件close的操做放在 @After 里面,让它自动去执行。
相似的,还有 @BeforeClass 和 @AfterClass 。 @BeforeClass 的做用是,在跑一个测试类的全部测试方法以前,会执行一次被 @BeforeClass 修饰的方法,执行完全部测试方法以后,会执行一遍被 @AfterClass 修饰的方法。这两个方法能够用来setup和release一些公共的资源,须要注意的是,被这两个annotation修饰的方法必须是静态的。
前面讲的是单元测试框架对于一个测试方法的第一步“setup”,为咱们作的事情。而对于第三部“验证结果”,则通常是经过一些assert方法来完成的。JUnit为咱们提供的assert方法,多数都在 Assert 这个类里面。最经常使用的那些以下:
assertEquals(expected, actual)
验证expected的值跟actual是同样的,若是是同样的话,测试经过,否则的话,测试失败。若是传入的是object,那么这里的对比用的是equals()
assertEquals(expected, actual, tolerance)
这里传入的expected和actual是float或double类型的,你们知道计算机表示浮点型数据都有必定的误差,因此哪怕理论上他们是相等的,可是用计算机表示出来则可能不是,因此这里运行传入一个误差值。若是两个数的差别在这个误差值以内,则测试经过,否者测试失败。
assertTrue(boolean condition)
验证contidion的值是true
assertFalse(boolean condition)
验证contidion的值是false
assertNull(Object obj)
验证obj的值是null
assertNotNull(Object obj)
验证obj的值不是null
assertSame(expected, actual)
验证expected和actual是同一个对象,即指向同一个对象
assertNotSame(expected, actual)
验证expected和actual不是同一个对象,即指向不一样的对象
fail()
让测试方法失败
注意:上面的每个方法,都有一个重载的方法,能够在前面加一个String类型的参数,表示若是验证失败的话,将用这个字符串做为失败的结果报告。
好比:
assertEquals("Current user Id should be 1", 1, currentUser.id());
当 currentUser.id() 的值不是1的时候,在结果报道里面将显示"Current user Id should be 1",这样可让测试结果更具备可读性,更清楚错误的缘由是什么。
比较有意思的是最后一个方法, fail() ,你或许会好奇,这个有什么用呢?其实这个在不少状况下仍是有用的,好比最明显的一个做用就是,你能够验证你的测试代码真的是跑了的。
此外,它还有另一个重要做用,那就是验证某个被测试的方法会正确的抛出异常,不过这点能够经过下面讲到的方法,更方便的作到,因此就不讲了。
这部分相对来讲仍是很好理解的,不作过多解释。
Ignore一些测试方法
不少时候,由于某些缘由(好比正式代码尚未实现等),咱们可能想让JUnit忽略某些方法,让它在跑全部测试方法的时候不要跑这个测试方法。要达到这个目的也很简单,只须要在要被忽略的测试方法前面加上 @Ignore 就能够了,以下:
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } // Omit testAdd() and testMultiply() for brevity @Test @Ignore("not implemented yet") public void testFactorial() { } }
验证方法会抛出某些异常
有的时候,抛出异常是一个方法正确工做的一部分。好比一个除法函数,当除数是0的时候,它应该抛出异常,告诉外界,传入的被除数是0,示例代码以下:
public class Calculator { // Omit testAdd() and testMultiply() for brevity public double divide(double divident, double dividor) { if (dividor == 0) throw new IllegalArgumentException("Dividor cannot be 0"); return divident / dividor; }}
那么如何测试当传入的除数是0的时候,这个方法应该抛出 IllegalArgumentException 异常呢?
在Junit中,能够经过给 @Test annotation传入一个expected参数来达到这个目的,以下:
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } // Omit testAdd() and testMultiply() for brevity @Test(expected = IllegalArgumentException.class) public void test() { mCalculator.divide(4, 0); } }
@Test(expected = IllegalArgumentException.class) 表示验证这个测试方法将抛出 IllegalArgumentException 异常,若是没有抛出的话,则测试失败。
这篇文字大概简单介绍了JUnit的使用,相对来讲是比较简单,也是比较容易理解的,但愿能帮助到你们。其中Assert部分,能够帮咱们验证一个方法的返回结果。然而,这些只能帮咱们测试有返回值的那些方法。在第一篇文章里面咱们讲了,一个类的方法分两种,一是有返回值的方法,这些能够经过咱们今天讲的JUnit来作测试。而另一种没有返回值的方法,即void方法,则要经过另一个框架,Mockito,来验证它的正确性。至于怎么样验证void方法的正确性,以及Mockito的使用,请关注下一篇文章。