JUnit 单元测试框架简单示例和最佳实践

Dec 26, 2021 阅读(749)

标签: 测试 JUnit


什么是 JUnit ?

JUnit 是Java 单元测试框架最常用的框架之一。JUnit是一个用于编写和运行测试的框架,简单易学。每个测试都是一个方法,包含特定场景下将执行的部分代码。比较预期输出和实际输出,以实现代码验证。

JUnit 简单实例

测试类通常包含三个阶段:


1. 准备;

2. 测试;

3. 清理


下面列举了一个单元测试的小案例:

import org.junit.*;

import java.util.Arrays;
import java.util.List;

public class FriendshipsTest {

    Friendships friendships;

    //一、 测试准备数据的方法
    @BeforeClass
    public static void beforeClass() {
        // @BeforeClass 指定的方法仅在执行类中的测试方法前执行一次
        // 非常适合用于执行大部分乃至全部测试都要求的一般性准备工作。
    }


    //一、 测试准备数据的方法
    @Before
    public void before() {
        // @Before 指定的方法将在每个测试方法前运行。

        friendships = new Friendships();
        friendships.makeFriends("Joe", "Audrey");
        friendships.makeFriends("Joe", "Peter");
        friendships.makeFriends("Joe", "Michael");
        friendships.makeFriends("Joe", "Britney");
        friendships.makeFriends("Joe", "Paul");
    }

    //二、 执行实际测试的方法
    @Test
    public void alexDoesNotHaveFriends() {
        Assert.assertTrue("Alex does not have friends", friendships.getFriendsList("Alex").isEmpty());
    }

    //二、 执行实际测试的方法
    @Test
    public void joeHas5Friends() {
        Assert.assertEquals("Joe has 5 friends", 5, friendships.getFriendsList("Joe").size());
    }

    //二、 执行实际测试的方法
    @Test
    public void joeIsFriendWithEveryone() {
        List<String> friendsOfJoe = Arrays.asList("Audrey", "Peter", "Michael", "Britney", "Paul");
        Assert.assertTrue(
                friendships.getFriendsList("Joe").containsAll(friendsOfJoe)
        );
    }


    //三、 执行完成后清理现场的方法
    @AfterClass
    public static void afterClass() {
        // @AfterClass 指定的方法仅在执行类中的测试方法后执行一次
    }

    //三、 执行完成后清理现场的方法
    @After
    public void after() {
        // @After 指定的方法将在每个测试方法后运行。
    }
}

要测试的逻辑代码:

import java.util.*;

public class Friendships {
    private final Map<String, List<String>> friendships = new HashMap<>();

    public void makeFriends(String person1, String person2) {
        addFriend(person1, person2);
        addFriend(person2, person1);
    }

    public List<String> getFriendsList(String person) {
        if (!friendships.containsKey(person)) {
            return Collections.emptyList();
        }
        return friendships.get(person);
    }
    
    public boolean areFriends(String person1, String person2) {
        return friendships.containsKey(person1)
                && friendships.get(person1).contains(person2);
    }

    private void addFriend(String person, String friend) {
        if (!friendships.containsKey(person)) {
            friendships.put(person, new ArrayList<String>());
        }
        List<String> friends = friendships.get(person);
        if (!friends.contains(friend)) {
            friends.add(friend);
        }
    }
}

单元测试旨在对一小块代码进行测试。

image.png

最佳实践

命名约定

1. 命名约定有助于更好地组织测试,从而让开发人员更容易测试。另一个好处是,很多工具都遵守这些约定(不遵守的话工具可能没办法执行输出正常的结果)。

2. 将实现代码(src/main/java)和测试代码 (src/test/java)分开,好处避免不小心将测试和产品二级制文件一起打包,很多构建工具都明确要求测试位于特定的源代码目录;

3. 测试类和实现类放在同一个包中。有助于更快找到代码。

4. 以类似于受测类的方式给测试类命名。有助于快速找到测试类。

5. 给测试方法指定描述性名称。有助于明白测试的目标。


流程

1. 先编写测试再写实现代码;

2. 仅在测试失败后才编写新代码;

3. 每次修改实现代码后,都再次运行所有测试;

4. 仅当所有测试都通过后才编写新测试;

5. 仅当测试都通过后才重构;


开发实践

1. 编写让测试能够通过的最简单的代码;

2. 为了更早的澄清测试目的,先编写断言,在编写操作;

3. 最大限度减少每个测试中的断言;

4. 不要让测试依赖其他测试;

5. 测试的运行速度必须快;

6. 使用测试替身,好处减少代码依赖并提高测试的执行速度;

7. 使用 set-up 和 tear-down 语义的方法;

8. 让测试更清晰不要在测试类中使用基类;


工具

1. 确保测试覆盖每个角落,使用代码覆盖率和持续集成(CI)工具;

2. 测试驱动开发(TDD) 和 行为驱动开发(BDD)结合使用;



MongoDB学习园