推薦一款開源的Diffy自動化測試框架


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-lengthdataupdated_at

當然,實際業務中,Content-lengthupdated_at這類型的差異可被忽略掉。

通過結合接口返回詳情功能,可查看到穩定版本和測試版本返回響應的差異處:

7. 小結

最后,小結幾點建議:

  1. 在使用Diffy時,需要通過Diffy代理服務發送待測請求,雖然我們可以通過postman、curl等工具一個個發送,實踐時,可通過Charles工具記錄所有線上待測請求,然后利用Charles的Rewrite功能將修改成Diffy的代理服務器地址,重寫請求,再重發。
  2. 除上借助Charles代理工具外,在實際應用時,也可借助線上引流工具(比如通過goreplay等引流工具)進行請求流量回放,或通過已有的接口自動化測試用例觸發請求。
  3. 在使用Diffy時,可以看到有些差異是請求頭部導致的,並不是我們想要發現的內容上的差異,如cookie的差異,nginx版本的差別,不同服務器等等,可以在命令行中加入配置可忽略頭部差異:excludeHttpHeadersComparison=true

如果你覺得文章還不錯,請轉發分享下,你的肯定是我最大的鼓勵和支持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM