JUnit高效实践(下)

简介: :单元测试(Unit Testing)是指对软件中的最小可测试单元进行检查和验证,一般要根据实际情况去判定其具体含义,比如在 Java 语言中,单元可以指一个类或一个方法。JUnit 是一个 Java 语言简单、开源的单元测试框架,在 Java 开发中比较流行且常用的测试框架,希望读者能通过这篇文章来深入了解 JUnit 的特性与使用。


6. 忽略测试


public class DemoIgnoreTest {
    @Test
    @Ignore("This test method is ignored.")
    public void test() {
        System.out.println("DemoIgnoreTest.test");
    }
    @Test
    public void test2() {
        System.out.println("DemoIgnoreTest.test2");
    }
}


7. 超时测试


在测试方法上的 @Test 注解上添加 timeout 属性可以指定该测试方法的执行超时时间,如果测试方法执行的耗时超过了该时间,则会抛出 TestTimedOutException 异常。

public class DemoTimeoutTest {
    @Test(timeout = 100)
    public void test() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println("DemoTimeoutTest.test");
    }
}


8. 参数测试


在测试的过程中,我们经常会给定一些测试数据,来跑单元测试方法,这在测试场景中非常有用。参数测试使用 Parameterized 运行器来实现,首先定义一个静态方法,并且使用 @Parameters 注解,静态方法的返回值是数组或者列表,然后对应数据在测试类上定义字段,使用构造方法来注入测试数据。

@RunWith(Parameterized.class)
public class DemoParameterTest {
    private int input1;
    private int input2;
    public DemoParameterTest(int input1, int input2) {
        this.input1 = input1;
        this.input2 = input2;
    }
    @Parameters
    public static Integer[][] data() {
        return new Integer[][]{
                {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55}
        };
    }
    @Test
    public void test() {
        System.out.println("DemoParameterTest.test::" + input1 + "-" + input2);
    }
}

除了使用构造方法外,还可以在字段上面使用 @Parameter 注解来注入测试数据,在注解的属性中指定数据列与字段的对应,注意字段的访问权限必须是 public

@RunWith(Parameterized.class)
public class DemoParameterTest {
    @Parameter
    public int input1;
    @Parameter(1)
    public int input2;
    @Parameters
    public static Integer[][] data() {
        return new Integer[][]{
                {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55}
        };
    }
    @Test
    public void test() {
        System.out.println("DemoParameterTest.test::" + input1 + "-" + input2);
    }
}

有多少组数据,测试方法就会执行多少次,为了区分每个测试数据执行的展示结果,可以在 @Parameters 注解上使用 name 属性来指定测试结果命名,命名的规则如下:

  • {index}:当前参数的索引
  • {0}, {1}, …:第1个,第2个等等的参数值
@Parameters(name = "{index}:{0}-{1}"
)
public static Integer[][] data() {
    return new Integer[][]{
        {1, 11}, {2, 22}, {3, 33}, {4, 44}, {5, 55}
    };
}

例如上面示例在 IDEA 中执行的结果如下:

微信图片_20220519202039.png


9. 理论测试


和参数测试类似,我们也可以使用理论测试来指定测试数据。理论测试使用 Theories 运行器来实现,可以在测试类中定义常量字段来指定测试数据,在字段上使用 @DataPoint 注解来标记,然后在测试方法定义和字段类型一致的参数就能注入数据。

@RunWith(Theories.class)
public class DemoTheoriesTest {
    @DataPoint
    public static String GOOD_USERNAME = "CodeArtist";
    @DataPoint
    public static String USERNAME_WITH_SLASH = "Hello CodeArtist";
    @Theory
    public void test(String str) {
        System.out.println("DemoTheoriesTest.test::" + str);
    }
}

也可以直接在测试方法的参数上使用 @TestedOn 来指定数据,如果有多个参数,会运行参数组合的每一种情况。

@RunWith(Theories.class)
public class DemoTheoriesTest {
    @Theory
    public void test(@TestedOn(ints = {1, 2, 3}) int input1,
                     @TestedOn(ints = {11, 22, 33}) int input2) {
        System.out.println("DemoTheoriesTest.test::" + input1 + "-" + input2);
    }
}

除了前面的方法指定数据外,JUnit 也支持自定义注解来指定数据。自定义一个注解如下:

@Target(PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@ParametersSuppliedBy(BetweenSupplier.class)
public @interface Between {
    int first();
    int last();
}

实现 ParameterSupplier 接口编写自定义注解的规则实现如下:

public class BetweenSupplier extends ParameterSupplier {
    @Override
    public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable {
        List<PotentialAssignment> list = new ArrayList<>();
        Between between = sig.getAnnotation(Between.class);
        for (int i = between.first(); i <= between.last(); i++) {
            list.add(PotentialAssignment.forValue("between", i));
        }
        return list;
    }
}

使用自定义的注解来指定参数,也会运行所有参数组合的情况。

@RunWith(Theories.class)
public class DemoTheoriesTest {
    @Theory
    public void test(@Between(first = 1, last = 3) int input1,
                     @Between(first = 4, last = 6) int input2) {
        System.out.println("DemoTheoriesTest.test::" + input1 + "-" + input2);
    }
}


10. 测试规则


规则可以为测试类中的每个测试方法灵活地添加扩展或重定义测试方法的行为,开发人员可以使用 JUnit 内置规则或自定义测试规则,方法级规则在规则字段上使用 @Rule 注解来指定,类级规则在规则字段上使用 @ClassRule 来指定。


10.1 内置规则


ExternalResource

ExternalResource 规则类是一个抽象类,可以为测试环境提供外部资源的访问,比如在测试前建立一个访问(文件、套接字、服务、数据库连接等等),在测试后关闭一个访问。

public class DemoExternalResourceTest {
    @Rule
    public final ExternalResource externalResource = new ExternalResource() {
        @Override
        protected void before() {
            System.out.println("DemoExternalResourceTest.before");
        }
        @Override
        protected void after() {
            System.out.println("DemoExternalResourceTest.after");
        }
    };
    @Test
    public void test() {
        System.out.println("DemoExternalResourceTest.test");
    }
}
TemporaryFolder

TemporaryFolder 规则继承了 ExternalResource 抽象类,实现了在测试过程中创建临时文件或文件夹的功能,在测试过程中创建的文件都会在测试后自动删除。

public class DemoTemporaryFolderTest {
    @Rule
    public final TemporaryFolder folder = new TemporaryFolder();
    @Test
    public void test() throws IOException {
        File createdFile = folder.newFile("codeartist.txt");
        File createdFolder = folder.newFolder("CodeArtist");
        System.out.println("DemoTemporaryFolderTest.test#createdFile: " + createdFile);
        System.out.println("DemoTemporaryFolderTest.test#createdFolder: " + createdFolder);
    }
}
Verifier

Verifier 规则类是一个抽象类,它可以在测试方法运行完后执行一些逻辑,所以可以用来校验测试的结果。

public class DemoVerifierTest {
    private static String result = "";
    @Rule
    public final Verifier verifier = new Verifier() {
        @Override
        protected void verify() {
            result += "verify";
        }
    };
    @Test
    public void test() {
        System.out.println("DemoVerifierTest.test::" + result);
    }
    @Test
    public void test2() {
        System.out.println("DemoVerifierTest.test::" + result);
    }
}
ErrorCollector

ErrorCollector 规则继承了 Verifier 抽象类,它是一个错误收集器,在测试方法运行时,遇到第一个异常会继续执行,先收集所有的异常然后一次性报告异常信息。

public class DemoErrorCollectorTest {
    @Rule
    public final ErrorCollector collector = new ErrorCollector();
    @Test
    public void test() {
        try {
            System.out.println("DemoErrorCollectorTest.test::First exception");
            int a = 1 / 0;
        } catch (Exception e) {
            collector.addError(e);
        }
        try {
            System.out.println("DemoErrorCollectorTest.test::Second exception");
            String a = null;
            System.out.println(a.getBytes());
        } catch (Exception e) {
            collector.addError(e);
        }
    }
}
TestWatcher

TestWatcher 规则类是一个抽象类,它可以监视一个测试方法执行的生命周期动作,但不能修改,例如可以在测试开始时、成功执行时、执行结束时添加逻辑。

public class DemoTestWatcherTest {
    @Rule
    public final TestWatcher watcher = new TestWatcher() {
        @Override
        protected void starting(Description description) {
            System.out.println("DemoTestWatcherTest.starting");
        }
        @Override
        protected void succeeded(Description description) {
            System.out.println("DemoTestWatcherTest.succeeded");
        }
        @Override
        protected void finished(Description description) {
            System.out.println("DemoTestWatcherTest.finished");
        }
    };
    @Test
    public void test() {
        System.out.println("DemoTestWatcherTest.test");
    }
}
TestName

TestName 规则可以获取当前运行的测试方法的名称。

public class DemoTestNameTest {
    @Rule
    public final TestName testName = new TestName();
    @Test
    public void test() {
        System.out.println("DemoTestNameTest.test::" + testName.getMethodName());
    }
}
Timeout

Timeout 规则可以指定测试类中所有测试方法的超时时间。

public class DemoTimeoutTest {
    @Rule
    public final Timeout timeout = Timeout.seconds(1);
    @Test
    public void test() throws InterruptedException {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("DemoTimeoutTest.test");
    }
}
ExpectedException

ExpectedException 规则可以指定测试方法抛出预期的异常或存在预期的异常消息描述,如果没有达到预期会抛出 AssertionError

public class DemoExpectedExceptionTest {
    @Rule
    public final ExpectedException exception = ExpectedException.none();
    @Test
    public void test() {
        exception.expect(NullPointerException.class);
        exception.expectMessage("happened?");
        exception.expectMessage(startsWith("What"));
        throw new NullPointerException("What happened?");
    }
}
ClassRule

@ClassRule 注解扩展了 @Rule 的方法级规则,在定义的规则字段上需要添加static 修饰,类级别规则在测试类运行时执行,与测试方法无关。

public class DemoClassRuleTest {
    @ClassRule
    public static final ExternalResource r = new ExternalResource() {
        @Override
        protected void before() {
            System.out.println("DemoClassRuleTest.before");
        }
        @Override
        protected void after() {
            System.out.println("DemoClassRuleTest.after");
        }
    };
    @Test
    public void test() {
        System.out.println("DemoClassRuleTest.test");
    }
    @Test
    public void test2() {
        System.out.println("DemoClassRuleTest.test2");
    }
}
RuleChain

RuleChain 规则可以定义规则链,也就是说可以在当前测试类中定义多个规则。

public class DemoRuleChainTest {
    @Rule
    public final RuleChain chain = RuleChain
            .outerRule(new TestLogger("outer rule"))
            .around(new TestLogger("around1 rule"))
            .around(new TestLogger("around2 rule"));
    @Test
    public void test() {
        System.out.println("DemoRuleChainTest.test");
    }
}

10.2 自定义规则

我们通过实现 TestRule 接口来自定义规则,也可以继承内置的基础规则抽象类来实现。

public class TestLogger implements TestRule {
    private final String message;
    public TestLogger(String message) {
        this.message = message;
    }
    @Override
    public Statement apply(Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                System.out.println("TestLogger.evaluate::" + message);
                base.evaluate();
            }
        };
    }
}


11. 测试卡具


测试卡具可以在测试方法执行的前面或者后面执行一些逻辑,@Before 注解修饰的方法在所有测试方法执行前都会执行,相反 @After 注解修饰的方法在所有测试方法执行后都会执行,它们是方法级别的,而 @BeforeClass@AfterClass 注解修饰的方法是类级别的,方法必须添加 static 修饰,而且不管测试类中存在多个测试方法,它们在测试方法执行前或执行后只执行一次。

public class DemoFixtureTest {
    @BeforeClass
    public static void beforeClass() {
        System.out.println("DemoFixtureTest.beforeClass");
    }
    @AfterClass
    public static void afterClass() {
        System.out.println("DemoFixtureTest.afterClass");
    }
    @Before
    public void before() {
        System.out.println("DemoFixtureTest.before");
    }
    @After
    public void after() {
        System.out.println("DemoFixtureTest.after");
    }
    @Test
    public void test() {
        System.out.println("DemoFixtureTest.test");
    }
    @Test
    public void test2() {
        System.out.println("DemoFixtureTest.test2");
    }
}

测试卡具可以实现下面功能:

  • 在测试前准备输入数据,或配置 Mock 对象
  • 加载一个特定的数据库来准备数据
  • 为某些测试类创建一些初始化状态



目录
相关文章
|
4月前
|
XML Java 测试技术
TestNG 与 JUnit 测试框架:哪个更好?
【2月更文挑战第16天】
165 1
TestNG 与 JUnit 测试框架:哪个更好?
|
Java 测试技术 API
Junit5单元测试框架原理和实践
Junit5单元测试框架原理和实践
216 1
|
XML 测试技术 应用服务中间件
【高效编码】还在用Junit么? out啦!!!快来看看这个框架。。。。
您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的一键三连吧。小伙伴们,有啥想看的,想问的,欢迎积极留言,前面几篇文章我们详细介绍了测试框架Junit的使用。但是Junit也就诸多局限性。例如:不能分组测试,不支持多线程测试,不支持参数化测试等等。针对局限,这篇文章将介绍另外一种单元测试框架TestNG。
90 0
【高效编码】还在用Junit么? out啦!!!快来看看这个框架。。。。
|
测试技术
软件测试面试题:使用TestNG的优势是什么?
软件测试面试题:使用TestNG的优势是什么?
239 0
|
Java 测试技术 Android开发
Junit - 基础篇
Junit - 基础篇
192 0
Junit - 基础篇
|
XML 测试技术 数据格式
利器 | TestNG 与 Junit 对比,测试框架如何选择?
利器 | TestNG 与 Junit 对比,测试框架如何选择?
|
XML 测试技术 数据格式
利器 | TestNG 与 Junit 对比,测试框架如何选择?
TestNG 和 Junit 作为两大流行的测试框架,有哪些区别?各有哪些优缺点?该如何选择呢?这里简要总结下: ![](https://ceshiren.com/uploads/default/original/3X/7/3/731391a1eddb1e44ccb4a50bedbe0df0cbdaf138.png) ![](https://ceshiren.com/uploads/def
|
Java 测试技术 数据库连接
JUnit4教程+实践
JUnit是Java编程语言的单元测试框架,用于编写和可重复运行的自动化测试。
275 0
|
Java 测试技术
JUnit高效实践(上)
单元测试(Unit Testing)是指对软件中的最小可测试单元进行检查和验证,一般要根据实际情况去判定其具体含义,比如在 Java 语言中,单元可以指一个类或一个方法。JUnit 是一个 Java 语言简单、开源的单元测试框架,在 Java 开发中比较流行且常用的测试框架,希望读者能通过这篇文章来深入了解 JUnit 的特性与使用。
137 0
|
设计模式 IDE Java
Lombok高效实践
Lombok 是一种Java 实用工具,可以帮助开发人员消除冗长的重复代码,尤其是对于简单的 Java 对象。它在代码编译阶段就会自动生成相应的代码,所以对性能没有任何影响。
282 0