51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 1579|回复: 0
打印 上一主题 下一主题

[讨论] JUnit单元测试框架的使用

[复制链接]
  • TA的每日心情
    无聊
    2023-12-19 11:16
  • 签到天数: 40 天

    连续签到: 1 天

    [LV.5]测试团长

    跳转到指定楼层
    1#
    发表于 2018-3-7 15:49:41 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    我们写单元测试,一般都会用到一个或多个单元测试框架,在这里,我们介绍一下JUnit4这个测试框
    架。这是Java界用的最广泛,也是最基础的一个框架,其他的很多框架,包括我们后面会看到的Rob
    olectric,都是基于或兼容JUnit4的。然而首先要解决的问题是。。。
    为什么要使用单元测试框架呢
    或者换句话说,单元测试框架能够为我们做什么呢?从最基本的开始说起,假如我们有这样一个类:
    1. public class Calculator {
    2.     public int add(int one, int another) {
    3.         // 为了简单起见,暂不考虑溢出等情况。
    4.         return one + another;
    5.     }

    6.     public int multiply(int one, int another) {
    7.         // 为了简单起见,暂不考虑溢出等情况。
    8.         return one * another;
    9.     }
    10. }
    11. 如果不用单元测试框架的话,我们要怎么写测试代码呢?我们恐怕得写出下面这样的代码:
    12. public class CalculatorTest {
    13.     public static void main(String[] args) {
    14.         Calculator calculator = new Calculator();
    15.         int sum = calculator.add(1, 2);
    16.         if(sum == 3) {
    17.             System.out.println("add() works!")
    18.         } else {
    19.             System.out.println("add() does not works!")
    20.         }

    21.         int product = calculator.multiply(2, 4);
    22.         if (product == 8) {
    23.             System.out.println("multiply() works!")
    24.         } else {
    25.             System.out.println("multiply() does not works!")
    26.         }
    27.     }
    28. }
    复制代码
    然后我们再通过某种方式,比如命令行或IDE,运行这个 CalculatorTest 的 main 方法,在看着termi
    nal的输出,才知道测试是通过还是失败。想想一下,如果我们有很多的类,每个类都有很多方法,
    那么就要写一堆这样的代码,每个类对于一个含有 main 方法的test类,同时 main 方法里面会有一
    堆代码。这样既写起来痛苦,跑起来更痛苦,比如说,你怎么样一次性跑所有的测试类呢?所以,
    一个测试框架为我们做的最基本的事情,就是允许我们按照某种更简单的方式写测试代码,把每一
    个测试单元写在一个测试方法里面,然后它会自动找出所有的测试方法,并且根据你的需要,运行
    所有的测试方法,或者是运行单个测试方法,或者是运行部分测试方法等等。
    对于上面的 Calculator 例子,如果使用Junit的话,我们可以按照如下的方式写测试代码:
    1. public class CalculatorTest {

    2.     @Test
    3.     public void testAdd() throws Exception {
    4.         Calculator calculator = new Calculator();
    5.         int sum = calculator.add(1, 2);
    6.         Assert.assertEquals(3, sum);
    7.     }

    8.     @Test
    9.     public void testMultiply() throws Exception {
    10.         Calculator calculator = new Calculator();
    11.         int product = calculator.multiply(2, 4);
    12.         Assert.assertEquals(8, product);
    13.     }

    14. }
    复制代码
    每一个被测试的方法( add(), multiply() ),写一个对应的测试方法( testAdd(), testMultiply() )。那JUnit
    怎么知道那些是测试方法,哪些不是呢?这个是通过前面的 @Test 注解来标志的,只要有这个注解,
    JUnit4就会当做是一个测试方法,方法名其实是可以随意起的。当然,名字还是应该起的更有可读性
    一点,让人一看就知道,这个测试方法是测试了被测的类的那个方法,或者是测试了那个功能点等等。
    除了帮我们找出所有的测试方法,并且方便运行意外,单元测试框架还帮我们做了其他事情。在 这个
    系列的第一篇文章 中我们提到,一个测试方法主要包括三个部分:
    setup
    执行操作
    验证结果
    而一个单元测试框架,可以让我们更方便的写上面的每一步的代码,尤其是第一步和第三部。比如
    说,在上面的 CalculatorTest 中, testAdd() 和 testMultiply() 都有相同的setup: Calculator calculato
    r = new Calculator(); ,如果 Calculator 还有其他的方法的话,这行代码就得重复更多次,这种dupli
    cation是没必要的。绝大多数单元测试框架考虑到了这一点,它们知道一个测试类的很多测试方法可
    能需要相同的setup,所以为我们提供了便捷方法。对于JUnit4,是通过 @Before 来实现的:

    1. public class CalculatorTest {
    2.     Calculator mCalculator;

    3.     @Before
    4.     public void setup() {
    5.         mCalculator = new Calculator();
    6.     }

    7.     @Test
    8.     public void testAdd() throws Exception {
    9.         int sum = mCalculator.add(1, 2);
    10.         assertEquals(3, sum);  //为了简洁,往往会static import Assert里面的所有方法。
    11.     }

    12.     @Test
    13.     public void testMultiply() throws Exception {
    14.         int product = mCalculator.multiply(2, 4);
    15.         assertEquals(8, product);
    16.     }

    17. }
    复制代码
    如果一个方法被 @Before 修饰过了,那么在每个测试方法调用之前,这个方法都会得到调用。所以
    上面的例子中, testAdd() 被运行之前, setup() 会被调用一次,把 mCalculator 实例化,接着运行
    testAdd() ; testMultiply() 被运行之前, setup() 又会被调用一次,把 mCalculator 再次实例化,接
    着运行 testMultiply() 。如果还有其他的测试方法,则以此类推。
    对应于 @Before 的,有一个 @After ,作用估计你也猜得到,那就是每个测试方法运行结束之后,
    会得到运行的方法。比如一个测试文件操作的类,那么在它的测试类中,可能 @Before 里面需要去
    打开一个文件,而每个测试方法运行结束之后,都需要去close这个文件。这个时候就可以把文件clo
    se的操作放在 @After 里面,让它自动去执行。
    类似的,还有 @BeforeClass 和 @AfterClass 。 @BeforeClass 的作用是,在跑一个测试类的所有测试
    方法之前,会执行一次被 @BeforeClass 修饰的方法,执行完所有测试方法之后,会执行一遍被 @Af
    terClass 修饰的方法。这两个方法可以用来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() ,你或许会好奇,这个有什么用呢?其实这个在很多情况下
    还是有用的,比如最明显的一个作用就是,你可以验证你的测试代码真的是跑了的。
    此外,它还有另外一个重要作用,那就是验证某个被测试的方法会正确的抛出异常,不过这点可
    以通过下面讲到的方法,更方便的做到,所以就不讲了。
    这部分相对来说还是很好理解的,不做过多解释。
    JUnit的其他功能
    Ignore一些测试方法
    很多时候,因为某些原因(比如正式代码还没有实现等),我们可能想让JUnit忽略某些方法,
    让它在跑所有测试方法的时候不要跑这个测试方法。要达到这个目的也很简单,只需要在要被忽
    略的测试方法前面加上 @Ignore 就可以了,如下:
    1. public class CalculatorTest {
    2.     Calculator mCalculator;

    3.     @Before
    4.     public void setup() {
    5.         mCalculator = new Calculator();
    6.     }

    7.     // Omit testAdd() and testMultiply() for brevity

    8.     @Test
    9.     @Ignore("not implemented yet")
    10.     public void testFactorial() {
    11.     }
    12. }
    复制代码
    验证方法会抛出某些异常
    有的时候,抛出异常是一个方法正确工作的一部分。比如一个除法函数,当除数是0的时候,它应该抛
    出异常,告诉外界,传入的被除数是0,示例代码如下:
    1. public class Calculator {

    2.     // Omit testAdd() and testMultiply() for brevity

    3.     public double divide(double divident, double dividor) {
    4.         if (dividor == 0) throw new IllegalArgumentException("Dividor cannot be 0");

    5.         return divident / dividor;
    6.     }}
    复制代码
    那么如何测试当传入的除数是0的时候,这个方法应该抛出 IllegalArgumentException 异常呢?
    在Junit中,可以通过给 @Test annotation传入一个expected参数来达到这个目的,如下:
    1. public class CalculatorTest {
    2.     Calculator mCalculator;

    3.     @Before
    4.     public void setup() {
    5.         mCalculator = new Calculator();
    6.     }

    7.     // Omit testAdd() and testMultiply() for brevity

    8.     @Test(expected = IllegalArgumentException.class)
    9.     public void test() {
    10.         mCalculator.divide(4, 0);
    11.     }

    12. }
    复制代码
    @Test(expected = IllegalArgumentException.class) 表示验证这个测试方法将抛出 IllegalArgumentExc
    eption 异常,如果没有抛出的话,则测试失败。
    在Android项目里面使用JUnit
    在Android项目里面使用JUnit是很简单的,你只需要将JUnit这个library加到你的dependencies里面。
    testCompile 'junit:junit:4.12'
    如果你通过AndroidStudio创建一个项目,这个dependency默认是加上了的,所以你甚至这步都可以省
    略。
    此外,你需要把测试代码放到src/test/java 目录下面。
    接下来关于怎么样运行测试代码,怎么样看结果,请参考 这个系列的第一篇文章 的相关部分,因为图
    比较多,这边就不重复了。
    这里让大家看一下运行的结果是什么样子的,其中有一个失败的测试用例是故意的。如果你直接在And
    roidStudio里面跑上面的测试类CalculatorTest的所有测试方法的话,会看到如下的结果:

    左边可以看到所有的测试方法,以及每个方法跑出来的结果,绿色表示测试通过的测试方法,黄色的
    感叹号或红色的表示测试失败的。第三个那个有条纹的球球表示被忽略的测试方法。如果是通过ter
    minal跑的话,则会看到如下的测试结果:



    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-4-20 01:16 , Processed in 0.069473 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表