通用技术 [Java][算法] 记一次失败的尝试 -- 基于全排列算法实现场景遍历

bauul · 2019年05月05日 · 最后由 bauul 回复于 2019年05月06日 · 1607 次阅读

缘由

由于当下测试的业务是基于任务流的,就是说在任务 1 完成后,可能会同时触发任务 2 和任务 3,任务 2 和任务 3 的提交顺序没有彼此依赖关系,
与此同时我设计的接口用例是按层次编写的,像下面的 Demo,
我就在想如果基于全排列,去自动排列组合出所有的场景,然后遍历执行这些场景,应该挺有意思的,

在一个 testSetList 中,用例是按顺序执行 testCaseList 的,但在同一个 testCaseList 中的用例顺序是可以无序执行的,
就是说一个用例集合中的用例是无序的,但用例集合之间的是有序的。

{
  "projectName": "Demo",
  "testSetList": [
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseName": "login",
            "caseOwner": "lishu",
            "caseComment": "登录-胡斐"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "批发新款-提交"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-增补供应商"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-下单"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "翻单-增补供应商"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-查询"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-不通过"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "现货批发-修改订单"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "下单审核-通过"
          }
        }
      ]
    },
    {
      "testCaseList": [
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "文案任务-提交"
          }
        },
        {
          "caseInfo": {
            "caseComment": "雪山飞狐",
            "caseName": "商品列表-试穿"
          }
        }
      ]
    }
  ]
}

如何进行全排列?

public class Producer {

    public static void arrange(List<ApiTestCase> apiTestCaseList, int start, int len, List<List<ApiTestCase>> setResultApiTestCasesList) {
        if (start == len - 1) {
//            for (int i = 0; i < apiTestCaseList.size(); i++) {
//                System.out.print(apiTestCaseList.get(i).getCaseInfo().getCaseName() + "--" + apiTestCaseList.get(i).getMethod() + ", ");
//            }
//            System.out.println();
            CaseList caseList = new CaseList();
            caseList.setApiTestCaseList(apiTestCaseList);
            String temp = JSON.toJSONString(caseList);
            setResultApiTestCasesList.add(JSON.parseObject(temp, CaseList.class).getApiTestCaseList());

            return;
        }
        for (int i = start; i < len; i++) {
            ApiTestCase temp = apiTestCaseList.get(start);
            apiTestCaseList.set(start, apiTestCaseList.get(i));
            apiTestCaseList.set(i, temp);
            arrange(apiTestCaseList, start + 1, len, setResultApiTestCasesList);
            temp = apiTestCaseList.get(start);
            apiTestCaseList.set(start, apiTestCaseList.get(i));
            apiTestCaseList.set(i, temp);
        }
    }

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();

        String apiTestProjectStr = ApiTestUtils.loadCases(
                "/Users/xxxx/Downloads/workspace/java/demo/src/main/resources/demo.json");
        ApiTestProject apiTestProject = JSON.parseObject(apiTestProjectStr, ApiTestProject.class);
        LinkedList<ApiTestSet> apiTestSets = apiTestProject.getTestSetList();

        List<List<ApiTestCase>> resultApiTestCasesList = new LinkedList<>();

        for (ApiTestSet apiTestSet : apiTestSets) {
            System.out.print("this set's first case:" + apiTestSet.getTestCaseList().get(0).getCaseInfo().getCaseName());
            System.out.println(", size:" + apiTestSet.getTestCaseList().size());
            List<List<ApiTestCase>> setResultApiTestCasesList = new LinkedList<>();

            arrange(apiTestSet.getTestCaseList(), 0, apiTestSet.getTestCaseList().size(), setResultApiTestCasesList);

            if (setResultApiTestCasesList.size() == 1) {
                if (resultApiTestCasesList.size() == 0) {
                    resultApiTestCasesList.add(setResultApiTestCasesList.get(0));
                } else {
                    for (List<ApiTestCase> resultList : resultApiTestCasesList) {
                        for (List<ApiTestCase> setResultList : setResultApiTestCasesList) {
                            resultList.addAll(setResultList);
                        }
                    }
                }
            } else {
                ResultList resultList = new ResultList();
                resultList.setResultApiTestCasesList(new LinkedList<>());
                resultList.getResultApiTestCasesList().addAll(resultApiTestCasesList);
                // 对结果进行扩容
                for (int i=0; i<setResultApiTestCasesList.size()-1; i++) {
                    String str = JSON.toJSONString(resultList);
                    ResultList tmp =  JSON.parseObject(str, ResultList.class);
                    resultApiTestCasesList.addAll(tmp.getResultApiTestCasesList());
                }
                System.out.println("resultApiTestCasesList size:" + resultApiTestCasesList.size());
                // 将结果分别填充至原场景末尾
                for (int i=0; i<resultApiTestCasesList.size(); i++) {
                    resultApiTestCasesList.get(i).addAll(setResultApiTestCasesList.get(i % setResultApiTestCasesList.size()));
                }
            }

            setResultApiTestCasesList.clear();
        }

        System.out.println(resultApiTestCasesList.size());
        for (List<ApiTestCase> list : resultApiTestCasesList) {
            for (ApiTestCase apiTestCase : list) {
                System.out.print(apiTestCase.getCaseInfo().getCaseName() + " ");
            }
            System.out.println();
        }

        System.out.println("time:" + (System.currentTimeMillis() - start));
    }
}

基本上是抄的别人的基于递归的全排列算法,自己改了改,基本上是搞定了

结果:

login 批发新款-提交 现货批发-增补供应商 现货批发-下单 翻单-增补供应商 下单审核-查询 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 翻单-增补供应商 下单审核-不通过 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-查询 翻单-增补供应商 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-查询 下单审核-不通过 翻单-增补供应商 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-不通过 下单审核-查询 翻单-增补供应商 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-不通过 翻单-增补供应商 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 翻单-增补供应商 下单审核-查询 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 翻单-增补供应商 下单审核-不通过 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-查询 翻单-增补供应商 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-查询 下单审核-不通过 翻单-增补供应商 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-不通过 下单审核-查询 翻单-增补供应商 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-不通过 翻单-增补供应商 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 翻单-增补供应商 下单审核-查询 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 翻单-增补供应商 下单审核-不通过 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-查询 翻单-增补供应商 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-查询 下单审核-不通过 翻单-增补供应商 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-不通过 下单审核-查询 翻单-增补供应商 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-不通过 翻单-增补供应商 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 翻单-增补供应商 下单审核-查询 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 翻单-增补供应商 下单审核-不通过 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-查询 翻单-增补供应商 下单审核-不通过 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-查询 下单审核-不通过 翻单-增补供应商 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 
login 批发新款-提交 现货批发-增补供应商 现货批发-下单 下单审核-不通过 下单审核-查询 翻单-增补供应商 现货批发-修改订单 下单审核-通过 文案任务-提交 商品列表-试穿 
login 批发新款-提交 现货批发-下单 现货批发-增补供应商 下单审核-不通过 翻单-增补供应商 下单审核-查询 现货批发-修改订单 下单审核-通过 商品列表-试穿 文案任务-提交 

踩到的坑

  1. 我也知道全排列的场景十分的多,但大部分业务流最多同时执行的也就 3,4 个场景,全排列的话也就 6,24 个,
    一开始还是没觉得会有多少,直到做完了以后才发现那不是一般的多,多的有算出来 18000 多的,直接撑到 OOM 了
    所以,一些小的场景还是可以玩一玩的,复杂的就算了吧。。。

  2. 如何对 list 进行复制?
    一开始试了各种 java 原生方法,复制出来的结果都是一模一样的,
    就是说 list.get(0) 与 list.get(1) 的==结果竟然是 true,导致修改其中一个,其他的也变掉了,
    最后查了网上的资料,基本上只能用序列化与反序列化了。

更多思考

  1. 采用随机对比的方法?
  2. get 接口单独放置,不参与排列组合,每次 post 提交完成后,调一遍?
  3. 将生成的组合丢到队列中去,慢慢跑
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

👍 666,如果按笛卡尔积来设计场景,其实也丢失了测试设计的精髓。何况这些场景对应的期望结果也是海量的,不容易搞

simple 回复

嗯,需要再结合业务深入思考一下,
另外从接口请求类型角度考虑,
比如,有 AB 两个接口,当这两个都是 GET 接口时,视为同一种场景,
总的来说,需要把总的量给控制在一定范围内来跑

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册