单元测试系列之三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"); } }复制代码
运行测试类,结果如下:
常用方法
在验证结果的时候,JUnit提供了很多Assert方法,都在Assert类中,常用的如下:
assertEquals(expected, actual)
验证expected的值跟actual是一样的,如果是一样的话,测试通过,不然的话,测试失败。assertEquals(expected, actual, tolerance)
这里传入的expected和actual是float或double类型的,最后一个参数是偏差值。如果两个数的差异在这个偏差值之内,则测试通过,否者测试失败。assertTrue(boolean condition)
验证contidion的值是trueassertFalse(boolean condition)
验证contidion的值是falseassertNull(Object obj)
验证obj的值是nullassertNotNull(Object obj)
验证obj的值不是nullassertSame(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有以下这些:
这里只介绍常用的几个:
ExpectedException:支持在测试中指定期望抛出的异常类型,类似于
@Test(expected = xxxException.class)
。ExternalResource:支持提前建立好资源并且会在测试结束后将其销毁,常用语Socket、数据库链接等资源的管理。
TemporaryFolder:支持创建文件与目录并且在测试运行结束后将其删除,用于使用文件系统且独立运行的测试。
TestName:支持在测试中获取当前测试方法的名称。
TestWatcher:提供五个触发点:测试开始、测试完成、测试成功、测试跳过、测试失败,允许我们在每个触发点执行自定义的逻辑。
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); } }复制代码
运行一下:
与@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); } }复制代码
运行结果如下:
但是请注意,使用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