阅读 114

单元测试系列之三Junit

Junit

Java应用最广泛、最基础的测试框架。在使用Android studio创建项目时会自动引入Junit的依赖包。

   testImplementation 'junit:junit:4.13.2'复制代码

但是使用我的框架时需要把这个依赖删除,以免出现依赖冲突。

简单使用

Junit通过@Test注解来判断一个方法是否是测试方法,方法名可以随便取,不过要注意代码可读性;使用@Before来为测试做准备工作,比如初始化对象、打开文件等,在每个测试方法调用之前该方法都会被调用一次。所以如果一个测试类有多个测试方法,在运行测试类时,@Before标注的方法就会被调用多次;对应的有@After,在每个测试方法运行之后都会被调用,可以用来关闭文件等。

类似的,还有@BeforeClass@AfterClass@BeforeClass的作用是,在跑一个测试类的所有测试方法之前,会执行一次被@BeforeClass修饰的方法,执行完所有测试方法之后,会执行被@AfterClass修饰的方法。这两个方法可以用来setup和release一些公共的资源,需要注意的是,被这两个annotation修饰的方法必须是静态的。

public class CalculatorTest {
   Calculator calculator;

   @BeforeClass
   public static void prepare(){
       System.out.println("BeforeClass");
  }

   @Before
   public void setUp() throws Exception {
       System.out.println("测试准备工作");
       calculator = new Calculator();
  }

   @Test
   public void testAdd() {
       System.out.println("测试方法 testAdd()");
       int sum = calculator.add(2, 3);
       Assert.assertEquals(5, sum);
  }

   @Test
   public void testMulti() {
       System.out.println("测试方法 testMulti()");
       int product = calculator.multi(2, 3);
       Assert.assertEquals(6, product);
  }

   @After
   public void tearDown() throws Exception {
       System.out.println("测试结束");
  }

   @AfterClass
   public static void finish(){
       System.out.println("AfterClass");
  }
}复制代码

运行测试类,结果如下:

截屏2021-09-02 下午4.18.37.png

常用方法

在验证结果的时候,JUnit提供了很多Assert方法,都在Assert类中,常用的如下:

  • assertEquals(expected, actual) 验证expected的值跟actual是一样的,如果是一样的话,测试通过,不然的话,测试失败。

  • 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",这样可以让测试结果更具有可读性,更清楚错误的原因是什么。

Junit Rule的使用

一个junit rule就是一个实现了TestRule的类,用于在每个测试方法执行前后执行一些代码。从源码里可以看到,junit rule有以下这些:

截屏2021-09-06 下午5.24.01

这里只介绍常用的几个:

  1. ExpectedException:支持在测试中指定期望抛出的异常类型,类似于@Test(expected = xxxException.class)

  2. ExternalResource:支持提前建立好资源并且会在测试结束后将其销毁,常用语Socket、数据库链接等资源的管理。

  3. TemporaryFolder:支持创建文件与目录并且在测试运行结束后将其删除,用于使用文件系统且独立运行的测试。

  4. TestName:支持在测试中获取当前测试方法的名称。

  5. TestWatcher:提供五个触发点:测试开始、测试完成、测试成功、测试跳过、测试失败,允许我们在每个触发点执行自定义的逻辑。

  6. Timeout:支持为测试类中的所有测试方法设置相同的超时时间,类似于@Test(timeout = 100)

原生Rule的使用

这些Rule的使用方法很简单:定义一个类的public field,必须是public的,并使用@Rule修饰。以Timeout为例:

public class JunitRuleTest {

   @Rule
   public Timeout timeout = new Timeout(10, TimeUnit.MILLISECONDS);

   @Test
   public void testRule1() throws InterruptedException {
       Thread.sleep(20);
       assertEquals(4, 2 + 2);
  }

   @Test
   public void testRule2() throws InterruptedException {
       Thread.sleep(5);
       assertEquals(4, 2 + 2);
  }
}复制代码

Junit rule对每个测试方法都生效,所以测试类的每个测试方法都不能超过指定的时间,否则就标记为测试失败。所以上面代码的运行结果是:testRule1测试失败,testRule2测试通过。

自定义Rule的使用

此外,我们还可以自定义自己的Rule:实现TestRule接口,并实现apply方法。

   class CustomRule implements TestRule {
       @Override
       public Statement apply(Statement base, Description description) {
           return new Statement() {
               @Override
               public void evaluate() throws Throwable {
                   String className = description.getClassName();
                   String methodName = description.getMethodName();
                   System.out.println("执行方法开始, className:" + className + ", methodName:" + methodName);//写在base.evaluate()前面

                   base.evaluate();//这个就是执行测试方法

                   System.out.println("执行方法结束, className:" + className + ", methodName:" + methodName);//写在base.evaluate()后面

              }
          };
      }
  }复制代码

使用方法和自带的Rule一样:

public class JunitRuleTest {
   @Rule
   public CustomRule customRule = new CustomRule();

   @Test
   public void testRule2() throws InterruptedException {
       assertEquals(4, 2 + 2);
  }
}复制代码

运行一下:

截屏2021-09-06 下午5.53.22.png

与@Before注解的区别

@Before注解的方法只能作用于当前测试类及其子类,而实现了TestRule的类可以被用于多个测试类,因此当你想在多个测试类中应用同样的代码时,JUnit Rule是更好的选择。JUnit Rule可以实现@Before, @BeforeClass, @After, @AfterClass的所有功能。除此之外,当多个不同的rule对象用于同一个测试用例时,还可以使用RuleChain来设定这些rule的执行先后顺序。

其他功能

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时,系统会抛出ArithmeticException,但我们可以指定抛出IllegalArgumentException,代码如下:

public class Calculator {
   public int divide(int num1, int num2){
       if (num2 == 0){
           throw new IllegalArgumentException("Divider cannot be 0");
      }
       return num1/num2;
  }
}复制代码

在Junit中,可以通过给@Test annotation传入一个expected参数来达到这个目的,如下:

public class CalculatorTest {
   Calculator calculator;

   @Before
   public void setUp() throws Exception {
       System.out.println("测试准备工作");
       calculator = new Calculator();
  }

   @Test(expected = IllegalArgumentException.class)
   public void testDivide() {
       System.out.println("测试方法 testDivide()");
       calculator.divide(2, 0);
  }
}复制代码

@Test(expected = IllegalArgumentException.class)表示验证这个测试方法将抛出IllegalArgumentException异常,如果没有抛出的话,则测试失败。

限制测试方法执行时间

很多时候我们会需要限制某些测试方法的执行时间,以便可以把控测试时长。在Junit中,可以通过给@Test 注解传入一个timeout参数来达到这个目的,如下:

public class ExampleUnitTest {
   @Test(timeout = 5)
   public void addition_isCorrect() throws InterruptedException {
       Thread.sleep(10);
       assertEquals(4, 2 + 2);
  }
}复制代码

运行结果如下:

截屏2021-09-06 下午4.50.29.png

但是请注意,使用timeout注解参数的测试方法运行的线程和@Before@After修饰的方法运行的线程不是同一个线程!这个是我看源码发现的,经过测试,同时使用timeout和@Before@After时,timeout并没有发挥作用。所以最好不用同时使用。

   /**
    * Optionally specify <code>timeout</code> in milliseconds to cause a test method to fail if it
    * takes longer than that number of milliseconds.
    * <p>
    * <b>THREAD SAFETY WARNING:</b> Test methods with a timeout parameter are run in a thread other than the
    * thread which runs the fixture's @Before and @After methods. This may yield different behavior for
    * code that is not thread safe when compared to the same test method without a timeout parameter.
    * <b>Consider using the {@link org.junit.rules.Timeout} rule instead</b>, which ensures a test method is run on the
    * same thread as the fixture's @Before and @After methods.
    * </p>
    */
   long timeout() default 0L;复制代码

至此,JUnit的基本应用都介绍完了。


作者:大师傅姑爷
链接:https://juejin.cn/post/7019596915742343175


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐