5.2.Junit4单元测试

五、SpringBoot单元测试

2、Junit4

JUnit4把所有的代码都打包到一个jar包

所需依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

5.2.1、名称规范

  • 单元测试类名

单元测试的类名应该起为 xxxxTest.java 表明这个一个测试类

类名应该用简洁的英文表明测试内容或函数

例如为了测试一个计算和的方法可以取名为 SumTest.java

  • 单元测试方法名

测试方法上应该加上 @Test 注解以表明其是一个测试方法

5.2.2、执行测试

(1)、运行测试方法
  1. 方式一

在idea 中每个测试方法前都有一个运行按钮,点击后会运行该测试方法。

在这里插入图片描述

  1. 方式二

在测试方法上右键选择运行测试方法

在这里插入图片描述

(2)、运行测试类
  1. 方式一

在idea 中在测试类的类名前面有一个运行按钮,点击后会运行当前测试类中所有的测试方法。

在这里插入图片描述

  1. 方式二

在测试类上右键选择运行测试类

在这里插入图片描述

5.2.3、查看测试覆盖率

(1)、查看测试方法覆盖率
  1. 方式一

在idea 中每个测试方法前都有一个运行按钮,在按钮上右键选择测试覆盖率

在这里插入图片描述

  1. 方式二

在测试方法上右键选择查看

在这里插入图片描述

(2)、查看测试类覆盖率
  1. 方式一

在idea 中在测试类的类名前面有一个运行按钮,右键点开查看

在这里插入图片描述

  1. 方式二

在测试类上右键选择查看

在这里插入图片描述

5.2.4、生成测试报告

可能会有很多单元测试类放在了 test/java/ 文件夹下

可以在 idea 中右侧maven页签中的 Lifecycle 下双击 test ,将会帮你运行所有的单元测试

在这里插入图片描述

每个单元测试类都会在 target/surefire-reports 下生成 txt 报告,里面有当前单元测试类的执行成功、失败、跳过、耗时等信息

在这里插入图片描述

生成 txt 报告后,如果不想一个个去看每个测试类生成的 txt 报告,可以在 idea 的 Terminal 命令行窗口输入 mvn surefire-report:report 命令执行

在这里插入图片描述

它会读取所有 txt 报告在 target/site/ 下生成 surefire-reports.html 文件,用浏览器打开该文件你就可以直观的看到所有单元测试类的所有执行情况

5.2.5、断言方法

在JUnit4中,org.junit.Assert类拥有所有的断言方法,用于判断输出的结果和期望的值是否相等

它们接受额外的错误描述信息作为方法的第一个参数

  • Assert.assertEquals:检测值是否相等
  • Assert.assertNotEquals:检测值是否不相等
  • Assert.assertThrows():检测是否抛出异常
  • Assert.assertArrayEquals():检测数组是否相同
  • Assert.assertTrue():检测条件是否为真
  • Assert.assertFalse():检测条件是否为假
  • Assert.assertNull():检测是否为空
  • Assert.assertNotNull():检测是否不为空
  • Assert.assertSame():测试两个对象引用是否引用同一对象
  • Assert.assertNotSame():测试两个对象引用是否不引用同一对象

5.2.6、测试示例

在模块springboot-05-000中测试

(1)、正常测试

【示例】

package org.junit;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import static java.util.Arrays.asList;
import static org.junit.Assert.*;

public class AssertTest {

    @Test
    public void testAssertions() {
        String str1 = new String("abc");
        String str2 = new String("abc");
        String str3 = null;
        String str4 = "abc";
        String str5 = "abc";
        int val1 = 5;
        int val2 = 6;
        String[] expectedArray = {"one", "two", "three"};
        String[] resultArray = {"one", "two", "three"};
        List<String> arrList = new ArrayList<>(asList("Java", "Junit", "Test"));
        List<String> linList = new LinkedList<>(asList("Java", "Junit", "Test"));
        long long1 = 10L;
        long long2 = 11L;

        assertEquals(str1, str2);//值相等测试通过
        assertNotEquals("值不相等", long1, long2);//值不相等测试通过
        assertArrayEquals(expectedArray, resultArray);//数组的元素都相等时测试通过
        assertTrue(val1 < val2);// 值为true时测试通过
        assertFalse(val1 > val2);//值为false时测试通过
        assertNull(str3);// 值为null时测试通过
        assertNotNull(str1);//值不为null时测试通过
        assertSame(str4, str5);//相同时测试通过
        assertNotSame(str1, str3);//不相同时测试通过

    }
}
(2)、参数化测试

参数化测试允许通过变化范围的参数值来测试方法

参数化测试可以通过以下简单的步骤实现

  1. 对测试类添加注解 @RunWith(Parameterized.class)
  2. 将需要使用变化范围参数值测试的参数定义为私有变量
  3. 使用上一步骤声明的私有变量作为入参创建构造函数
  4. 创建一个使用@Parameters注解的公共静态方法,它将需要测试的各种变量值通过集合的形式返回
  5. 使用定义的私有变量定义测试方法

【示例】

EvenNumberChecker.java 校验输入的数字是否为偶数

package org.junit;

public class EvenNumberChecker {

    /**
     * 校验输入的数字是否为偶数
     *
     * @param i
     * @return
     */
    public boolean isEven(int i) {
        if (i % 2 == 0) {
            return true;
        } else {
            return false;
        }
    }
}

EvenNumberCheckerTest.java 对 EvenNumberChecker.java 进行参数化测试

package org.junit;

import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.Collection;

import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

// 1.对测试类添加注解 @RunWith(Parameterized.class)
@RunWith(Parameterized.class)
public class EvenNumberCheckerTest {

    // 2.将范围变化的测试的参数定义为私有变量
    private int inputNumber;
    private boolean isEven;

    // 3.使用上一步骤声明的私有变量作为入参创建构造函数
    public EvenNumberCheckerTest(int inputNumber, boolean isEven) {
        super();
        this.inputNumber = inputNumber;
        this.isEven = isEven;
    }

    // 4.创建一个使用@Parameters注解的公共静态方法,它将需要测试的各种变量值通过集合的形式返回
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][]{
                {2, true},
                {5, false},
                {10, true}
        };
        return Arrays.asList(data);
    }

    // 5.使用定义的私有变量定义测试方法
    @Test
    public void test() {
        System.out.println("inputNumber: " + inputNumber + "; isEven: " + isEven);
        EvenNumberChecker evenNumberChecker = new EvenNumberChecker();
        boolean actualResult = evenNumberChecker.isEven(inputNumber);
        assertEquals(isEven, actualResult);
    }
}

测试结果

在这里插入图片描述

(3)、套件测试

Junit 4允许通过使用测试套件类批量运行测试类

为一套测试类创建一个测试套件要为测试类添加以下注解:

  • @RunWith(Suite.class)
  • @SuiteClasses(TestClass1.class, TestClass2.class)

当运行时所有包含在@SuiteClasses注解内的所有测试类都会被执行

【示例】

Arithmetic.java

package org.junit;

public class Arithmetic {

    public int add(int i, int j) {
        return i + j;
    }
}

ArithmeticTest.java

package org.junit;

import static org.junit.Assert.assertEquals;

public class ArithmeticTest {

    @BeforeClass
    public static void setUpClass() {
        // one time setup method
        System.out.println("@BeforeClass - executed only one time and is first method to be executed");
    }

    @AfterClass
    public static void tearDownClass() {
        // one time tear down method
        System.out.println("@AfterClass - executed only one time and is last method to be executed");
    }

    @Before
    public void setUp() {
        // set up method executed before every test method
        System.out.println("@Before - executed before every test method");
    }

    @After
    public void tearDown() {
        // tear down method executed after every test method
        System.out.println("@After - executed after every test method");
    }

    @Test
    public void testAdd() {
        Arithmetic arithmetic = new Arithmetic();
        int actualResult = arithmetic.add(3, 4);
        int expectedResult = 7;
        assertEquals(expectedResult, actualResult);
        System.out.println("@Test - defines test method");
    }
}

Geometric.java

package org.junit;

public class Geometric {
    public int squareArea(int length) {
        return length * length;
    }
}

GeometryTest.java

package org.junit;

import static org.junit.Assert.assertEquals;

public class GeometryTest {
    @Test
    public void testSquareArea() {
        Geometric geometry = new Geometric();
        int actualResult = geometry.squareArea(2);
        int expectedResult = 4;
        assertEquals(expectedResult, actualResult);
        System.out.println("GeometryTest运行");
    }
}

套件测试类

package org.junit;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({ ArithmeticTest.class, GeometryTest.class })
public class AllTests {
}

测试结果

在这里插入图片描述

(4)、忽略测试

Junit 4 忽略测试(Ignore test)被用来禁止执行junit测试类的某些或者全部测试方法

Junit 提供了@Ignore注解来实现忽略测试,它可以用来跳过失败、或者抛出异常的测试方法

  • @Ignore可以应用到某些测试方法上
  • @Ignore可以应用到整个测试类上
忽略测试方法

在需要忽略的测试方法上使用@Ignore注解

【示例】

ArithmeticMethod.java

package org.junit;

public class ArithmeticMethod {
    public int add(int i, int j) {
        return i + j;
    }

    public int substract(int i, int j) {
        return i - j;
    }
}

ArithmeticMethodTest.java

testSubstract() 方法使用了@Ignore注解

在执行测试类时此方法会被忽略

package org.junit;

import static org.junit.Assert.assertEquals;

public class ArithmeticMethodTest {
    @Test
    public void testAdd() {
        System.out.println("testAdd方法执行");
        ArithmeticMethod arithmetic = new ArithmeticMethod();
        int actualResult = arithmetic.add(1, 2);
        int expectedResult = 3;
        assertEquals(expectedResult, actualResult);
    }

    /**
     * testSubstract() 方法使用了@Ignore注解
     * 在执行测试类时此方法会被忽略
     */
    @Test
    @Ignore
    public void testSubstract() {
        System.out.println("testSubstract忽略方法执行");
        ArithmeticMethod arithmetic = new ArithmeticMethod();
        int actualResult = arithmetic.substract(4, 2);
        int expectedResult = 2;
        assertEquals(expectedResult, actualResult);
    }
}

运行测试类

在这里插入图片描述

测试结果

在这里插入图片描述

忽略整个类

需要禁止测试类上的所有方法的执行则在测试类上添加@Ignore注解即可

【示例】

package org.junit;

public class ArithmeticAll {
    public int add(int i, int j) {
        return i + j;
    }

    public int substract(int i, int j) {
        return i - j;
    }
}

ArithmeticAllTest.java 其所有方法都将被忽略

package org.junit;

import static org.junit.Assert.*;

@Ignore
public class ArithmeticAllTest {
    /**
     * Example of test case success
     */
    @Test
    public void testAdd() {
        ArithmeticAll arithmetic = new ArithmeticAll();
        int actualResult = arithmetic.add(1, 2);
        // example of test case success
        int expectedResult = 3;
        assertEquals(expectedResult, actualResult);
    }

    /**
     * Example of @ignore annotation
     */
    @Test
    public void testSubstract() {
        ArithmeticAll arithmetic = new ArithmeticAll();
        int actualResult = arithmetic.substract(4, 2);
        int expectedResult = 2;
        // example of test case failure
        assertEquals(expectedResult, actualResult);
    }
}

运行整个测试类测试

在这里插入图片描述

(5)、标记测试

在JUnit4中使用@Category注解

(6)、超时测试

Junit 4超时测试(Timeout test)可以被用来测试方法的执行时间

使用方式

  • 在测试类的方法上使用 @Timeout 注解
  • 测试类的所有方法应用 Timeout 规则
测试单个方法超时

【示例】

package org.junit;

public class TimeoutTest {
    @Test(timeout = 200)
    public void testTimeout() {
        while (true) ;
    }
}

运行测试方法测试结果

在这里插入图片描述

测试所有方法超时

Junit 4 提供了 Timeout 规则来测试类中的所有方法

如果类中的任意一个方法执行时间超过了在Timeout 规则中规定的值则测试方法将抛出异常,测试结果为失败

指定的超时参数是以毫秒记

使用方式

  • 在测试类中使用@Rule注解标记规则

【示例】

package org.junit;

import org.junit.rules.Timeout;

public class TimeoutRuleTest {

    /**
     * Rule is applied to all methods of class
     * time is specified in milliseconds
     */
    @Rule
    public Timeout timeout = new Timeout(1000);

    /**
     * Example of timeout test.
     * Test will fail if it takes more than 1 sec to execute
     */
    @Test
    public void testTimeout1() {
        while (true) ;
    }

    /**
     * Example of timeout test.
     * Test will fail if it takes more than 1 sec to execute
     */
    @Test
    public void testTimeout2() {
        while (true) ;
    }
}

运行测试类测试结果

在这里插入图片描述

(7)、期望异常测试

单元测试可以检查抛出预期异常( expected exceptions)的代码

【示例】写一个测试类,定义一个canVote() 方法返回true或者false

package org.junit;

public class Student {
    public boolean canVote(int age) {
        if (age <= 0) throw new IllegalArgumentException("age should be +ve");
        if (age < 18) return false;
        else return true;
    }
}

检查抛出的异常有三种方式,它们各自都有优缺点

@Test(expected…)

@Test注解有一个可选的参数,"expected"允许你设置一个Throwable的子类

如果想要验证上面的canVote()方法抛出预期的异常可以这样写

public class StudentTest {
    @Test(expected = IllegalArgumentException.class)
    public void canVote_throws_IllegalArgumentException_for_zero_age() {
        Student student = new Student();
        boolean flag=student.canVote(-1);
        System.out.println(flag);
    }
}

Debug测试方法

在这里插入图片描述

ExpectedException

如果要使用JUnit框架中的ExpectedException类需要声明ExpectedException异常

@Rule
public ExpectedException thrown = ExpectedException.none();

然后可以使用更加简单的方式验证预期的异常

@Test
public void canVote_throws_IllegalArgumentException_for_zero_age1() {
   Student student = new Student();
   thrown.expect(NullPointerException.class);
   student.canVote(0);
}

在这里插入图片描述

或者可以设置预期异常的属性信息

@Test
public void canVote_throws_IllegalArgumentException_for_zero_age2() {
	Student student = new Student();
	thrown.expect(IllegalArgumentException.class);
	thrown.expectMessage("age should be +ve");
	student.canVote(0);
}

在这里插入图片描述

Try/catch with assert/fail

在JUnit4之前的版本中使用try/catch语句块检查异常

@Test
public void canVote_throws_IllegalArgumentException_for_zero_age3() {
    Student student = new Student();
    try {
        student.canVote(0);
    } catch (IllegalArgumentException ex) {
        assertThat(ex.getMessage(), containsString("age should be +ve"));
    }
    fail("expected IllegalArgumentException for non +ve age");
}

尽管这种方式很老了,不过还是非常有效的

主要的缺点就是很容易忘记在catch语句块之后需要写fail()方法,如果预期异常没有抛出就会导致信息的误报

(8)、优先级测试

在写JUnit测试用例时,有时候需要按照定义顺序执行我们的单元测试方法。比如在测试数据库相关的用例时候要按照测试插入、查询、删除的顺序测试。如果不按照这个顺序测试可能会出现问题。比如删除方法在前面执行,后面的方法就都不能通过测试,因为数据已经被清空了。而JUnit测试时默认的顺序是随机的。所以这时就需要有办法要求JUnit在执行测试方法时按照我们指定的顺序来执行。

JUnit是通过@FixMethodOrder注解(annotation)来控制测试方法的执行顺序的

@FixMethodOrder注解的参数是org.junit.runners.MethodSorters对象

在枚举类org.junit.runners.MethodSorters中定义了如下三种顺序类型

  • MethodSorters.JVM

    按照JVM得到的方法顺序,也就是代码中定义的方法顺序

  • MethodSorters.DEFAULT(默认的顺序)

    以确定但不可预期的顺序执行

  • MethodSorters.NAME_ASCENDING

    按方法名字母顺序执行

    【示例】

以下的代码定义了三个方法testAddAndGet,testSearch,testRemove

设计的时候是希望三个方法按定义的顺序来执行

package org.junit;

import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.JVM) // 指定测试方法按定义的顺序执行
public class TestJNI {
    @Test
    public void testAddAndGet() {
        System.out.println("test 'addBean' and 'getBean' ");
    }

    @Test
    public final void testSearch() {
        System.out.println("test search CODE from JNI memory...");
    }

    @Test
    public final void testRemove() {
        System.out.println("test remove CODE from JNI memory...");
    }
}

运行测试类测试结果

在这里插入图片描述

如果@FixMethodOrder定义为MethodSorters.DEFAULT或去掉代码中的@FixMethodOrder注解,那么测试用便执行的顺序是

在这里插入图片描述

这并不是想要的结果,testRemove如果先执行了,testSearch肯定什么也找不到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值