单元测试 - JUnit4 详解

arcstack约 4307 字大约 14 分钟

单元测试 - JUnit4 详解

JUint是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。本文主要针对Junit4要点进行梳理总结。@pdai

什么是JUnit?

JUint是Java编程语言的单元测试框架,用于编写和运行可重复的自动化测试。

JUnit特点?

JUnit 是一个开放的资源框架,用于编写和运行测试。

  • 提供注解来识别测试方法。
  • 提供断言来测试预期结果。
  • JUnit 测试允许你编写代码更快,并能提高质量。
  • JUnit 优雅简洁。没那么复杂,花费时间较少。
  • JUnit测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
  • JUnit测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
  • JUnit在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。

官方资料

最好的资料依然在Junit官方网站,以下我帮你总结下Junit相关的官方网址。@pdai

  • 官网地址

https://junit.org/junit4/

  • 官方入门文档

https://github.com/junit-team/junit4/wiki/Assertions

  • 官方github

https://github.com/junit-team

常用注解

  • @Test

在junit3中,是通过对测试类和测试方法的命名来确定是否是测试,且所有的测试类必须继承junit的测试基类。在junit4中,定义一个测试方法变得简单很多,只需要在方法前加上@Test就行了。 注意:测试方法必须是public void,即公共、无返回数据。可以抛出异常。

  • @Ignore

有时候我们想暂时不运行某些测试方法\测试类,可以在方法前加上这个注解。在运行结果中,junit会统计忽略的用例数,来提醒你。但是不建议经常这么做,因为这样的坏处时,容易忘记去更新这些测试方法,导致代码不够干净,用例遗漏。使用此标注的时候不能与其它标注一起使用,如:和@Test 标注一起使用,那就没用了

  • @BeforeClass

当我们运行几个有关联的用例时,可能会在数据准备或其它前期准备中执行一些相同的命令,这个时候为了让代码更清晰,更少冗余,可以将公用的部分提取出来,放在一个方法里,并为这个方法注解@BeforeClass。意思是在测试类里所有用例运行之前,运行一次这个方法。例如创建数据库连接、读取文件等。 注意:方法名可以任意,但必须是public static void,即公开、静态、无返回。这个方法只会运行一次。

  • @AfterClass

跟@BeforeClass对应,在测试类里所有用例运行之后,运行一次。用于处理一些测试后续工作,例如清理数据,恢复现场。 注意:同样必须是public static void,即公开、静态、无返回。这个方法只会运行一次。

  • @Before

与@BeforeClass的区别在于,@Before不止运行一次,它会在每个用例运行之前都运行一次。主要用于一些独立于用例之间的准备工作。 比如两个用例都需要读取数据库里的用户A信息,但第一个用例会删除这个用户A,而第二个用例需要修改用户A。那么可以用@BeforeClass创建数据库连接。用@Before来插入一条用户A信息。 注意:必须是public void,不能为static。不止运行一次,根据用例数而定。

  • @After:与@Before对应。

  • @Runwith

  • 首先要分清几个概念:测试方法、测试类、测试集、测试运行器。

  • 其中测试方法就是用@Test注解的一些函数。

  • 测试类是包含一个或多个测试方法的一个Test.java文件。

  • 测试集是一个suite,可能包含多个测试类。

  • 测试运行器则决定了用什么方式偏好去运行这些测试集/类/方法。

  • 而@Runwith就是放在测试类名之前,用来确定这个类怎么运行的。也可以不标注,会使用默认运行器。常见的运行器有: * @RunWith(Parameterized.class) 参数化运行器,配合@Parameters使用junit的参数化功能

  • @RunWith(Suite.class) @SuiteClasses({ATest.class,BTest.class,CTest.class})测试集运行器配合使用测试集功能

  • @RunWith(JUnit4.class) junit4的默认运行器

  • @RunWith(JUnit38ClassRunner.class) 用于兼容junit3.8的运行器

  • 一些其它运行器具备更多功能。例如@RunWith(SpringJUnit4ClassRunner.class)集成了spring的一些功能

  • @Parameters: 用于使用参数化功能。

编写单元测试

接下来,我们开始学习JUnit4单元测试实例:

Maven包引入

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>

        <groupId>org.example</groupId>
        <artifactId>java-junit4</artifactId>
        <version>1.0-SNAPSHOT</version>

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>

        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>

    </project>

测试:Hello World

    package tech.pdai.junit4;

    import org.junit.Test;

    import static org.junit.Assert.assertEquals;

    /** * Hello world test. * * @author pdai */
    public class HelloWorldTest {

        @Test
        public void firstTest() {
            assertEquals(2, 1 + 1);
        }
    }

执行结果 dev-ut4-1.png @Test注解在方法上标记方法为测试方法,以便构建工具和 IDE 能够识别并执行它们。JUnit 4 需要测试方法为public,这和Junit 5 有差别。

测试:生命周期

  • @BeforeClass注解修饰的方法(该方法要用static修饰)会在所有方法运行前被执行,且只执行一次,通常用来为后面测试方法的准备工作,如加载配置、进行数据库的连接等。父类的@BeforeClass注解方法会在子类的@BeforeClass注解方法执行前执行。

  • @Before注解修饰的方法会在每个测试方法执行前执行一次,父类@Before修饰的方法会在子类@Before修饰的方法执行前 执行

  • @After注解修饰的方法会在每个测试方法执行后执行一次,父类@After修饰的方法会在子类@After修饰的方法执行后执行。

  • @AfterClass注解修饰的方法(该方法要用static修饰)会在所有方法执行结束后执行一次,且也只执行一次,通常用来对资源进行释放,比如数据库连接的关闭等,无论测试用例里的其他方法有没有抛出异常,该方法最终都会被执行。而且父类中的被@AfterClass注解方法修饰的方法会在子类的@AfterClass注解修饰的方法执行之后才会被执行。

    package tech.pdai.junit4;

    import org.junit.*;

    /** * Standard Test. */
    public class StandardTest {

        @BeforeClass
        public static void beforeClass() {
            System.out.println("in before class");
        }

        @AfterClass
        public static void afterClass() {
            System.out.println("in after class");
        }

        @Before
        public void before() {
            System.out.println("in before");
        }

        @After
        public void after() {
            System.out.println("in after");
        }

        @Test
        public void testCase1() {
            System.out.println("in test case 1");
        }

        @Test
        public void testCase2() {
            System.out.println("in test case 2");
        }

    }

执行结果 dev-ut4-3.png

测试:禁用测试

@Ignore:暂不执行该方法;

    package tech.pdai.junit4;

    import org.junit.Ignore;
    import org.junit.Test;

    /** * Ignore Test. */
    public class IgnoreTest {

        /** * ignore. */
        @Ignore
        @Test
        public void ignoreTest(){
            System.out.println("ignore test");
        }
    }

执行结果 dev-ut4-2.png

测试:断言测试

  • 断言测试注解有哪些

断言描述void assertEquals([String message],expected value,actual value)断言两个值相等。值类型可能是int,short,long,byte,char,Object,第一个参数是一个可选字符串消息void assertTrue([String message],boolean condition)断言一个条件为真void assertFalse([String message],boolean condition)断言一个条件为假void assertNotNull([String message],java.lang.Object object)断言一个对象不为空(null)void assertNull([String message],java.lang.Object object)断言一个对象为空(null)void assertSame([String message],java.lang.Object expected,java.lang.Object actual)断言两个对象引用相同的对象void assertNotSame([String message],java.lang.Object unexpected,java.lang.Object actual)断言两个对象不是引用同一个对象void assertArrayEquals([String message],expectedArray,resultArray)断言预期数组和结果数组相等,数组类型可能是int,short,long,byte,char,Object* 简单测试

    package tech.pdai.junit4;

    import org.junit.Assert;
    import org.junit.Test;

    /** * Assertion Test. */
    public class AssertionTest {

        @Test
        public void test() {
            String obj1 = "junit";
            String obj2 = "junit";
            String obj3 = "test";
            String obj4 = "test";
            String obj5 = null;

            int var1 = 1;
            int var2 = 2;

            int[] array1 = {1, 2, 3};
            int[] array2 = {1, 2, 3};

            Assert.assertEquals(obj1, obj2);

            Assert.assertSame(obj3, obj4);
            Assert.assertNotSame(obj2, obj4);

            Assert.assertNotNull(obj1);
            Assert.assertNull(obj5);

            Assert.assertTrue(var1 < var2);
            Assert.assertFalse(var1 > var2);

            Assert.assertArrayEquals(array1, array2);

        }
    }

在以上类中我们可以看到,这些断言方法是可以工作的。

  • assertEquals() 如果比较的两个对象是相等的,此方法将正常返回;否则失败显示在JUnit的窗口测试将中止。

  • assertSame() 和 assertNotSame() 方法测试两个对象引用指向完全相同的对象。

  • assertNull() 和 assertNotNull() 方法测试一个变量是否为空或不为空(null)。

  • assertTrue() 和 assertFalse() 方法测试if条件或变量是true还是false。

  • assertArrayEquals() 将比较两个数组,如果它们相等,则该方法将继续进行不会发出错误。否则失败将显示在JUnit窗口和中止测试。

  • 更多测试,来自官网https://github.com/junit-team/junit4/wiki/Assertions

    package tech.pdai.junit4;

    import org.hamcrest.core.CombinableMatcher;
    import org.junit.Test;

    import java.util.Arrays;

    import static org.hamcrest.CoreMatchers.*;
    import static org.junit.Assert.*;

    /** * More Assertion Test from Junit-Team. */
    public class Assertion2Test {

        @Test
        public void testAssertArrayEquals() {
            byte[] expected = "trial".getBytes();
            byte[] actual = "trial".getBytes();
            assertArrayEquals("failure - byte arrays not same", expected, actual);
        }

        @Test
        public void testAssertEquals() {
            assertEquals("failure - strings are not equal", "text", "text");
        }

        @Test
        public void testAssertFalse() {
            assertFalse("failure - should be false", false);
        }

        @Test
        public void testAssertNotNull() {
            assertNotNull("should not be null", new Object());
        }

        @Test
        public void testAssertNotSame() {
            assertNotSame("should not be same Object", new Object(), new Object());
        }

        @Test
        public void testAssertNull() {
            assertNull("should be null", null);
        }

        @Test
        public void testAssertSame() {
            Integer aNumber = Integer.valueOf(768);
            assertSame("should be same", aNumber, aNumber);
        }