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

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

最早提到接口测试的优点时,有一个就是执行效率提升,可能是 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 条回复 时间 点赞

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

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

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

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

没看明白意义在哪

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

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

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

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

CKL的思考 回复

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

lazyBoy 回复

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

leixs 回复

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

木小白 回复

谢谢

Stay 回复

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

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

微凉 回复

这是个需求。

king 回复

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

FunTester 回复

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

lazyBoy 回复

我的锅,造成误会了

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