概览

Junit 4

  • 用Description这个类描述 一个测试,以前是用 TestCase,Testsuite
  • Runner 类 有
    • getDescription
    • run
    • testCount 是从Descirption中获取
  • ParentRunner

    • run方法的实现
      • RunNotifier 入参,告知进度用,可以添加 监听
      • 这里调Statement的evaluate,这里statemment会把 beforeclass, afterclass, 和classRules 注解的satement带进来 ```java public void run(final RunNotifier notifier) { // … try {
        1. Statement statement = classBlock(notifier);
        2. statement.evaluate();
        } catch (AssumptionViolatedException e) {

    // ….. protected Statement classBlock(final RunNotifier notifier) {

      Statement statement = childrenInvoker(notifier);
      if (!areAllChildrenIgnored()) {
          statement = withBeforeClasses(statement);
          statement = withAfterClasses(statement);
          statement = withClassRules(statement);
      }
      return statement;
    

    } ```

    - Statement 里这里未实现evaluate
    - childrenInvoker     会用 RunnerScheduler 调用 runChild,通过getFilteredChildren
    

�获取的child 依次调用, runChild是抽象方法

  • 这里 childrenLock 锁在实现filter 方法和sort 方法,及getFilteredChildren使用
    • BlockJUnit4ClassRunner
  • it is now the default test class runner, but it should have exactly
    the same behavior as the old test class runner ({
    @code JUnit4ClassRunner}).
  • 但有些变化
    • 再执行流程插入新操作有更简单的实现
  • 重要实现runChild
    • 获得Description 实例,describeChild-》Description.createTestDescription ```java protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) {
      notifier.fireTestIgnored(description);  // 忽略掉ignore注解的方法
      
      } else {
      runLeaf(methodBlock(method), description, notifier);
      
      } }

//……. protected Description describeChild(FrameworkMethod method) { Description description = methodDescriptions.get(method);

    if (description == null) {
        description = Description.createTestDescription(getTestClass().getJavaClass(),
                testName(method), method.getAnnotations());
        methodDescriptions.putIfAbsent(method, description);
    }

    return description;
}

   - MethodBlock //这里有关键的逻辑来组装 运行的statement
         - 这里加入了 各种注解方法的 statement
         - 这里会用createTest创建测试类
```java
protected Statement methodBlock(FrameworkMethod method) {
        Object test;
        try {
            test = new ReflectiveCallable() {
                @Override
                protected Object runReflectiveCall() throws Throwable {
                    return createTest();
                }
            }.run();
        } catch (Throwable e) {
            return new Fail(e);
        }

        Statement statement = methodInvoker(method, test);
        statement = possiblyExpectingExceptions(method, test, statement);
        statement = withPotentialTimeout(method, test, statement);
        statement = withBefores(method, test, statement);
        statement = withAfters(method, test, statement);
        statement = withRules(method, test, statement);
        return statement;
    }
  • runLeaf实现

    • 通知 和执行
    • notifier
    • statement
      protected final void runLeaf(Statement statement, Description description,
          RunNotifier notifier) {
      EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
      eachNotifier.fireTestStarted();
      try {
          statement.evaluate();
      } catch (AssumptionViolatedException e) {
         //...//
      
  • InvokeMethod ,

    • 这里的 传入runLeaf的是 InokeMethod,Statement的子类
    • 它的 evaluate 是通过FrameworkMethod 的invokeExplosively 实现的
    • 而invokeExplosively 是通过反射包 Method的invoke方法实现的


ZeroCodeUnitRunner

  • 继承关系
    • BlockJUnit4ClassRunner
    • ParentRunner 实现 Filterable, Sortable 接口
    • Runner 实现接口 Describable
  • run 方法实现
    • 主要还是调用父类的run
    • 添加了一个 生成报告的listener
  • runChild

    • 覆盖了父类的实现
    • 识别 @JsonTestCase 与 @Scenario,有的调用 runLeafJsonTest
    • 没有 走老路 runLeafJUnitTest ```java protected void runChild(FrameworkMethod method, RunNotifier notifier) {

      final Description description = describeChild(method); JsonTestCase jsonTestCaseAnno = method.getMethod().getAnnotation(JsonTestCase.class); if(jsonTestCaseAnno == null){

         jsonTestCaseAnno = evalScenarioToJsonTestCase(method.getMethod().getAnnotation(Scenario.class));
      

      }

      if (isIgnored(method)) {

         notifier.fireTestIgnored(description);
      

      } else if (jsonTestCaseAnno != null) {

         runLeafJsonTest(notifier, description, jsonTestCaseAnno);
      

      } else {

         // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
         // It is an usual Junit test, not the JSON test case
         // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
         runLeafJUnitTest(methodBlock(method), description, notifier);
      

      }



- runLeafJsonTest
   - 配置文件转java实例,    
      - smartUtils.scenarioFileToJava(currentTestCase, ScenarioSpec.class);
      - jackson databind ,    ObjectMapper readValue    //TODO  
```java
public <T> T scenarioFileToJava(String scenarioFile, Class<T> clazz) throws IOException {
        if (scenarioFile.endsWith(".yml") || scenarioFile.endsWith(".yaml")) {
            return yamlMapper.readValue(readYamlAsString(scenarioFile), clazz);
        }

        return mapper.readValue(readJsonAsString(scenarioFile), clazz);
    }
  • multiStepsRunner.runScenario
    • ZeroCodeMultiStepsScenarioRunner 是个接口,行为由初始化塞入的实现类实现

  • notifier.fireTestStarted(description); notifier.fireTestFailure(new Failure(description, ioEx)); notifier.fireTestFinished(description);

    private void runLeafJsonTest(RunNotifier notifier, Description description, JsonTestCase jsonTestCaseAnno) {
       if (jsonTestCaseAnno != null) {
           currentTestCase = jsonTestCaseAnno.value();
       }
    
       notifier.fireTestStarted(description);
    
       LOGGER.debug("### Running currentTestCase : " + currentTestCase);
    
       ScenarioSpec child = null;
       try {
           child = smartUtils.scenarioFileToJava(currentTestCase, ScenarioSpec.class);
    
           LOGGER.debug("### Found currentTestCase : -" + child);
    
           passed = multiStepsRunner.runScenario(child, notifier, description);
    
       } catch (Exception ioEx) {
           ioEx.printStackTrace();
           notifier.fireTestFailure(new Failure(description, ioEx));
       }
    
       testRunCompleted = true;
    
       if (passed) {
           LOGGER.info(String.format("\n**FINISHED executing all Steps for [%s] **.\nSteps were:%s",
                   child.getScenarioName(),
                   child.getSteps().stream()
                           .map(step -> step.getName() == null ? step.getId() : step.getName())
                           .collect(Collectors.toList())));
       }
    
       notifier.fireTestFinished(description);
    }
    

ZeroCodeMultiStepsScenarioRunnerImpl

runScenario

  • ioWriterBuilder 写报告
  • resultReportBuilder 报告
  • parameterizedProcessor.resolveParameterized 解析 Scenario参数
  • executeSteps

    • 遍历Scenario 每个step
    • 对每个step执行 executeRetryWithSteps

      • 解析出引用的step,重新组装step

        //....
        thisStep = extFileProcessor.resolveExtJsonFile(thisStep);
        List<Step> thisSteps = extFileProcessor.createFromStepFile(thisStep, thisStep.getId());
        if(null == thisSteps || thisSteps.isEmpty()) thisSteps.add(thisStep);
        //....
        
      • 对每个step执行executeRetry

重头戏executeRetry

  • StepExecutionState 存储step的运行状态
  • stepExecutionState.addRequest(resolvedRequestJson); 把解析出的json存到请求
  • 获取重试信息
  • 执行 executionResult = executeApi(logPrefixRelationshipId, thisStep, resolvedRequestJson, scenarioExecutionState);
  • logging repsonse
  • handle assertion
  • logging assertion
  • 根据重试信息判断是否继续循环
  • 根据是否忽略,处理失败与成功 assersion
  • Build step report for each step. 在finally 里

    executeApi

  • 根据 apiType 调用不同方法

    • java
    • restful
    • kafka
  • apiExecutor 封装的执行方法分别执行

    ApiServiceExecutorImpl //apiExecutor的实现

  • 使用 google guice 注入

  • JavaMethodExecutor的实现类 JavaMethodExecutorImp
  • HttpApiExecutor 的实现类 HttpApiExecutorImp
  • BasicKafkaClient









参考