Cucumber测试框架增加失败重试机制后,如何把重复执行的结果过滤掉

目录

一 工具使用情况

二 cucumber结果生成流程

三 解决方案

1 爆改源码

2 自定义插件


一 工具使用情况

开发语言:java

cucumber版本:6.8.1

报告生成插件:net.masterthought:maven-cucumber-reporting:3.9.0

二 cucumber结果生成流程

        主要是在cucumber执行入口中,配置了内置的json结果的内置插件,后边再用业界使用较方泛的插件net.masterthought:maven-cucumber-reporting:3.9.0在基于这个json结果文件生成一个较可观的报告。

三 解决方案

1 爆改源码

        从github上下载cucumber-core的源码,找到 io.cucumber.core.plugin.JsonFormatter 这个文件,通读源码,发现修改handleTestCaseStarted这个方法就可以了,增加三行代码如下:

if (this.currentElementsList.size() > 0) {
    this.currentElementsList.removeIf(element -> element.get("id").equals(this.currentElementMap.get("id")));
}

        全部方法如下图所示,基本思路就是在写入下一条用例结果前,把之前已存入有相同用例的结果移除掉,只保存最后一次执行的结果。

         修改完后把这个包打到本地仓库,就可以使用了;但如果在项目团队中,还需要把这个上传到私库中才方便大家使用。

2 自定义插件

        Cucumber官方指导文档中介绍,支持自定义插件,介绍如下;大体意思是自定义的插件要实现或继承于一个标准的Fomatter接口,再通过使用--format就能使用了,都看明白了,但具体不知道怎么做......

         那我们查看cucumber执行入口,看看源码在cucumber执行上下文初始化过程中,能不能指定我们自定义的插件,如下

 

        终于找到这个指定入口了,需要在cucumber配置文件中通过cucumber.plugin指定一个自定义的插件来添加到执行上下文中,而我们查看内置的JsonFormatter也是继承自这个Plugin,完全符合这个要求,所以我们直接把内置的JsonFormatter源码拿过来,然后在配置文件中指定这个自定义的插件;但全考过来,里边有些使用的class不是public级别的,需要小改一下,但需要额外把 TestSourcesModel也考过来,以下为自定义的Json插件代码:

import io.cucumber.core.exception.ExceptionUtils;
import io.cucumber.messages.Messages;
import io.cucumber.messages.internal.com.google.gson.Gson;
import io.cucumber.messages.internal.com.google.gson.GsonBuilder;
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.event.Argument;
import io.cucumber.plugin.event.DataTableArgument;
import io.cucumber.plugin.event.DocStringArgument;
import io.cucumber.plugin.event.EmbedEvent;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.HookTestStep;
import io.cucumber.plugin.event.HookType;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.StepArgument;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseStarted;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestSourceRead;
import io.cucumber.plugin.event.TestStep;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.TestStepStarted;
import io.cucumber.plugin.event.WriteEvent;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;


public class Json2Formatter implements EventListener {
    private static final String before = "before";
    private static final String after = "after";
    private final List<Map<String, Object>> featureMaps = new ArrayList();
    private final Map<String, Object> currentBeforeStepHookList = new HashMap();
    private final Gson gson = (new GsonBuilder()).setPrettyPrinting().create();
    private final Writer writer;
    private final TestSourcesModel testSources = new TestSourcesModel();
    private URI currentFeatureFile;
    private List<Map<String, Object>> currentElementsList;
    private Map<String, Object> currentElementMap;
    private Map<String, Object> currentTestCaseMap;
    private List<Map<String, Object>> currentStepsList;
    private Map<String, Object> currentStepOrHookMap;

    @SuppressWarnings("WeakerAccess")
    public Json2Formatter(OutputStream out) {
        this.writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
    }

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestSourceRead.class, this::handleTestSourceRead);
        publisher.registerHandlerFor(TestCaseStarted.class, this::handleTestCaseStarted);
        publisher.registerHandlerFor(TestStepStarted.class, this::handleTestStepStarted);
        publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
        publisher.registerHandlerFor(WriteEvent.class, this::handleWrite);
        publisher.registerHandlerFor(EmbedEvent.class, this::handleEmbed);
        publisher.registerHandlerFor(TestRunFinished.class, this::finishReport);
    }


    private void handleTestSourceRead(TestSourceRead event) {
        this.testSources.addTestSourceReadEvent(event.getUri(), event);
    }

    private void handleTestCaseStarted(TestCaseStarted event) {
        if (this.currentFeatureFile == null || !this.currentFeatureFile.equals(event.getTestCase().getUri())) {
            this.currentFeatureFile = event.getTestCase().getUri();
            Map<String, Object> currentFeatureMap = this.createFeatureMap(event.getTestCase());
            this.featureMaps.add(currentFeatureMap);
            this.currentElementsList = (List)currentFeatureMap.get("elements");
        }

        this.currentTestCaseMap = this.createTestCase(event);
        if (this.testSources.hasBackground(this.currentFeatureFile, event.getTestCase().getLocation().getLine())) {
            this.currentElementMap = this.createBackground(event.getTestCase());
            this.currentElementsList.add(this.currentElementMap);
        } else {
            this.currentElementMap = this.currentTestCaseMap;
        }

        if (this.currentElementsList.size() > 0) {
            this.currentElementsList.removeIf(element -> element.get("id").equals(this.currentElementMap.get("id")));
        }

        this.currentElementsList.add(this.currentTestCaseMap);
        this.currentStepsList = (List)this.currentElementMap.get("steps");

    }

    private void handleTestStepStarted(TestStepStarted event) {
        if (event.getTestStep() instanceof PickleStepTestStep) {
            PickleStepTestStep testStep = (PickleStepTestStep)event.getTestStep();
            if (this.isFirstStepAfterBackground(testStep)) {
                this.currentElementMap = this.currentTestCaseMap;
                this.currentStepsList = (List)this.currentElementMap.get("steps");
            }

            this.currentStepOrHookMap = this.createTestStep(testStep);
            if (this.currentBeforeStepHookList.containsKey("before")) {
                this.currentStepOrHookMap.put("before", this.currentBeforeStepHookList.get("before"));
                this.currentBeforeStepHookList.clear();
            }

            this.currentStepsList.add(this.currentStepOrHookMap);
        } else {
            if (!(event.getTestStep() instanceof HookTestStep)) {
                throw new IllegalStateException();
            }

            HookTestStep hookTestStep = (HookTestStep)event.getTestStep();
            this.currentStepOrHookMap = this.createHookStep(hookTestStep);
            this.addHookStepToTestCaseMap(this.currentStepOrHookMap, hookTestStep.getHookType());
        }

    }

    private void handleTestStepFinished(TestStepFinished event) {
        this.currentStepOrHookMap.put("match", this.createMatchMap(event.getTestStep(), event.getResult()));
        this.currentStepOrHookMap.put("result", this.createResultMap(event.getResult()));
    }

    private void handleWrite(WriteEvent event) {
        this.addOutputToHookMap(event.getText());
    }

    private void handleEmbed(EmbedEvent event) {
        this.addEmbeddingToHookMap(event.getData(), event.getMediaType(), event.getName());
    }

    private void finishReport(TestRunFinished event) {
        Throwable exception = event.getResult().getError();
        if (exception != null) {
            this.featureMaps.add(this.createDummyFeatureForFailure(event));
        }

        this.gson.toJson(this.featureMaps, this.writer);

        try {
            this.writer.close();
        } catch (IOException var4) {
            throw new RuntimeException(var4);
        }
    }

    private Map<String, Object> createFeatureMap(TestCase testCase) {
        Map<String, Object> featureMap = new HashMap();
        featureMap.put("uri", TestSourcesModel.relativize(testCase.getUri()));
        featureMap.put("elements", new ArrayList());
        Messages.GherkinDocument.Feature feature = this.testSources.getFeature(testCase.getUri());
        if (feature != null) {
            featureMap.put("keyword", feature.getKeyword());
            featureMap.put("name", feature.getName());
            featureMap.put("description", feature.getDescription() != null ? feature.getDescription() : "");
            featureMap.put("line", feature.getLocation().getLine());
            featureMap.put("id", TestSourcesModel.convertToId(feature.getName()));
            featureMap.put("tags", feature.getTagsList().stream().map((tag) -> {
                Map<String, Object> json = new LinkedHashMap();
                json.put("name", tag.getName());
                json.put("type", "Tag");
                Map<String, Object> location = new LinkedHashMap();
                location.put("line", tag.getLocation().getLine());
                location.put("column", tag.getLocation().getColumn());
                json.put("location", location);
                return json;
            }).collect(Collectors.toList()));
        }

        return featureMap;
    }

    private Map<String, Object> createTestCase(TestCaseStarted event) {
        Map<String, Object> testCaseMap = new HashMap();
        testCaseMap.put("start_timestamp", this.getDateTimeFromTimeStamp(event.getInstant()));
        TestCase testCase = event.getTestCase();
        testCaseMap.put("name", testCase.getName());
        testCaseMap.put("line", testCase.getLine());
        testCaseMap.put("type", "scenario");
        TestSourcesModel.AstNode astNode = this.testSources.getAstNode(this.currentFeatureFile, testCase.getLine());
        if (astNode != null) {
            testCaseMap.put("id", TestSourcesModel.calculateId(astNode));
            Messages.GherkinDocument.Feature.Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode);
            testCaseMap.put("keyword", scenarioDefinition.getKeyword());
            testCaseMap.put("description", scenarioDefinition.getDescription() != null ? scenarioDefinition.getDescription() : "");
        }

        testCaseMap.put("steps", new ArrayList());
        if (!testCase.getTags().isEmpty()) {
            List<Map<String, Object>> tagList = new ArrayList();
            Iterator var6 = testCase.getTags().iterator();

            while(var6.hasNext()) {
                String tag = (String)var6.next();
                Map<String, Object> tagMap = new HashMap();
                tagMap.put("name", tag);
                tagList.add(tagMap);
            }

            testCaseMap.put("tags", tagList);
        }

        return testCaseMap;
    }

    private Map<String, Object> createBackground(TestCase testCase) {
        TestSourcesModel.AstNode astNode = this.testSources.getAstNode(this.currentFeatureFile, testCase.getLocation().getLine());
        if (astNode != null) {
            Messages.GherkinDocument.Feature.Background background = TestSourcesModel.getBackgroundForTestCase(astNode);
            Map<String, Object> testCaseMap = new HashMap();
            testCaseMap.put("name", background.getName());
            testCaseMap.put("line", background.getLocation().getLine());
            testCaseMap.put("type", "background");
            testCaseMap.put("keyword", background.getKeyword());
            testCaseMap.put("description", background.getDescription() != null ? background.getDescription() : "");
            testCaseMap.put("steps", new ArrayList());
            return testCaseMap;
        } else {
            return null;
        }
    }

    private boolean isFirstStepAfterBackground(PickleStepTestStep testStep) {
        TestSourcesModel.AstNode astNode = this.testSources.getAstNode(this.currentFeatureFile, testStep.getStepLine());
        if (astNode == null) {
            return false;
        } else {
            return this.currentElementMap != this.currentTestCaseMap && !TestSourcesModel.isBackgroundStep(astNode);
        }
    }

    private Map<String, Object> createTestStep(PickleStepTestStep testStep) {
        Map<String, Object> stepMap = new HashMap();
        stepMap.put("name", testStep.getStepText());
        stepMap.put("line", testStep.getStepLine());
        TestSourcesModel.AstNode astNode = this.testSources.getAstNode(this.currentFeatureFile, testStep.getStepLine());
        StepArgument argument = testStep.getStepArgument();
        if (argument != null) {
            if (argument instanceof DocStringArgument) {
                DocStringArgument docStringArgument = (DocStringArgument)argument;
                stepMap.put("doc_string", this.createDocStringMap(docStringArgument));
            } else if (argument instanceof DataTableArgument) {
                DataTableArgument dataTableArgument = (DataTableArgument)argument;
                stepMap.put("rows", this.createDataTableList(dataTableArgument));
            }
        }

        if (astNode != null) {
            Messages.GherkinDocument.Feature.Step step = (Messages.GherkinDocument.Feature.Step)astNode.node;
            stepMap.put("keyword", step.getKeyword());
        }

        return stepMap;
    }

    private Map<String, Object> createHookStep(HookTestStep hookTestStep) {
        return new HashMap();
    }

    private void addHookStepToTestCaseMap(Map<String, Object> currentStepOrHookMap, HookType hookType) {
        String hookName;
        if (hookType != HookType.AFTER && hookType != HookType.AFTER_STEP) {
            hookName = "before";
        } else {
            hookName = "after";
        }

        Map mapToAddTo;
        switch(hookType) {
            case BEFORE:
                mapToAddTo = this.currentTestCaseMap;
                break;
            case AFTER:
                mapToAddTo = this.currentTestCaseMap;
                break;
            case BEFORE_STEP:
                mapToAddTo = this.currentBeforeStepHookList;
                break;
            case AFTER_STEP:
                mapToAddTo = (Map)this.currentStepsList.get(this.currentStepsList.size() - 1);
                break;
            default:
                mapToAddTo = this.currentTestCaseMap;
        }

        if (!mapToAddTo.containsKey(hookName)) {
            mapToAddTo.put(hookName, new ArrayList());
        }

        ((List)mapToAddTo.get(hookName)).add(currentStepOrHookMap);
    }

    private Map<String, Object> createMatchMap(TestStep step, Result result) {
        Map<String, Object> matchMap = new HashMap();
        if (step instanceof PickleStepTestStep) {
            PickleStepTestStep testStep = (PickleStepTestStep)step;
            if (!testStep.getDefinitionArgument().isEmpty()) {
                List<Map<String, Object>> argumentList = new ArrayList();

                HashMap argumentMap;
                for(Iterator var6 = testStep.getDefinitionArgument().iterator(); var6.hasNext(); argumentList.add(argumentMap)) {
                    Argument argument = (Argument)var6.next();
                    argumentMap = new HashMap();
                    if (argument.getValue() != null) {
                        argumentMap.put("val", argument.getValue());
                        argumentMap.put("offset", argument.getStart());
                    }
                }

                matchMap.put("arguments", argumentList);
            }
        }

        if (!result.getStatus().is(Status.UNDEFINED)) {
            matchMap.put("location", step.getCodeLocation());
        }

        return matchMap;
    }

    private Map<String, Object> createResultMap(Result result) {
        Map<String, Object> resultMap = new HashMap();
        resultMap.put("status", result.getStatus().name().toLowerCase(Locale.ROOT));
        if (result.getError() != null) {
            resultMap.put("error_message", ExceptionUtils.printStackTrace(result.getError()));
        }

        if (!result.getDuration().isZero()) {
            resultMap.put("duration", result.getDuration().toNanos());
        }

        return resultMap;
    }

    private void addOutputToHookMap(String text) {
        if (!this.currentStepOrHookMap.containsKey("output")) {
            this.currentStepOrHookMap.put("output", new ArrayList());
        }

        ((List)this.currentStepOrHookMap.get("output")).add(text);
    }

    private void addEmbeddingToHookMap(byte[] data, String mediaType, String name) {
        if (!this.currentStepOrHookMap.containsKey("embeddings")) {
            this.currentStepOrHookMap.put("embeddings", new ArrayList());
        }

        Map<String, Object> embedMap = this.createEmbeddingMap(data, mediaType, name);
        ((List)this.currentStepOrHookMap.get("embeddings")).add(embedMap);
    }

    private Map<String, Object> createDummyFeatureForFailure(TestRunFinished event) {
        Throwable exception = event.getResult().getError();
        Map<String, Object> feature = new LinkedHashMap();
        feature.put("line", 1);
        Map<String, Object> scenario = new LinkedHashMap();
        feature.put("elements", Collections.singletonList(scenario));
        scenario.put("start_timestamp", this.getDateTimeFromTimeStamp(event.getInstant()));
        scenario.put("line", 2);
        scenario.put("name", "Could not execute Cucumber");
        scenario.put("description", "");
        scenario.put("id", "failure;could-not-execute-cucumber");
        scenario.put("type", "scenario");
        scenario.put("keyword", "Scenario");
        Map<String, Object> when = new LinkedHashMap();
        Map<String, Object> then = new LinkedHashMap();
        scenario.put("steps", Arrays.asList(when, then));
        Map<String, Object> whenMatch = new LinkedHashMap();
        when.put("result", whenMatch);
        whenMatch.put("duration", 0);
        whenMatch.put("status", "passed");
        when.put("line", 3);
        when.put("name", "Cucumber could not execute");
        whenMatch = new LinkedHashMap();
        when.put("match", whenMatch);
        whenMatch.put("arguments", new ArrayList());
        whenMatch.put("location", "io.cucumber.core.Failure.cucumber_could_not_execute()");
        when.put("keyword", "When ");
        Map<String, Object> thenMatch = new LinkedHashMap();
        then.put("result", thenMatch);
        thenMatch.put("duration", 0);
        thenMatch.put("error_message", exception.getMessage());
        thenMatch.put("status", "failed");
        then.put("line", 4);
        then.put("name", "Cucumber will report this error:");
        thenMatch = new LinkedHashMap();
        then.put("match", thenMatch);
        thenMatch.put("arguments", new ArrayList());
        thenMatch.put("location", "io.cucumber.core.Failure.cucumber_reports_this_error()");
        then.put("keyword", "Then ");
        feature.put("name", "Test run failed");
        feature.put("description", "There were errors during the execution");
        feature.put("id", "failure");
        feature.put("keyword", "Feature");
        feature.put("uri", "classpath:io/cucumber/core/failure.feature");
        feature.put("tags", new ArrayList());
        return feature;
    }

    private String getDateTimeFromTimeStamp(Instant instant) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneOffset.ofHours(8));
        return formatter.format(instant);
    }

    private Map<String, Object> createDocStringMap(DocStringArgument docString) {
        Map<String, Object> docStringMap = new HashMap();
        docStringMap.put("value", docString.getContent());
        docStringMap.put("line", docString.getLine());
        docStringMap.put("content_type", docString.getMediaType());
        return docStringMap;
    }

    private List<Map<String, List<String>>> createDataTableList(DataTableArgument argument) {
        List<Map<String, List<String>>> rowList = new ArrayList();
        Iterator var3 = argument.cells().iterator();

        while(var3.hasNext()) {
            List<String> row = (List)var3.next();
            Map<String, List<String>> rowMap = new HashMap();
            rowMap.put("cells", new ArrayList(row));
            rowList.add(rowMap);
        }

        return rowList;
    }

    private Map<String, Object> createEmbeddingMap(byte[] data, String mediaType, String name) {
        Map<String, Object> embedMap = new HashMap();
        embedMap.put("mime_type", mediaType);
        embedMap.put("data", Base64.getEncoder().encodeToString(data));
        if (name != null) {
            embedMap.put("name", name);
        }

        return embedMap;
    }
}

        增加配置文件cucumber.properties,增加配置项(主要就是自定义插件的package路径):cucumber.plugin=xxx.Json2Formatter:target/cucumber.json

        把执行入口之前配置的内置json任件去掉,到此为止,即大功告成,以下为验证结果,重试第三次成功的报告

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值