推薦一款開源的Diffy自動化測試框架
1. 前言
軟件測試是軟件開發(fā)生命周期一個十分重要的環(huán)節(jié),測試工作開展的好壞,很大程度上決定了產品質量的好壞,但軟件產品隨著版本的持續(xù)迭代,功能日益增多,系統(tǒng)愈加復雜,而從質量保障的角度,除了要保障好每次新增、優(yōu)化的產品質量外,還需要確認新增或修改的功能不影響之前已存在的功能。若要進行產品功能全量回歸,這個測試的工作量將會非常巨大。同時因為是回歸,可能幾百甚至上千用例中才會發(fā)現(xiàn)一個問題,甚至一個問題也沒有,測試投入工作的時間與最終的收益不成比例。
因此如何在有限的時間、人力投入下,有效、高效的保證產品回歸測試的質量,也一度成為了行業(yè)老司機以及團隊管理者頭疼的問題!

而今天的主角Diffy則為上述問題提供了較好的解決方案。它基于穩(wěn)定版本和它副本的輸出,對候選版本的輸出進行嚴格對比,以檢查候選版本是否正確,大大降低了回歸工作量。
接下來,讓我們詳細了解一下Diffy的工作原理,以及結合實戰(zhàn)演練帶大家感受一下它的魅力。
2. 關于Diffy
關于Diffy,公號此前發(fā)表過一篇文章:
推薦一款Diffy:Twitter的開源自動化測試工具
有過詳細介紹,之前不了解的讀者,可詳細閱讀一下。
簡單來理解,Diffy是一個開源的自動化測試工具,是一種自動Diff測試技術。它能夠自動檢測基于Apache Thrift或者基于HTTP的服務。通過同時運行新/老代碼,對比運行結果,發(fā)現(xiàn)潛在bug。并且使用Diffy,只需要進行簡單的配置,而不需要再編寫測試代碼。
3. Diffy工作原理
在整個測試開展過程中,Diffy需要部署三個版本的系統(tǒng),以實現(xiàn)它的噪聲過濾和對比功能,它們分別是:
- 候選版本(candidate):該版本為待測版本,有著最新待測代碼。
- 穩(wěn)定版本(primary):該版本通常是已經上線版本,或者是已知功能正常的版本。
- 穩(wěn)定版本副本(secondary):該版本是穩(wěn)定版本的副本,和穩(wěn)定版本運行相同的代碼,主要用于排除噪聲。
而Diffy主要職責充當了一個前置代理服務的角色,它能夠將來源請求分發(fā)到不同版本的系統(tǒng)中去,通過對各個版本系統(tǒng)的輸出進行對比,做出最終的結論。
Diffy整個工作原理流程圖如下:

說明:
diffy本身作為一個代理服務(proxy),需要人為構造或引流http請求,發(fā)到proxy代理服務中。- 當proxy代理服務接收到請求后,會把請求分發(fā)到三個地方:被測服務,通常稱之為侯選版本(
candidate)、穩(wěn)定版本(primary)服務、穩(wěn)定版本副本(secondary)服務; - 接著,侯選版本服務與穩(wěn)定版本服務的返回結果進行diff,生成原始diff結果(raw differences),即原始區(qū)別;
- 其次,穩(wěn)定版本與穩(wěn)定版本副本的返回結果進行diff,生成噪聲diff差異值結果(non-deterministic noise),即噪聲,通過對這些差異值做減法來消除噪聲。
- 最后,通過比對原始的diff結果與消除噪聲后的結果,得到最終的diff結果通過去噪聲,得到最終過濾后的diff結果(filtered differences);
最終過濾后的對比結果會在平臺提供的html頁面中展示出來。
為了方便大家更好的理解上述工作流程,在網(wǎng)上找了一張圖,標注了一下示例(本圖來源于網(wǎng)絡):

其中:
- 原始區(qū)別為候選版本和穩(wěn)定版本之間輸出的區(qū)別,其中可能會包含上述的噪聲。
- 噪聲從穩(wěn)定版本和其副本中獲得,如果兩個運行相同代碼的系統(tǒng)輸入相同輸出卻不同,則Diffy會認為這是開發(fā)人員不需要關心的噪聲。
基于上述兩個區(qū)別集合,Diffy可以識別出候選版本和穩(wěn)定版本真實的區(qū)別,這些區(qū)別很有可能就是一個缺陷。
當然,對于一個概率性出現(xiàn)隨機值,僅僅一次請求的結論可能是不準確的。例如對于一個50%概率出現(xiàn)true或者false的布爾值,則有50%的概率會出現(xiàn)候選版本和穩(wěn)定版本的不同,同時又會有50%的概率出現(xiàn)穩(wěn)定版本和其副本出現(xiàn)不同(即將這個值認定為噪聲),最終會有25%的概率認為這是一個缺陷。因為此時穩(wěn)定版本和其副本值相同,候選版本和穩(wěn)定版本值不同。因此,Diffy還會聚合原始區(qū)別和噪聲,當發(fā)現(xiàn)二者出現(xiàn)的概率類似的時候,會認定之前識別出來的缺陷屬于誤報。
4. Diffy編譯、部署
Diffy是Twitter使用scala語言開發(fā)的項目,并且在GitHub持續(xù)更新中,關于diffy的源碼,github上對應有兩個版本:
1. twitter/diffy:
https://github.com/twitter/diffy
2. opendiffy/diffy:
https://github.com/opendiffy/diffy
按照官方的說明,建議優(yōu)先使用opendiffy/diffy進行編譯部署。
由于我們最終是需要用到diffy編譯成功生成的jar包(實際上diffy平臺使用的是scala語言),此時運行環(huán)境需要安裝JDK,這里建議安裝Java 8,編譯環(huán)境安裝好之后,克隆diffy源碼并進行sbt編譯構建。
git clone https://github.com/opendiffy/diffy
cd diffy
./sbt assembly
需要注意的是./sbt assembly這個編譯下載過程十分漫長,有條件的同學建議掛個代理。

編譯好之后,生成的Jar包位置:diffy/target/scala-xx/diffy-server.jar(diffy根目錄的相對路徑下)
除了利用Github的源碼進行搭建外,還有兩種方式也可以搭建Diffy。其一是直接利用jar包,但該方法或者使用docker的Diffy容器(https://hub.docker.com/r/diffy/diffy)進行搭建,在此不一一贅述。
5. Diffy常用命令參數(shù)
編譯生成好jar包后,直接通過java命令啟動diffy服務即可,其中,運行Diffy服務的常用參數(shù)如下:
| 參數(shù)配置 | 含義 |
|---|---|
| candidate='PC1:8888' | 待上線版本部署地址,即候選版本 |
| master.primary='PC2:8888' | 已上線版本地址1,即穩(wěn)定版本 |
| master.secondary='PC3: 8888' | 已上線版本地址2,即穩(wěn)定版本副本 |
| service.protocol='http' | http協(xié)議或https |
| serviceName='Test Service' | 服務名稱 |
| proxy.port=:9990 | Diffy代理端口,所以請求都應從這個端口訪問 |
| admin.port=:9991 | 通過http://PC0:8881/admin可查看請求狀況 |
| http.port=:9999 | 查看界面,在這里可以比較差異 |
| responseMode=primary | 代理服務器是否返回結果,默認(empty)無返回,可指定primary返回線上版本,secondary(同線上版本,用于噪音消除),candidate(待測試版本) |
| allowHttpSideEffects=true | Diffy考慮到安全性,POST,PUT,DELETE請求默認忽略,因此該參數(shù)為true則表示這三種類型請求仍能正常代理發(fā)送 |
| excludeHttpHeadersComparison=false | 是否排除header的差異,不同服務器,cookie,nginx版本可能有所差異,設置為true可以忽略這 |
| notifications.targetEmail | (對差異發(fā)送到指定郵箱) |
例如:
java -jar diffy-server.jar \
-candidate='127.0.0.1:80' \
-master.primary='127.0.0.1:81' \
-master.secondary='127.0.0.1:82' \
-service.protocol='http' \
-serviceName='My Diffy Service' \
-proxy.port=:8880 \
-admin.port=:8881 \
-http.port=:8888 \
-allowHttpSideEffects=true \
-excludeHttpHeadersComparison=false \
-notifications.targetEmail=tester@emal.com
6. Diffy項目實戰(zhàn)演練
安裝和使用Diffy的一般步驟如下:
- 安裝Diffy;
- 啟動候選服務、穩(wěn)定服務和穩(wěn)定服務副本;
- 運行Diffy;
- 發(fā)送請求&查看結果;
接下來,通過一則簡單的實戰(zhàn)項目示例,為大家演示整個diffy的使用過程。
本文示例項目:是基于Django搭建的一套簡易型REST API服務。關于如何通過Django來實現(xiàn)REST API服務過程可參考:Python利用Django 構建Rest Api: 快速入門教程
假設按照上述教程,你已經成功的搭建好了REST API服務,項目名為:blog_project,接下來,繼續(xù)往下操作:
1. 部署primary(穩(wěn)定版本)
由于本文不區(qū)分線上正式環(huán)境和測試環(huán)境,皆通過本地環(huán)境演示。(讀者在實際生產&測試環(huán)境操作時,除了環(huán)境差異外,操作思路皆一樣)
將示例項目blog_project代碼拷貝一份到其它目錄(為了和測試版本區(qū)分開來),激活虛擬環(huán)境,啟動Django服務,端口設置為8001,此服務作為穩(wěn)定版本服務,命令如下:
source env/bin/activate
cd blog_project
python manage.py runserver 8001
2. 部署secondary(穩(wěn)定版本副本)
同上一步操作一樣,激活虛擬環(huán)境,啟動Django服務,端口設置為8002,此服務作為穩(wěn)定版副本服務,命令如下:
source env/bin/activate
cd blog_project
python manage.py runserver 8002
3. 驗證primary和secondary(穩(wěn)定版本服務)
此步非必須,但為了讓大家直觀能和測試版本的服務區(qū)分開來,我們先驗證一下,當前穩(wěn)定版本服務的接口輸出信息,比如:
http http://127.0.0.1:8001/api/
輸出信息:

從上述輸出信息中,我們可以知道訪問api/接口時,會輸出兩條信息,并且每條記錄,分別對應有content,id,title,updated_at,create_at幾個字段。
接著驗證secondary副本服務:
http http://127.0.0.1:8002/api/

可以看出,secondary副本服務和primary穩(wěn)定版本服務輸出結果是一樣的。
4. 部署candidate(測試版本)
接下來,我們開始部署測試版本服務,為了和穩(wěn)定版本服務有所不同,我們在測試版本中,給api接口請求記錄中,增加一個data字段。(實際工作中,也經常會面臨接口字段的增、刪、改)
1、修改blog_api/models.py文件,在原來的數(shù)據(jù)模型中,增加一個data字段:
from django.db import models
# Create your models here.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
data = models.CharField(max_length=250,default='--')
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
2、修改serializers.py文件,在fields中增加返回data字段。
class PostSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'title', 'content', 'data','created_at', 'updated_at',)
model = models.Post
3、生成遷移文件、同步執(zhí)行數(shù)據(jù)庫變更
python manage.py makemigrations
python manage.py migrate
4、啟動服務,默認端口為8000,作為待測版本服務。
python manage.py runserver
5. 啟動diffy服務
由于演示需要,直接在本地啟動diffy服務即可,命令如下:
java -jar diffy-server.jar
-candidate=localhost:8000
-master.primary=localhost:8001
-master.secondary=localhost:8002
-service.protocol=http
serviceName=My-Service
-proxy.port=:8880
-admin.port=:8881
-http.port=:8888
-rootUrl='localhost:8888'
-allowHttpSideEffects=true
從上述啟動命令中,可知:
- diffy代理接口為8880,后續(xù)測試的所以請求都應從這個端口訪問
- 查看請求:通過http://localhost:8881/admin (admin.port)可以查看請求狀況
- 查看差異:通過http://localhost:8888 (http.port)比較差異
在命令行中,輸入如下命令,運行測試:
http http://127.0.0.1:8880/api/
命令經執(zhí)行后,經diffy代理轉發(fā)到穩(wěn)定版本服務(端口8001)、穩(wěn)定版本副本服務(端口8002)、測試版本服務(端口8000)中。

訪問http://localhost:8888,查看diff請求對比界面,功能說明如下圖所示:

通常接口差異主要分為以下幾類:
- 每次調用本身返回值就不同,如updatetime(可忽略);
- 測試環(huán)境和線上環(huán)境數(shù)據(jù)不一致(可忽略);
- 實時數(shù)據(jù)接口、動態(tài)變化數(shù)據(jù)(可忽略);
- 軟件缺陷或非預期修改。
對于可忽略的差異,可點擊按鈕忽略。
訪問http://localhost:8881/admin,查看diff后臺界面,功能說明如下圖所示:

連續(xù)運行幾次測試請求,訪問http://localhost:8888,對比請求差異,如圖所示。

從上圖中,可知,已經成功diffy出在測試版本中,新增了一個data字段。
6. 修改測試版本服務
繼續(xù)在測試版本服務上面修改以驗證diffy的有效性,比如修改api/接口返回的記錄內容。
1、訪問http://localhost:8000/admin,訪問測試版本服務后臺,修改其中一條記錄,比如:

更新date中的內容,并點擊保存。此時需要注意,當點擊保存后,此時記錄的updated_at字段值會被修改。
2、再次運行diffy代理請求。
http http://127.0.0.1:8880/api/
3、此時再觀察http://localhost:8888界面,

可以看到,在diffy界面中,檢查出了三個差異:返回的內容長度Content-length、data、updated_at。
當然,實際業(yè)務中,Content-length、updated_at這類型的差異可被忽略掉。
通過結合接口返回詳情功能,可查看到穩(wěn)定版本和測試版本返回響應的差異處:

7. 小結
最后,小結幾點建議:
- 在使用Diffy時,需要通過Diffy代理服務發(fā)送待測請求,雖然我們可以通過postman、curl等工具一個個發(fā)送,實踐時,可通過Charles工具記錄所有線上待測請求,然后利用Charles的Rewrite功能將修改成Diffy的代理服務器地址,重寫請求,再重發(fā)。
- 除上借助Charles代理工具外,在實際應用時,也可借助線上引流工具(比如通過goreplay等引流工具)進行請求流量回放,或通過已有的接口自動化測試用例觸發(fā)請求。
- 在使用Diffy時,可以看到有些差異是請求頭部導致的,并不是我們想要發(fā)現(xiàn)的內容上的差異,如cookie的差異,nginx版本的差別,不同服務器等等,可以在命令行中加入配置可忽略頭部差異:
excludeHttpHeadersComparison=true
如果你覺得文章還不錯,請轉發(fā)分享下,你的肯定是我最大的鼓勵和支持。

浙公網(wǎng)安備 33010602011771號