专栏文章 智能测试平台之文本相似度校验

泰斯特 · 2019年11月29日 · 最后由 泰斯特 回复于 2021年02月25日 · 10577 次阅读
本帖已被设为精华帖!

起源

我们都知道 (不知道也没关系),接口测试就是验证接口响应结果符不符合预期的一个验证过程。其中接口测试又分为人工测试和自动化测试。人工测试是使用工具/程序先去发送接口请求,然后用肉眼去验证接口的返回结果。而自动化测试则是完全由程序去执行并验证结果。

笔者在自动化测试的实践中发现,当接口返回数据因业务实际需求变动时 (一般是业务初期,啥都不确定),测试工程师需要花大量时间去修改接口响应结果的验证数据。而当返回数据频繁变动时,这就让人比较崩溃了。

举个极端栗子:

确定的需求:

接口 1-1 返回: {'greeting': '你好,欢迎你来到智能测试平台,希望你过得开心!'}
接口 1-1 校验: assert_true(greeting == '你好,欢迎你来到智能测试平台,希望你过得开心!')

这时候需求变了:

接口 1-2 返回: {'greeting': '您好,欢迎你来到智能测试平台,希望你过得开心!'}
(于是) 接口 1-2 校验: assert_true(greeting == '您好,欢迎你来到智能测试平台,希望你过得开心!')

这时候需求又变了:

接口 1-3 返回: {'greeting': '您好,欢迎您来到智能测试平台,希望您过得开心!'}
接口 1-3 校验 (os:谁知道他什么时候又要改回去,那么是时候上正则了):
regex = re.compile('你 | 您好,欢迎你 | 您来到智能测试平台,希望你 | 您过得开心!')
assert_not_none(re.match(regex, greeting))

这时候需求再次变更:

接口 1-4 返回: {'greeting': 'XXX 先生/女士,欢迎您来到我的智能测试平台,希望您今天过得开心!'}
接口 1-4 校验: ???

这时候,一个想法闪过了我的脑海,既然接口微调前和微调后的返回结果很可能在表达上是相似的,那么有没有可能使用 文本相似度 去校验结果是否符合预期呢?

这有什么不可能的?于是笔者开始了行动。

实现

模型选择

这还用问,当然是选择开源的模型啦,笔者选择了谷歌最强 NLP 模型 BERT

下载地址

https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip

下载 server&client:

pip install bert-serving-server # server
pip install bert-serving-client # client, independent of bert-serving-server

启动模型:

bert-serving-start -model_dir C://Users/Admin/Downloads/chinese_L-12_H-768_A-12/ -num_worker=4

最佳实践:

运行代码:

from bert_serving.client import BertClient
bc = BertClient()
test = bc.encode(['你好', '您好'])
print(test)

控制台输出:

[[ 0.28940186 -0.13572685  0.07591164 ... -0.14091228  0.5463005
  -0.3011805 ]
 [ 0.22952817  0.15336302 -0.18656345 ... -0.40034887  0.12984493
  -0.34498438]]

Process finished with exit code 0

相似度算法

好的,既然我们已经能够成功将中文文本转化成了 向量 ,那么我们接下来可以使用 余弦夹角 来求得文本之间的相似度。

笔者作为数学爱好者,当然得 从头证明且实现一遍 给读者看。

首先我们先利用三角形得出一个关于三角形三边与夹角的关系式:

然后根据上式求得两个向量间的夹角与向量之间的关系式:

然后我们就可以通过这个公式求得两个向量之间的 余弦夹角

简易代码实现

import numpy as np
class Nlper:

    def __init__(self, bert_client):
        self.bert_client = bert_client

    def get_text_similarity(self, base_text, compaired_text, algorithm='cosine'):
        if isinstance(algorithm, str) and algorithm.lower() == 'cosine':
            arrays = self.bert_client.encode([base_text, compaired_text])
            norm_1 = np.linalg.norm(arrays[0])
            norm_2 = np.linalg.norm(arrays[1])
            dot_product = np.dot(arrays[0], arrays[1])
            similarity = round(0.5 + 0.5 * (dot_product / (norm_1 * norm_2)), 2)
            return similarity

这里实现了一个简单的 Nlper 类,初始化 Nlper 对象时传入 bert 模型,然后通过 get_text_similarity 方法即可求得两个文本之间的相似度。方法内部实现使用了非常方便的 numpy 库,最后返回结果前将余弦区间 [-1,1] 映射至了 [0,1] 。

最佳实践

运行代码

if __name__ == '__main__':
    from bert_serving.client import BertClient
    bc = BertClient()
    nlper = Nlper(bert_client=bc)
    similarity = nlper.get_text_similarity('你好', '您好')
    print(similarity)

控制台输出

0.83

Process finished with exit code 0

平台集成

后面的工作就是将这套算法集成到测试平台上咯。过程就不在这赘述了,我们直接演示一下具体使用。

我们直接拿本机的登录接口进行一次演示,首先我们得创建一条用例:

然后输入错误的用户名以及密码进行一次测试,于是成功获取到了接口返回的数据结构:

然后我们再次进入用例修改页面,将接口返回中的data抽取为全局变量,然后点击 『智能相似度校验』,在文本一中填入 \${data}1,文本二中填入 \${data} , 目标相似度中填入: 1(强行把他变成了一个永远无法通过的用例)

然后我们点击测试,可以发现测试并没有通过 (能通过就有鬼了哦) ,可以在下图看到算法计算出来的文本相似度为 0.94。嗯,合理。

我们再次修改用例,(已知接口正确返回的数据为 「用户名/密码错误!」),将文本二改为 「用户名或密码错误!」、目标相似度调整为 0.95

执行测试后我们可以看到测试已通过,说明两个文本的相似度达到了0.95以上。

至此目前的一个小成果就演示完毕啦。

虽然说这不一定真的能够给测试带来特别显著的提升,但是至少在不断尝试创新的过程中笔者慢慢感受到人工智能是能够影响测试、给予测试更多可能性的。

结尾

欢迎大家关注我的专栏、公众号「智能测试开发」

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

图片都挂了,点击跳转都是 403 ,估计是防盗链了。建议修正下?

陈恒捷 回复

已修正😅

泰斯特 回复

别用简书做图床了

恒温 回复

那我重新弄吧

陈恒捷 将本帖设为了精华贴 12月06日 00:02

预期相似度太玄学了。另外文本相似度和语义相似度是两码事,除非结合起来我觉得才有点搞头。文本的测试上弄统计学,我始终觉得有点虚。测试不就是看那不相似的么。。。

Ouroboros 回复

语义相似度是可以有的😏

之前研究线上日志聚合时,也调研过相似度,有两种方法:
一种是简单的,python 标准库里有个 difflib,也可以用来比较两个文本的相似度,像这样:

def similarity(str1, str2) -> float:
    '''获取两个字符串的相似度 [0~1]'''
    sm = difflib.SequenceMatcher(None, str1, str2)
    return sm.ratio()

只是要不断的调整阈值,挺难定义的。(最后还是被我给废掉了,原因是我统计崩溃日志时,同一种原因的崩溃,但在两处位置调用,从 bug 的角度讲,这是两个位置的 bug。但从文本相似的角度讲,这是一个 bug。结果碰上个也不太细心的开发,最终 bug 没修复彻底)
一种难点的,也是使用 google 的算法
只说下思路:
1、使用 google 的 simHash 算法,获取文本的唯一指纹信息
2、比较两个指纹的 Haming 距离,当距离小于 3 时即认为相似
但实际使用时,当文本长度越长,对有差别的内容检测就越模糊,虽然可以通过加权的方式调整,但实际上仍很麻烦

我比较同意作者的思路,没事就应该多折腾尝试,兴许就发现更多的测试思路和巧,不过也同意@Ouroboros 的观点,测试就看不那么相似的,如同我遇到的那个问题,两个相似度极高的崩溃 log,它是两个 bug

prettfool 回复

多折腾没坏处😏

不同类型文本相似度的阈值如何设置,人为经验吗,还是统一一个阈值

挺好的尝试~
直接用 BERT 的预训练模型做语义相似度在通用领域下应该效果还可以的。
但是在专业领域的词汇下就比较玄学了。

不错,探索新思路了

呆呆 回复

这个就需要靠经验了

大东 回复

哈哈是的,只能说是个尝试。

john 回复

这个也是比较好落地的哈哈。其实还有一大堆想法

这个直接用编辑距离算法是不是更简单些啊😉

JamesChung 回复

当然。

在做流量回放,数据对比可以借鉴楼主,感谢分享

81—1 回复

能帮到你就好😀

之前遇到跟 LZ 一样的问题,我用的是 difflib.SequenceMatcher,完全够用了...小巧轻便

想不到看到了向量

需求变更的时候,产品经理会对产品设计做对应的更改,开发工程师会对设计实现做对应的更改,测试工程师会对测试方案、测试用例做对应的更改。那么自动化测试工程师就应该对测试脚本做对应的更改,此处的对应,就是指测试脚本需要和测试用例严格对应,而不应该是相似。

接口的设计 对应是业务实现的抽象
接口的测试的核心;是检查业务变更点是否全面生效以及业务相关影响点检查
接口的返回,特别是 msg 描述 不是接口测试的核心内容
想法很好~用在搜索算法匹配上面测试很好

在上家公司碰到过类似问题,进去之后接手了一个 RTF 框架写的接口测试用例(10000+ 的用例库,一家做 GIS 的还比较不错的公司,涉及到很多瓦片数据),断言方式全部是全量断言,并且字段非常多(没有使用关键字断言),而断言里面有个别字段是变化的,这样就导致每次跑完用例(2 天时间),就会出现 2000 多个失败用例,每跑一次需要人肉花费 3 个工作日去排错(因为有些是因为返回变了,有些确实是有问题,如:404、500),当时给领导提了建议使用 jieba 库去做相似度对比(相似度可以设置一定阈值),但是领导太固执,非要在短时间内搞重构。。实在没办法,只有走人了,走之前把相似度对比的脚本输出给他们了。。

sky 回复

404 500 应该可以放到断言的前半部分~,后半部分使用关键词正则校验或者相似度去断言具体返回值

泰斯特 回复

是的,我给的方案,先判断 404、500,如果不是还会判断是否有些关键字,比如 Error、Exception,如果没有就进行相似度对比,相似度达到一定程度,就认为通过。

说你笨吧,你还能想到这样的方法。说你聪明吧,你又特别费事。。。

ys 回复

用在这确实不一定合适,现在觉得用于做日志分析更好

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