接口测试参数化方案

Posted TesterHome

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接口测试参数化方案相关的知识,希望对你有一定的参考价值。

缘由

12月23号去上海参加了论坛举办的活动,干货蛮多的,感觉别人家做的都好腻害,
其中Lego接口自动化测试印象挺深的,通过配置文件的方式生成接口测试用例,
因为恰好我们公司也准备做接口自动化的部分,之前还没有这部分

依赖

  1. rest-assured

  2. testng

两种方案

代码实现

这是我在参加活动前的方案,有同事推荐retrofit不错,自己写了一下,感觉不方便
在论坛上看到的rest-assured挺不错的,用起来方便的

@Slf4j@Listeners({InterfaceFailureHandle.class, Retry.class})public abstract class BaseInterfaceTest {

    public static Map<String, String> testParametersMap = new HashMap<>();

    static {

        /** 公共参数 **/
        testParametersMap.put("appKey", "android_lk98f83");
        testParametersMap.put("appTimestamp", String.valueOf(System.currentTimeMillis()));
        testParametersMap.put("appTypeId", "0");
        testParametersMap.put("appVersion", "6.12");
        testParametersMap.put("cookieId", "e0dc298b-2e2d-4f14-ab80-cfcce4471679");
        testParametersMap.put("countryCode", "SA");
        testParametersMap.put("currency", "SAR");
        testParametersMap.put("lang", "0");
        testParametersMap.put("terminalType", "1");

    }

    @BeforeClass
    @Parameters({"baseUrl", "appVersion"})
    public void init(@Optional String baseUrl, @Optional String appVersion) {
        log.debug("init");

        if (baseUrl == null) {
            baseUrl= "http://weekly.test.com";
        }
        if (appVersion != null) {
            testParametersMap.put("appVersion", appVersion);
        }

        RestAssured.baseURI = baseUrl;
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.requestSpecification = new RequestSpecBuilder().build().accept(JSON).contentType(JSON);
    }}
@Slf4j@Listeners({InterfaceFailureHandle.class})public class LoginNew extends BaseInterfaceTest{

    @Test
    public void loginWithUerNamePassword() {
        log.debug("actions");

        JSONObject jsonObject = new JSONObject();
        testParametersMap.put("userName", "carl@163.com");
        testParametersMap.put("password", "kkkkkk");
        jsonObject.putAll(testParametersMap);
        testParametersMap.put("sign", SignGen.getSign(jsonObject, SignGen.appSecret));
        jsonObject.putAll(testParametersMap);

        log.debug(jsonObject.toJSONString());

        given().body(jsonObject.toJSONString())
                .when().post("/user/login").then()
                .body("messageCode", is("0"),
                        "messageType", is(0));
    }}

参数配置实现

简单学习了一下yaml配置文件的写法,还是没完全搞清楚,
所以我选择写出javabean出来先,然后dump出来,再按dump出来的格式学着具体写

@Datapublic class APITestProject {

    private String baseUrl;
    private Map<String, Object> globalRequestParmeters = new HashMap<>();
    private Map<String, APITestSuite> testSuites = new HashMap<>();}
@Datapublic class APITestSuite {

    private String description;
    private List<APITestCase> testCaseList = new ArrayList<>();
    private Map<String, Object> testSuiteParameters = new HashMap<>();}
@Datapublic class APITestCase {

    private String name;
    private String description;
    private String apiUrl;
    private Map<String, String> sqlCommands = new HashMap<>();
    private Map<String, Object> requestParameters = new HashMap<>();
    private Map<String, Map<String, Object>> resultVerify = new HashMap<>();}

一个project下面有多个 testsuite,一个testsuite下面有多个用例,然后全局参数,测试套参数,用例参数,校验结果

!!APITestProjectbaseUrl: http://weekly.test.com#全局参数globalRequestParmeters:
  appVersion: '6.12'
  appTypeId: '0'
  countryCode: SA
  cookieId: e0dc298b-2e2d-4f14-ab80-cfcce4471679
  appKey: android_lk98f83
  currency: SAR
  lang: '0'
  appTimestamp: '1514204071822'
  terminalType: '1'#测试套集合testSuites:
  firstSuite:
    description: testSuiteDesc
    #测试用例集合
    testCaseList:
    - apiUrl: /user/login
      description: kkk
      name: loginNew
      requestParameters:
        password: kkkkkk
        userName: clark@163.com
      resultVerify:
        #结果校验
        is:
          messageType: 0
          messageCode: '0'
      #sql,在测试前或测试后做数据准备或还原,未完成
      sqlCommands: {}
    #测试套参数集合
    testSuiteParameters: {}
@Slf4j@Listeners({InterfaceFailureHandle.class, Retry.class})public class APITestExecutor {

    private static Map<String, Object> testParametersMap = new HashMap<>();
    private static APITestProject apiTestProject;

    static {

        Yaml yaml = new Yaml();
        try {
            apiTestProject = yaml.loadAs(new FileInputStream(new File("src\\main\\resources\\InterfaceTest.yaml")), APITestProject.class);
            testParametersMap.putAll(apiTestProject.getGlobalRequestParmeters());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            log.debug(e.getMessage());
        }
    }

    @BeforeMethod
    @Parameters({"baseUrl", "appVersion"})
    public void init(@Optional String baseUrl, @Optional String appVersion) {
        log.debug("init");

        if (baseUrl == null) {
            baseUrl = apiTestProject.getBaseUrl();
        }
        if (appVersion != null) {
            testParametersMap.put("appVersion", appVersion);
        }

        RestAssured.baseURI = baseUrl;
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.requestSpecification = new RequestSpecBuilder().build().accept(JSON).contentType(JSON);

    }

    @Test
    public void executor() {

        JSONObject jsonObject = new JSONObject();

        Map<String, APITestSuite> testSuites = apiTestProject.getTestSuites();
        for (Map.Entry<String, APITestSuite> entry : testSuites.entrySet()) {

            List<APITestCase> testCases = entry.getValue().getTestCaseList();
            for (APITestCase testCase : testCases) {
                testParametersMap.putAll(testCase.getRequestParameters());
                jsonObject.putAll(testParametersMap);

                testParametersMap.put("sign", SignGen.getSign(jsonObject, SignGen.appSecret));
                jsonObject.putAll(testParametersMap);

                log.debug(jsonObject.toJSONString());

                ValidatableResponse validatable = given().body(jsonObject.toJSONString()).when().post(testCase.getApiUrl()).then();

                Map<String, Map<String, Object>> resultVerify = testCase.getResultVerify();

                resultVerify.forEach((condition, v) -> {
                    v.forEach((key, value) -> {
                        try {
                            Matchers matches = new Matchers();
                            Matcher matcher = (Matcher) Matchers.class.getDeclaredMethod(condition, Object.class).invoke(matches, value);

                            validatable.body(key, matcher);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }

                    });
                });

                validatable.log().all();

            }
        }

    }}

读取yaml文件并遍历,执行测试

对比

代码:

  • 优点是写代码,灵活性高,特殊场景什么的都可以处理;

  • 缺点:如果100,1000条用例的话,重复代码非常多,后期维护可能很心累

配置文件:

  • 优点是结构清晰,轻量级

  • 缺点:未来可能出现特殊场景,随着用例数上去之后,很难通过修改配置文件的方式去兼容更多的场景

问题

  1. 如果yaml文件中有10条用例,在执行时是放在一个@Test方法中执行的,就是说第3条用例失败即全部失败了,如何进一步拆分

  2. rest-assured初始化需要7~8秒的时间,感觉有点长了

解决方案

  1. 通过代码来运行testNG的测试用例,读取用例后,放入队列中,每次执行时读取一条用例即可

    public class APITestRun {public static void main(String[] args) {
        for (int i=0; i<APITestUtils.getBlockingQueue().size(); i++) {
            TestNG testNG = new TestNG();
            testNG.setTestClasses(new Class[]{APITestExecutor.class});
            testNG.run();
        }}}
@Slf4j@Listeners({InterfaceFailureHandle.class, Retry.class})public class APITestExecutor {

    private APITestCase testCase;

    @BeforeMethod
    @Parameters({"baseURL"})
    public void init(@Optional String baseURL) {
        log.debug("init");

        if (baseURL == null) {
            baseURL = APITestUtils.getApiTestProject().getBaseURL();
        }

        RestAssured.baseURI = baseURL;
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.requestSpecification = new RequestSpecBuilder().build().accept(JSON).contentType(JSON);

        testCase = APITestUtils.getTestCase();
    }

    @Test
    public void executor() {

        JSONObject jsonObject = new JSONObject();
        jsonObject.putAll(APITestUtils.getApiTestProject().getGlobalRequestParmeters());
        jsonObject.putAll(testCase.getRequestParameters());

        String sign = SignGenerate.getSign(jsonObject, SignGenerate.appSecret);
        jsonObject.put("sign", sign);

        log.debug(jsonObject.toJSONString());

        ValidatableResponse validatableResponse = given().body(jsonObject.toJSONString()).when().post(testCase.getApiUrl()).then();

        Map<String, Map<String, Object>> resultVerify = testCase.getResponseVerify();

        resultVerify.forEach((condition, v) -> {
            v.forEach((key, value) -> {
                try {
                    Matchers matchers = new Matchers();
                    Matcher matcher = (Matcher) Matchers.class.getDeclaredMethod(condition, Object.class).invoke(matchers, value);
                    validatableResponse.body(key, matcher);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        });

        validatableResponse.log().all();

        updateTestCaseInfo();
    }

    /**     * 更新测试用例的名字和描述信息     */
    public void updateTestCaseInfo() {
        TestCaseEvent testCaseEvent = new TestCaseEvent() {
            @Override
            public void process(TestCaseResult testCaseResult) {
                log.debug("name:" + APITestUtils.getCurrentTestCase().getName());
                log.debug("description:" + APITestUtils.getCurrentTestCase().getDescription());
                testCaseResult.setName(APITestUtils.getCurrentTestCase().getName());
                ru.yandex.qatools.allure.model.Description description = new ru.yandex.qatools.allure.model.Description();
                description.setValue(APITestUtils.getCurrentTestCase().getDescription());
                testCaseResult.setDescription(description);//                testCaseResult.getLabels().add(new Label().withName("testSuite").withValue(""));
            }
        };

        APITestUtils.getAllure().fire(testCaseEvent);
    }}
@Slf4jpublic final class APITestUtils {

    private static APITestProject apiTestProject;
    private static BlockingQueue<APITestCase> blockingQueue = new LinkedBlockingQueue<>();
    private static APITestCase currentTestCase;
    private static Allure allure = Allure.LIFECYCLE;

    private APITestUtils() {

    }

    static {
        Yaml yaml = new Yaml();
        try {
            // 加载用例文件
            apiTestProject = yaml.loadAs(new FileInputStream(new File("src\\main\\resources\\InterfaceTest.yaml")), APITestProject.class);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            log.debug(e.getMessage());
        }

        /** 遍历用例并放入队例中 **/
        Map<String, APITestSuite> testSuites = apiTestProject.getTestSuites();
        for (Map.Entry<String, APITestSuite> entry : testSuites.entrySet()) {
            List<APITestCase> testCases = entry.getValue().getTestCaseList();
            blockingQueue.addAll(testCases);
        }
    }

    public static BlockingQueue<APITestCase> getBlockingQueue() {
        return blockingQueue;
    }

    public static APITestCase getTestCase() {
        currentTestCase = blockingQueue.poll();
        return currentTestCase;
    }

    public static APITestCase getCurrentTestCase() {
        return currentTestCase;
    }

    public static APITestProject getApiTestProject() {
        return apiTestProject;
    }

    public static Allure getAllure() {
        return allure;
    }

报告

其他小芝麻

  1. TestNG测试注解以及生命周期:
    @BeforeClass(执行一次)
    @BeforeMethod(N个Test 方法执行N次)
    @Test Test方法(此注解可能在类上表示多个,在方法表示一个)
    @AfterMethod(N个Test 方法执行N次)
    @AfterClass(执行一次)

  2. LinkedBlockingQueue
    poll: 若队列为空,返回null。
    remove:若队列为空,抛出NoSuchElementException异常。
    take:若队列为空,发生阻塞,等待有元素。

最后

看别人做的好漂亮,好厉害,自己动手尝试去做的时候,就感觉坑也不少的


以上是关于接口测试参数化方案的主要内容,如果未能解决你的问题,请参考以下文章

Jmeter 接口测试,参数值为列表,如何参数化?

Jmeter 接口测试,参数值为列表,如何参数化?

接口测试教程-jmeter

To Lanhe 小伙伴:Web接口测试, 参数化测试

接口测试学习之旅

Jmeter接口测试系列之参数化方法