基于cucumber二次开发的测试框架整理

来源:互联网 发布:公安网络报警平台 编辑:程序博客网 时间:2024/06/05 09:27

最近接触了一个基于cucumber开发的测试框架,感觉挺不错的。想把整个框架的设计以及实现具体记录下,已方便自己后面的查询和参考。

想想主要从以下几个方面来阐述:

一、框架基本介绍

   框架展示形式基于cucumber,对于cucumber的使用不再多说可以百度出很多介绍,该框架基于cucumber+spring+testng。下面就基于修改地方简单描述:

    1、本框架基于cucumber可以灵活操作变量,将变量级别分为 全局变量、feature级变量,测试人员可以自定义变量,也可以在关键字中灵活的保存为对应级别的变量已方便后面的操作。

     2、运行多套环境,多套数据。只需要配置对应的环境配置。例如 测试环境 配置为qa 联调环境配置为int,即可完成环境之间的切换(FileHandle.FILE_EXT)

    3、封装多个关键字。可以直接使用 如 http请求 dubbo 请求 操作数据库的关键字....(这个不是重点,可以自己不断的完善补充)

    5、集成spring,可以方便的使用spring的依赖注入(这个也不是重点,cucumber集成spring的包cucumber-spring。需要特别注意的是spring的配置文件名称必须为cucumber.xml

    4、清晰完善的测试报告(这个也不是重点,cucumber测试报告有很多插件..cucumber-reporting-2.2.0.jar)


二、框架的设计思路以及代码实现


如下图为针对cucumber主要改造的设计图




运行的用例集成cucumber-testng的抽象方法AbstractTestNGCucumberTests,该抽象方法主要有四个方法,上面的图中缺少一个dataProvider方法,主要是运行过程中数据提供者,上图中的三个方法非常明了:

                            1、BeforeClass 运行前的初始化 在这个步骤中假如我们自己的全局变量以及一些初始化工作TestContext。

                            2、feature 运行对应feature,数据由dataProvide提供。在这个步骤中针对每个feature加入feature变量hookUtil.beforeFeature(cucumberFeature);

                            3、根据方法配置的测试报告路径,生成对应的测试报告


先上下上面描述的代码:

1、运行测试的类

@Test
@CucumberOptions(features={"src/main/resources/feature/dubbotest/demo.feature"},glue={"com.tom.test"},plugin = {"pretty", "html:./target/cucumber","json:./target/cucumber/report.json"},tags={})
public class SingleTest  extends SpecTestNgCuke  {

}

2、SpecTestNgCuke类===等价AbstractTestNGCucumberTests
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.tom.utils.HookUtil;
import com.tom.utils.TestContext;

import cucumber.api.CucumberOptions;
import cucumber.api.testng.CucumberFeatureWrapper;
import net.masterthought.cucumber.Configuration;
import net.masterthought.cucumber.ReportBuilder;

public class SpecTestNgCuke {
    private SpecCucumberRunner testNGCucumberRunner;
    private File jsonPath=null;

    @BeforeClass(alwaysRun = true)
    public void setUpClass() throws Exception {
        TestContext.getInstance().init();
        Class<? extends SpecTestNgCuke> clazz=this.getClass();
        testNGCucumberRunner = new SpecCucumberRunner(clazz);
        //拿注解
        CucumberOptions options=clazz.getAnnotation(CucumberOptions.class);
        for(String plugin:options.plugin()){
             //判断是json
            if(plugin.trim().startsWith("json")){
                jsonPath=new File(plugin.split(":")[1]);
            }
        }
    }

    @Test(groups = "cucumber", description = "Runs Cucumber Feature", dataProvider = "features")
    public void feature(CucumberFeatureWrapper cucumberFeature) {
        HookUtil hookUtil = new HookUtil();
        hookUtil.beforeFeature(cucumberFeature);
        try {
            testNGCucumberRunner.runCucumber(cucumberFeature.getCucumberFeature());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            throw e;
        } finally {
            hookUtil.afterFeature(cucumberFeature);
        }
    }

    /**
     * @return returns two dimensional array of {@link CucumberFeatureWrapper}
     *         objects.
     */
    @DataProvider
    public Object[][] features() {
        return testNGCucumberRunner.provideFeatures();
    }

    @AfterClass
    public void tearDownClass() throws Exception {
        testNGCucumberRunner.finish();
        //生成报告
        File reportOutputDirectory = new File("target/cucumber");
        List<String> jsonFiles = new ArrayList<>();
        jsonFiles.add(jsonPath.getAbsolutePath());

        String projectName = "cuke";

        Configuration configuration = new Configuration(reportOutputDirectory, projectName);

        ReportBuilder reportBuilder = new ReportBuilder(jsonFiles, configuration);
        reportBuilder.generateReports();
    }

}



3、重要核心类 TestContext 片段,该类为单例。


public class TestContext {
    private static Logger logger = (Logger) LoggerFactory.getLogger(TestContext.class);

    private static TestContext testContext = null;
//    private boolean internalScenario = false;
    Map<String, Object> scenarioParas = new HashMap<>();   //定义变量保存在一个MAP中
    Map<String, Object> featureParas = new HashMap<>();  //定义变量保存在一个MAP中
    Map<String, Object> globalParas = new HashMap<>();  //定义变量保存在一个MAP中

    public final String LAST_RESPONSE_STRING = "last_response_string";
    private DubboHandle dubboHandle;
    private FileHandle fileHandle;
    private ExceptionHandle exceptionHandle;
    private MQProducer mqProducer;
    private OutputHandle outputHandle;
    private RSAHandle rsaHandle;

    private TestContext() {
    }

    public void init() throws Exception {
        dubboHandle = new DubboHandle();
        fileHandle = new FileHandle();
        exceptionHandle = new ExceptionHandle();
        rsaHandle=new RSAHandle();
        fileHandle.init();
        dubboHandle.init(fileHandle.getFileExt());
        this.outputHandle = new OutputHandle();
        setMqProducer(new MQProducer());
        rsaHandle.init();
        // 初始化全局参数
        try {
            putGlobalParameters(fileHandle.loadParaFile(Thread.currentThread().getContextClassLoader()
                    .getResource("global/common." + fileHandle.getFileExt()).getPath()));
        } catch (Exception e) {
            // TODO Auto-generated catch block
            logger.warn("全局参数文件,gloal/common" + fileHandle.getFileExt()+ "不存在");
        }
    }

    /**
     * 获取实例
     *
     * @return
     * @throws Exception
     */
    public static TestContext getInstance() {
        if (testContext == null) {
            try {
                testContext = new TestContext();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                logger.error("没有找到 全局变量文件 \n" + e.getMessage());
            }
        }
        return testContext;
    }


4、通过java关键字解析是否变量是否在各个级别中

/**
     * 将字符串中含有 ${}或 $()的替换成值,分三种情况,一种是${}完全的,一种是在中间,还有$()这种,统一变成 json字符串
     *
     * @param toParse
     * @return 对象返回json格式,简单类型直接返回
     */
    public String parseParameter(String toParse) {
        String result = toParse;
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
        Matcher matcher = pattern.matcher(toParse);
        while (matcher.find()) {
            String para = matcher.group();
            // 判断方法
            String pname = matcher.group(1);
            String value = null;
            if (pname.contains("(") && pname.contains(")")) {
                Pattern methodPattern = Pattern.compile("(.+?)\\((.*?)\\)");
                Matcher methodMatcher = methodPattern.matcher(pname.trim());
                if (methodMatcher.find()) {
                    // 先不考虑传参数的情况,
                    String methodName = methodMatcher.group(1);
                    String orValues=methodMatcher.group(2).trim();
                    String[] keyValues=orValues.split("\\|");
                    if(keyValues[0].isEmpty()){
                        keyValues=new String[0];
                    }
                    String className = null;
                    Class clazz = null;
                    try {
                        if (!methodName.contains(".")) {
                            clazz = FunctionUtil.class;
                        } else {
                            // 包名 com.tom.test.Function.test()
                            className = methodName.substring(0, methodName.lastIndexOf("."));
                            clazz = Class.forName(className);
                        }
                        methodName = methodName.substring(methodName.lastIndexOf(".") + 1);
                        List<Class> types=new ArrayList<>();
                        List<Object> values=new ArrayList<>();
                        for(int i=0;i<keyValues.length;i++){
                            String[] kv=keyValues[i].split("=");
                            types.add(ClassUtils._forName(kv[0]));
                            values.add(JSON.parse(kv[1], ClassUtils._forName(kv[0])));
                        }
                        Method method = clazz.getMethod(methodName, types.toArray(new Class[types.size()]));
                        value=method.invoke(clazz.newInstance(), values.toArray()).toString();
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("方法:" + pname + " 无法找到");
                    } catch (InstantiationException e) {
                        throw new RuntimeException("类:" + className + " 构造失败");
                    } catch (ParseException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } else {
                Object obj = TestContext.getInstance().getParameter(para);
                if (obj instanceof String) {
                    value = obj.toString();
                } else {
                    value = new JsonUtil().objectToString(obj);
                }
            }
            result = result.replace(para, value);
        }

四、依赖的jar包以及源码地址

参考依赖的jar包

<dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-testng</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.8.17</version>
        </dependency>

      <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>1.2.4</version>
        </dependency>

源码git地址:

https://github.com/simple365/Pineapple


原创粉丝点击