1. 前言
軟件測試是軟件開發生命周期一個十分重要的環節,測試工作開展的好壞,很大程度上決定了產品質量的好壞,但軟件產品隨着版本的持續迭代,功能日益增多,系統愈加復雜,而從質量保障的角度,除了要保障好每次新增、優化的產品質量外,還需要確認新增或修改的功能不影響之前已存在的功能。若要進行產品功能全量回歸,這個測試的工作量將會非常巨大。同時因為是回歸,可能幾百甚至上千用例中才會發現一個問題,甚至一個問題也沒有,測試投入工作的時間與最終的收益不成比例。
因此如何在有限的時間、人力投入下,有效、高效的保證產品回歸測試的質量,也一度成為了行業老司機以及團隊管理者頭疼的問題!
而今天的主角Diffy
則為上述問題提供了較好的解決方案。它基於穩定版本和它副本的輸出,對候選版本的輸出進行嚴格對比,以檢查候選版本是否正確,大大降低了回歸工作量。
接下來,讓我們詳細了解一下Diffy
的工作原理,以及結合實戰演練帶大家感受一下它的魅力。
2. 關於Diffy
關於Diffy
,公號此前發表過一篇文章:
推薦一款Diffy:Twitter的開源自動化測試工具
有過詳細介紹,之前不了解的讀者,可詳細閱讀一下。
簡單來理解,Diffy
是一個開源的自動化測試工具,是一種自動Diff測試技術。它能夠自動檢測基於Apache Thrift或者基於HTTP的服務。通過同時運行新/老代碼,對比運行結果,發現潛在bug。並且使用Diffy,只需要進行簡單的配置,而不需要再編寫測試代碼。
3. Diffy工作原理
在整個測試開展過程中,Diffy需要部署三個版本的系統,以實現它的噪聲過濾和對比功能,它們分別是:
- 候選版本(candidate):該版本為待測版本,有着最新待測代碼。
- 穩定版本(primary):該版本通常是已經上線版本,或者是已知功能正常的版本。
- 穩定版本副本(secondary):該版本是穩定版本的副本,和穩定版本運行相同的代碼,主要用於排除噪聲。
而Diffy
主要職責充當了一個前置代理服務的角色,它能夠將來源請求分發到不同版本的系統中去,通過對各個版本系統的輸出進行對比,做出最終的結論。
Diffy
整個工作原理流程圖如下:
說明:
diffy
本身作為一個代理服務(proxy),需要人為構造或引流http請求,發到proxy代理服務中。- 當proxy代理服務接收到請求后,會把請求分發到三個地方:被測服務,通常稱之為侯選版本(
candidate
)、穩定版本(primary
)服務、穩定版本副本(secondary
)服務; - 接着,侯選版本服務與穩定版本服務的返回結果進行diff,生成原始diff結果(raw differences),即原始區別;
- 其次,穩定版本與穩定版本副本的返回結果進行diff,生成噪聲diff差異值結果(non-deterministic noise),即噪聲,通過對這些差異值做減法來消除噪聲。
- 最后,通過比對原始的diff結果與消除噪聲后的結果,得到最終的diff結果通過去噪聲,得到最終過濾后的diff結果(filtered differences);
最終過濾后的對比結果會在平台提供的html頁面中展示出來。
為了方便大家更好的理解上述工作流程,在網上找了一張圖,標注了一下示例(本圖來源於網絡):
其中:
- 原始區別為候選版本和穩定版本之間輸出的區別,其中可能會包含上述的噪聲。
- 噪聲從穩定版本和其副本中獲得,如果兩個運行相同代碼的系統輸入相同輸出卻不同,則Diffy會認為這是開發人員不需要關心的噪聲。
基於上述兩個區別集合,Diffy可以識別出候選版本和穩定版本真實的區別,這些區別很有可能就是一個缺陷。
當然,對於一個概率性出現隨機值,僅僅一次請求的結論可能是不准確的。例如對於一個50%概率出現true或者false的布爾值,則有50%的概率會出現候選版本和穩定版本的不同,同時又會有50%的概率出現穩定版本和其副本出現不同(即將這個值認定為噪聲),最終會有25%的概率認為這是一個缺陷。因為此時穩定版本和其副本值相同,候選版本和穩定版本值不同。因此,Diffy還會聚合原始區別和噪聲,當發現二者出現的概率類似的時候,會認定之前識別出來的缺陷屬於誤報。
4. Diffy編譯、部署
Diffy是Twitter使用scala語言開發的項目,並且在GitHub持續更新中,關於diffy
的源碼,github上對應有兩個版本:
1. twitter/diffy:
https://github.com/twitter/diffy
2. opendiffy/diffy:
https://github.com/opendiffy/diffy
按照官方的說明,建議優先使用opendiffy/diffy
進行編譯部署。
由於我們最終是需要用到diffy
編譯成功生成的jar
包(實際上diffy平台使用的是scala語言),此時運行環境需要安裝JDK,這里建議安裝Java 8
,編譯環境安裝好之后,克隆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常用命令參數
編譯生成好jar包后,直接通過java命令啟動diffy服務即可,其中,運行Diffy服務的常用參數如下:
參數配置 | 含義 |
---|---|
candidate='PC1:8888' | 待上線版本部署地址,即候選版本 |
master.primary='PC2:8888' | 已上線版本地址1,即穩定版本 |
master.secondary='PC3: 8888' | 已上線版本地址2,即穩定版本副本 |
service.protocol='http' | http協議或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請求默認忽略,因此該參數為true則表示這三種類型請求仍能正常代理發送 |
excludeHttpHeadersComparison=false | 是否排除header的差異,不同服務器,cookie,nginx版本可能有所差異,設置為true可以忽略這 |
notifications.targetEmail | (對差異發送到指定郵箱) |
例如:
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項目實戰演練
安裝和使用Diffy的一般步驟如下:
- 安裝Diffy;
- 啟動候選服務、穩定服務和穩定服務副本;
- 運行Diffy;
- 發送請求&查看結果;
接下來,通過一則簡單的實戰項目示例,為大家演示整個diffy
的使用過程。
本文示例項目:是基於Django
搭建的一套簡易型REST API
服務。關於如何通過Django來實現REST API服務過程可參考:Python利用Django 構建Rest Api: 快速入門教程
假設按照上述教程,你已經成功的搭建好了REST API服務,項目名為:blog_project
,接下來,繼續往下操作:
1. 部署primary(穩定版本)
由於本文不區分線上正式環境和測試環境,皆通過本地環境演示。(讀者在實際生產&測試環境操作時,除了環境差異外,操作思路皆一樣)
將示例項目blog_project
代碼拷貝一份到其它目錄(為了和測試版本區分開來),激活虛擬環境,啟動Django
服務,端口設置為8001
,此服務作為穩定版本服務,命令如下:
source env/bin/activate
cd blog_project
python manage.py runserver 8001
2. 部署secondary(穩定版本副本)
同上一步操作一樣,激活虛擬環境,啟動Django
服務,端口設置為8002
,此服務作為穩定版副本服務,命令如下:
source env/bin/activate
cd blog_project
python manage.py runserver 8002
3. 驗證primary和secondary(穩定版本服務)
此步非必須,但為了讓大家直觀能和測試版本的服務區分開來,我們先驗證一下,當前穩定版本服務的接口輸出信息,比如:
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穩定版本服務輸出結果是一樣的。
4. 部署candidate(測試版本)
接下來,我們開始部署測試版本服務,為了和穩定版本服務有所不同,我們在測試版本中,給api接口請求記錄中,增加一個data
字段。(實際工作中,也經常會面臨接口字段的增、刪、改)
1、修改blog_api/models.py文件,在原來的數據模型中,增加一個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、生成遷移文件、同步執行數據庫變更
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,后續測試的所以請求都應從這個端口訪問
- 查看請求:通過http://localhost:8881/admin (admin.port)可以查看請求狀況
- 查看差異:通過http://localhost:8888 (http.port)比較差異
在命令行中,輸入如下命令,運行測試:
http http://127.0.0.1:8880/api/
命令經執行后,經diffy代理轉發到穩定版本服務(端口8001
)、穩定版本副本服務(端口8002
)、測試版本服務(端口8000
)中。
訪問http://localhost:8888
,查看diff請求對比界面,功能說明如下圖所示:
通常接口差異主要分為以下幾類:
- 每次調用本身返回值就不同,如updatetime(可忽略);
- 測試環境和線上環境數據不一致(可忽略);
- 實時數據接口、動態變化數據(可忽略);
- 軟件缺陷或非預期修改。
對於可忽略的差異,可點擊按鈕忽略。
訪問http://localhost:8881/admin
,查看diff后台界面,功能說明如下圖所示:
連續運行幾次測試請求,訪問http://localhost:8888
,對比請求差異,如圖所示。
從上圖中,可知,已經成功diffy出在測試版本中,新增了一個data
字段。
6. 修改測試版本服務
繼續在測試版本服務上面修改以驗證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
。
當然,實際業務中,Content-length
、updated_at
這類型的差異可被忽略掉。
通過結合接口返回詳情功能,可查看到穩定版本和測試版本返回響應的差異處:
7. 小結
最后,小結幾點建議:
- 在使用Diffy時,需要通過Diffy代理服務發送待測請求,雖然我們可以通過postman、curl等工具一個個發送,實踐時,可通過Charles工具記錄所有線上待測請求,然后利用Charles的Rewrite功能將修改成Diffy的代理服務器地址,重寫請求,再重發。
- 除上借助Charles代理工具外,在實際應用時,也可借助線上引流工具(比如通過goreplay等引流工具)進行請求流量回放,或通過已有的接口自動化測試用例觸發請求。
- 在使用Diffy時,可以看到有些差異是請求頭部導致的,並不是我們想要發現的內容上的差異,如cookie的差異,nginx版本的差別,不同服務器等等,可以在命令行中加入配置可忽略頭部差異:
excludeHttpHeadersComparison=true
如果你覺得文章還不錯,請轉發分享下,你的肯定是我最大的鼓勵和支持。