后端——框架——测试框架——junit——测试案例


  测试案例的知识点有三个。

第一,  介绍测试案例相关的概念。

第二,  介绍测试案例的各种类别,

第三,  介绍测试案例的相关扩展,例如TestExecutionExceptionHandler,TestWatcher等。

1、概念

  引用原著中对Test Plan, Test Class, Test Method, Lifecycle Method的概念。

  Test Plan:Test plan is a hierarchical (and read-only) description of all engines, classes, and test methods that fit the LauncherDiscoveryRequest. The client can traverse the tree, retrieve details about a node, and get a link to the original source (like class, method, or file position). Every node in the test plan has a unique ID that can be used to invoke a particular test or group of tests.

Test Plan它代表一次运行计划,它是树形结构,每个节点都抽象为Node。每个Node都与实际的资源(测试类,测试方法,测试文件路径)关联,每个Node都有一个唯一的ID。 树形结构是只读的,可以通过遍历树形结构获取Node的细节信息。

Test Class:any top-level class, static member class, or @Nested class that contains at least one test method

       至少包含一个测试方法的外部类,内部类,使用@Nested标注的内部类。

Test Method:any instance method that is directly annotated or meta-annotated with @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate.

        有上述注解中任意一种的实例方法,每种注解都对应一种测试案例类别.

Lifecycle Method: any method that is directly annotated or meta-annotated with @BeforeAll, @AfterAll, @BeforeEach, @AfterEach

       有上述注解中任意一种的方法。

       以生活为例,TestPlan,Node是一种逻辑结构,它类似于一个公司的组织结构图,每个图上的一个节点对应一个具体的子公司或者部门。Test class, Test method,Lifecycle Method是物理结构,一个节点一一对应一个物理结构。

2、类别

测试案例的类别有@Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate五种。

2.1   Test

它表示普通的测试案例,较常见,易理解。略。

2.2   RepeatedTest

  它表示重复测试案例,value属性值表示重复的次数。它有特殊的属性。

currentRepetition, 当前为第几次执行。

totalRepetition,执行的总次数。

在方法上,可以添加它的独有参数RepetitionInfo,获取上述信息。

示例如下:

@RepeatedTest(3)
void repeatTest(RepetitionInfo repeat){
    System.out.println("repeated run test");
}

  它的使用场景通常用于多次调用相同方法时,需要跳转到不同的逻辑分支,例如

  1. 使用手机注册,若第一次,则创建用户,并获取授权等等,若第二次,则直接进入登陆。
  2. 当客户付费时,相同渠道达到特定次数时,给与优惠,例如免除手续费等等。

2.3   ParameterizedTest

  它表示提供一组模拟的数据,作为方法的参数。它必须提供模拟数据(方法参数)的方式。

当为单个参数时,参数来源有:

  1. 字面量,使用@ValueSource,它的值为字面量数组,例如参数为字符串,则为字符串数组。
  2. 特殊值,@NullSource, @EmptySource, @NullAndEmptySource。
  3. 枚举值,只适用于参数为枚举类型,@EnumSource。
  4. 方法返回值,当测试类不为PER_CLASS,必须是静态方法,使用@MethodSource,value属性指定方法名称。方法的返回值通常为Stream。T对应测试案例的参数类型。

当为多个参数时,参数来源有:

  1. 表格(@CsvSource),每一行,一个字符串,每个字符串使用逗号分隔,可以设置delimiter属性自定义分隔符,分隔之后的数组个数,类型与参数对应。
  2. @CsvSourceFile,指定表格文件,内容个数与@CsvSource相同。
  3. @ArgumentSource,ArgumentsProvider的实现类,它的返回值为Stream<? extends Arguments>。
  4. @MethodSource, 它的返回值为Stream。每一组arguments与测试案例的方法对应。

  示例如下:

@ParameterizedTest
@MethodSource("testArgument")
public void test(String key, String value) {
	System.out.println(key + " , " + value);
}

private static Stream testArgument() {
	return Stream.of(arguments("a", "b"), arguments("c", "d"));
}

  若提供的类型与方法参数类型不同时,还需要提供类型转换器,它类似于spring中的Converter,详细参考官网

  它的使用场景很广泛,例如测试接口,将参数定义为JSONObject,提供不同的报文。

2.4   TestFactory

  它表示动态的构建Test Plan,Test Plan是一棵树。它由一个或多个节点组成,每个节点对应具体的测试案例。

DynamicNode,它代表树上的一个节点。

DynamicContainer,它是容器,它代表一组节点,类似于文件夹与文件的关系。

DynamicTest,它代表一个具体的测试案例,若方法直接返回DynamicTest时,它与类对应的Node关联。

@TestFactory的方法返回值必须是DynamicNode对象。

示例如下:

第一种情况,返回DynamicTest,它对应top-level class的Node

@TestFactory
Collection test(){
    return Arrays.asList(
            DynamicTest.dynamicTest("test case 1", () -> System.out.println("one")),
            DynamicTest.dynamicTest("test case 2", () -> System.out.println("two")));
}

  第二种情况,返回DynamicContainer,它有N组DynamicNode,每组的数据结构为Map,其中key值为组名,value为Stream,Collection< DynamicNode>或者是单个的DynamicNode。可以理解为文件的目录结构。

@TestFactory
DynamicContainer test(){
    return DynamicContainer.dynamicContainer("group 1",Stream.of("one","two")
                                  .map(text -> DynamicTest.dynamicTest("test case 1", () -> System.out.println(text)))); }

2.5   TestTemplate

  通用的测试案例模板,通常由第三方测试类库开发者实现自己的测试类别。

测试类型必须使用@ExtendsWith注解,值为TestTemplateInvocationContextProvider接口的实现类。

接口的方法有两个。

第一个,supportsTestTemplate,是否支持当前TestTemplate,通常为true,可以添加一些判断。

第二个,provideTestTemplateInvocationContext,含义是提供测试类的上下文,它的返回值是Stream对象。

TestTemplateInvocationContext有两个方法。

第一个,getDisplayName,获取测试案例的显示名称。

第二个,getAddtionalExtension(),等价于给测试案例注册扩展

示例参考官网。使用频率较低。

3、扩展

本篇介绍与测试案例有紧密关系的扩展。

3.1   TestWatcher

TestWatcher类似于监听器。它有四个方法。

testDisabled,当不满足条件,被跳过时,触发。

testAborted,当发生中断时,触发。

testSuccessful,当测试案例方法运行成功时,触发一次。

testFailed,运行失败时,触发。

  示例,

public class TestWatcherSample implements TestWatcher {
    // log object
    private static Logger log = LoggerFactory.getLogger(TestWatcherSample.class);

    //
    @Override
    public void testDisabled(ExtensionContext context, Optional reason) {
        log.info("------------disabled start------------");
        log.info("displayName:{}", context.getDisplayName());
        log.info("tagName:{}", context.getTags().toString());
        log.info("className:{}, methodName:{}", context.getClass().getSimpleName(), context.getRequiredTestMethod().getName());
        log.info("reason:{}", reason.get());
    }

    @Override
    public void testSuccessful(ExtensionContext context) {
        log.info("------------successful start------------");
        log.info("className:{}, methodName:{}", context.getClass().getSimpleName(), context.getRequiredTestMethod().getName());
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable cause) {
        log.info("------------aborted start------------");
        log.info("className:{}, methodName:{}", context.getClass().getSimpleName(), context.getRequiredTestMethod().getName());
        log.info("throwable:{}", cause.getMessage());
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        log.info("------------failed start------------");
        log.info("className:{}, methodName:{}", context.getClass().getSimpleName(), context.getRequiredTestMethod().getName());
        log.info("throwable:{}", cause.getMessage());
    }
}


// Test case
@ExtendWith(TestWatcherSample.class)
public class WatchSample {
    // log object
    private static Logger log = LoggerFactory.getLogger(WatchSample.class);

    @Test
    public void test() {
        log.info("this is the sucessful case");
    }

    @Disabled
    @Test
    public void disabled() {
        log.info("this is the disabled case");
    }

    @Test
    public void failed() {
        log.info("this is the failed case");
        log.info(1 / 0 + "");
    }

    @Test
    public void testAborted() {
        log.info("this is the aborted case");
        Assumptions.assumeTrue(false);
        log.info("aborted");
    }
}

3.2   TestExecutionHandler

Junit将异常分为两种,

第一种是在测试案例方法运行期间发生的异常,此时实现TestExecutionExceptionHandler。

第二种是在生命周期方法中发生的异常,此时实现LifeCycleExecutionExceptionHandler。接口方法名称格式为

handleXXXMethodExcutionException。XXX为生命周期的方法。每个生命周期在接口中都有对应的方法。例如beforeAll,对应的处理方法为handlerBeforeAllMethodExecutionException。

示例

public class ExceptionHandler implements TestExecutionExceptionHandler {

    // log object
    private static Logger log = LoggerFactory.getLogger(ExceptionHandler.class);
@Override public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable { if(throwable instanceof ArithmeticException){ log.info("just ignore this exception"); return; } throw throwable; } }

  被异常处理器处理过之后未发生异常,则认为是成功的案例,触发testSuccessful,而不是testFailed。

3.3   ParameterResolver

  为测试案例提供参数。它有两类:

第一类,是从上下文中获取信息,并构建参数对象。参考TestInfoParameterResolver。

第二类,是创建参数,并将其放入上下文中,在测试案例方法中获取。参考RandomParametersExtension

  ParameterResolver,它主要有两个方法。

public interface ParameterResolver extends Extension {
  boolean supportsParameter(ParameterContext var1, ExtensionContext var2) throws ParameterResolutionException;   Object resolveParameter(ParameterContext var1, ExtensionContext var2) throws ParameterResolutionException; }

  supportsParameter是判断当前的ParameterResolver支持的参数。若返回true表示支持,false表示不支持。

resolveParameter生成参数类型的一个实例对象。

两个方法中的参数都是一样的。其中parameterContext获取参数相关的上下文,ExtensionContext获取整个Test Plan 运行的上下文。

3.3.1   ParameterContext

  获取位置:getIndex。

注解相关:

isAnnotated,判断参数上是否存在某个注解。

findAnnotation,获取参数上的注解。

findRepeatableAnnotation,获取参数上的注解,其类型必须是@Repeateable。

Executable获取构造器或者方法。

综合信息:getParameter,返回Parameter对象

3.3.1   ExtensionContext

  略。

相关