zoukankan      html  css  js  c++  java
  • 后端——框架——测试框架——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>。T对应测试案例的参数类型。

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

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

      示例如下:

    @ParameterizedTest
    @MethodSource("testArgument")
    public void test(String key, String value) {
    	System.out.println(key + " , " + value);
    }
    
    private static Stream<Arguments> 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<DynamicTest> 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<DynamicNode>,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>对象。

    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<String> 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

      略。

  • 相关阅读:
    [转]WIBKIT技术资料
    WebKit学习要点
    提高IOS开发效率的常用网站、开源类库及工具
    【浏览器那些基础】Android平台有那些CPU类型
    深刻的理解
    Spring Boot 最流行的 16 条实践解读,值得收藏!
    MyBatis动态SQL(认真看看, 以后写SQL就爽多了)
    为什么很多 SpringBoot 开发者放弃了 Tomcat,选择了 Undertow?
    朋友,别告诉我你懂分布式事务!
    分布式锁用 Redis 还是 Zookeeper?
  • 原文地址:https://www.cnblogs.com/rain144576/p/15580578.html
Copyright © 2011-2022 走看看