专栏文章 当我遇到 10 亿参数组合

FunTester · 2022年10月24日 · 最后由 FunTester 回复于 2022年11月04日 · 8816 次阅读

最早提到接口测试的优点时,有一个就是执行效率提升,可能是 UI 层面执行的 N 倍。但是今天我要分享的这个案例这个优点的升级版本。

某个接口参数倒是不多,但是每个参数的范围略大,最大的将近 500 个枚举范围,小的也是 20 个。如果把所有参数组合穷举完,粗略估计可能 10 亿级别的。

需求就是要把这部分所有参数组合都遍历进行测试,然后我就开始了踩坑了。

初版方案

一开始的想法就是多个循环嵌套,然后并发发起请求,实现起来非常简单方便。如下:

@Log4j2
class TT extends MonitorRT {

    static void main(String[] args) {
        ["types参数集合"].each {
            def type = it
            ["id类型集合"].each {
                def id = it
                2.upto(99) {
                    def a = it
                    2.upto(99) {
                        def b = it
                        2.upto(99) {
                            def c = it
                            def params = new JSONObject()
                            params.id = id
                            params.endTime = 0
                            params.type = type
                            params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
                            fun {
                                getHttpResponse(getHttpGet(url,params))
                            }
                        }
                    }
                }
            }
        }
    }
}

但是方案的缺陷显而易见。

  1. 数量太大,导致后面的异步任务直接被线程池拒绝
  2. 无法控制 QPS 和并发数

针对这第一个问题,我是增加了异步线程池等待队列的长度,可以我发现了新的问题,就是内存压力太大,这个会在后面的中也遇到。

升级版

针对存在第二个问题,我回归到性能测试框架中,通过动态调整 QPS 的功能来调整 QPS 或者并发数,这里我选择了 QPS,这个更容易更可控。我的思路是,先把所有参数遍历一遍,存在一个 List 当中,然后在去遍历这个 List,通过动态 QPS 压测模型把所有请求发出去。


static void main(String[] args) {
    def list = []
    ["types参数集合"].each {
        def type = it
        ["id类型集合"].each {
            def id = it
            2.upto(99) {
                def a = it
                2.upto(99) {
                    def b = it
                    2.upto(99) {
                        def c = it
                        def params = new JSONObject()
                        params.id = id
                        params.endTime = 0
                        params.type = type
                        params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
                    }
                }
            }
        }
    }
    AtomicInteger index = new AtomicInteger()
    def test = {
        def increment = index.getAndIncrement()
        if (increment >= list.size()) FunQpsConcurrent.stop()
        else getHttpResponse(getHttpGet(url, list.get(increment)))
    }
    new FunQpsConcurrent(test,"遍历10亿参数组合").start()
}

但是新的问题立马就来了,当我运行改代码的时候,发现本机的 CPU 疯狂飙升,仔细看了一下,原来是 GC 导致的。存放这么多的数据,内存撑不住了。下面就着手解决内存的问题,这里参考10 亿条日志回放 chronicle 性能测试中的思路。

终版

这里用到了线程安全的队列java.util.concurrent.LinkedBlockingQueue以及对应长度的等待功能,再配合异步生成请求参数,基本上完美解决需求。这里依旧使用休眠 1s 来进行缓冲,避免队列长度过大,只有队列长度足够 1s 的 2 倍消费即可。

static void main(String[] args) {
    def ps = new LinkedBlockingQueue()
    fun {
        ["types参数集合"].each {
            def type = it
            ["id类型集合"].each {
                def id = it
                2.upto(99) {
                    def a = it
                    2.upto(99) {
                        def b = it
                        2.upto(99) {
                            def c = it
                            def params = new JSONObject()
                            params.id = id
                            params.endTime = 0
                            params.type = type
                            params.paramMap = parse("{\"a\":\"${a}\",\"b\":\"$b\",\"c\":\"$c\"}")
                            if (ps.size() > 10_0000) sleep(1.0)
                            ps.put(params)
                        }
                    }
                }
            }
        }
    }
    AtomicInteger index = new AtomicInteger()
    def test = {
        def params = ps.poll(100, TimeUnit.MILLISECONDS)
        if (params == null) FunQpsConcurrent.stop()
        else getHttpResponse(getHttpGet(url, params))
    }
    new FunQpsConcurrent(test, "遍历10亿参数组合").start()
}

随着对队列的学习和使用,最近自己也想写一个 10 亿级别的日志回放功能,到时候对比chronicle看看性能如何,敬请期待。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 18 条回复 时间 点赞
lazyBoy 回复

我的锅,造成误会了

FunTester 回复

文章背景写的是接口测试。。

king 回复

这是个需求,不是测试场景。也不是测试用例。

微凉 回复

这是个需求。

没遇到这种场景,这是个需求,不是测试场景。

Stay 回复

这是个需求,实现的意义就是满足需求。

木小白 回复

谢谢

leixs 回复

超过 1 小时,小于 4 小时,没太关注具体时间。

lazyBoy 回复

这个类似清理脏数据,需求就是穷举所有参数组合。不存在等价。不是测试用例。

CKL的思考 回复

业务上有需要,类似清空脏数据

猜想:
1.是不是作者想表达,这个工具可以满足穷举的测试用例覆盖,功能很强大
2.KPI 功能?

但是
我们做事情,需要有目的,有价值,有意义,而不是炫耀
可能你现在做的这些就跟二楼说那样,出现问题了,你怎么排查,这其中发费的代价有多大,如果不去分析出现的问题,那做的意义何在

没看出来意思是什么,为了证明接口自动化的价值?

想问下,你们测试的时候,也会考虑这 10 亿左右的用例吗?

没看明白意义在哪

这样穷尽的测试大概需要多少时间呢

“10 亿” 中得有多少是等价 case...

看着好像是测试完全了,但如果这 10 亿中,有 1w 个结果报错,谁来排查,又怎么排查,属于是管杀不管埋了。
可以看下精准测试相关的落地实践

其实没太看明白,为什么要去遍历这所有的参数组合?有什么实际的意义?

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