1. 程式人生 > >[持續交付實踐] 基於 Junit 的接口自動化測試框架實現

[持續交付實踐] 基於 Junit 的接口自動化測試框架實現

lis ebo 命名 早已 更多 數據集 matcher 似的 相關

前言

這半個月基本都在出差以及各種公司業務上的事情,難得有空閑整理一些測試技術上的事情。周末有些空閑抓緊碼一篇填坑,持續交付/持續集成這一系列文章不僅僅是想在壇子裏和同行者做些分享,對個人的一種自我思考和鞭策。總體來說我覺得這個論壇目前還比較清爽,希望在人氣快速提升的同時能保持初心,堅持做一個單純技術分享交流的平臺。

分層的自動化測試

5~10年前,我們接觸的自動化測試更關註的是UI層的自動化測試,Mercury的WinRunner/QTP是那個時代商業性自動化測試產品的典型代表,在那個時代大家單純想的都是能用一個自動化操作的工具替代人力的點擊,商業化或是私有化框架大行其道。
而分層的自動化測試倡導產品的不同階段(層次)都需要自動化測試。在《google軟件測試之道》中,在google 70%的投入為單元測試(小型測試),20%為接口/集成測試(中型測試),10%為UI層的自動化測試(大型測試),也就是大家熟悉的金字塔模型,越往上自動化實現難度越大,投入產生的收益也越低(需要強調的是,UI層的自動化測試作為最接近用戶操作的測試,仍然有其存在的意義和場景)。

技術分享

接口測試的意義

接口測試是驗證兩個或多個模塊應用之間的交互(通常是采用接口的方式),測試的重點是要檢查數據的交換,傳遞和控制管理過程,還包括處理的次數。
接口測試的核心戰略在於:以保證系統的正確和穩定為核心,以持續集成為手段,提高測試效率,提升用戶體驗,降低產品研發成本。
接口測試要為代碼的編寫保駕護航,增強開發人員和測試人員的自信,讓隱含的 BUG提前暴露出來,要讓開發人員在第一時間修復 BUG,要讓業務測試人員在測試的時候更加順手,最大限度得減少底層 BUG 的出現數量,要讓產品研發的流程更加敏捷,要縮短產品的研發周期,最後在產品上線以後,要讓用戶用得更加順暢,要讓用戶感覺產品服務零缺陷。
不同於單元測試,接口測試本質上還是一種黑盒的測試,所以非常適合專職測試工程師去參與和覆蓋。

接口測試框架選型

1.目前接口測試框架的選型,最常見的方法是采用jmeter,soapUI,postman,robotframework等UI化的接口測試框架來做。
好處是業務測試人員可以不用或很少寫測試代碼,入門門檻低,前幾年有很多公司都曾經開發過類似的測試框架,有前端有後端,專職的測試開發人員維護,業務測試人員只需要知道怎麽操作而不需要參與具體coding。
這種方法看起來非常高大上,但實際的問題是執行過程中主要的工作變成了測試框架的維護,非常依賴專職測試開發人員的設計和開發能力,每增加一種新的接口協議(比如dubbo、hessian或者內部自定義的協議)就需要在框架上增加支持;更致命的是一旦核心測試開發人員出現流動,就很容易造成整個接口測試體系的崩塌;另外對業務測試人員的技能成長也並不公平,個人已面試過太多只會使用某大公司XXX測試框架卻完全不了解具體實現方式的工程師。
《google軟件測試之道》中早已有過預言,保密和私有化的基礎測試設施並不能獲得想象中的好處,這種方式意味著昂貴和遲緩,即使在公司內部的不同項目之間也很難做到復用。未來的測試基礎設施必然是建立在共享代碼和開源框架的基礎上,測試開發人員需要更多的利用開源項目並為之貢獻。
最近重讀了一次這本四五年前幾乎改變軟件測試行業的書籍,發現裏面的預言都是如此準確,當然也可以認為國內整個行業都正參照google的方式在進行演變。

技術分享

2.使用junit、testng等java接口框架,直接編寫測試代碼去測試,同時對一些重復性的工作抽象建立基礎庫或方法。
有點類似於單元測試,這種方法擴展性好實現靈活,作為程序員可以用代碼實現靈活的場景組織和功能,只要稍微二次開發一下,但需要測試工程師有一定的編碼基礎。 
這種方式在前幾年實施的難度還是比較大,因為在市場上要找到懂java代碼的測試工程師都寥寥無幾,但在對測試工程師開發能力要求越來越多的今天實施難度已沒有想象中困難,java/python等語言的編碼能力也已成為我們團隊招聘時的基本要求。
另外提下,這裏使用java而不用其他語言的原因,主要是團隊的技術儲備java是強項,擁有豐富的開源測試庫,而且一般互聯網公司的產品基本都是采用java框架進行開發,和開發團隊技術棧保持一致非常有必要性。

我們封裝的接口測試框架

有很多公司做了各種不同的接口框架,都是基於自己公司的業務基礎設計開發。我們基於自己的業務特點(為避免廣告嫌疑,盡量我不提及具體公司的信息),也封裝了自己的接口測試框架gtest-framework,在開發人員的單元測試中也正逐漸使用。
gtest-framework要做的事情:
1.前置數據準備和自動清理。
2.常見接口協議的實現和封裝。
3.依賴註入配置方式的支持。
4.如文件、圖片、xml、字符等各類通用處理方法的集成。
5.斷言方式的擴展等。

技術分享

接口測試關鍵實踐

1.數據準備
接口測試的數據準備,一般是指數據庫的數據準備,有時候還包括文件和緩存的數據準備。具體實現可以從下面幾個方面去考慮
(1)硬編碼的方式準備測試數據,在寫測試代碼的時候,使用到什麽數據就插入什麽數據。為了避免數據重復,很多人會習慣於使用隨機字符或隨機數(這種方法可能造成測試用例不穩定,盡量避免)。
(2)可以直接通過調用其他API的方式準備測試數據,這種情況在測試最上層服務的時候比較有用,比如測試購買商品,就需要準備要購買的商品數據,購買商品的用戶數據,這個時候,可以直接調用生成商品的api和生成用戶的api直接生成測試數據。此方法實現簡單,但前提是需要具備相應的api並且此api功能正確。
(3)使用excel或xml準備測試數據,這種準備測試數據的方式,主要針對對象數據的準備,比如可以將一條商品數據對應excel中的一條數據,因為一般開發都會使用pojo映射,而在準備測試數據的時候,這些pojo對象屬性的設置往往是重復和大工作量的,用excel或XML方式準備,則可以減少在代碼當中重復去準備這些數據。

一般我們使用的是2/3兩種方式,其中3這種方式主要利用dbunit、spring-test、unitils等測試框架的特性經二次開發增加自定義註解,很輕松的導入excel或xml格式的文件並在測試完成後對數據進行自動回滾。


/** 
* @ClassName: TestJdbcDataSet 
* @Description: 采用自定義TestDataSet註解方式準備測試數據,推薦。
* @author Cay.Jiang   
* @date 2017年7月10日 上午9:10:29 
*  
*/
public class TestJdbcDataSet extends BaseCase{

    Map<String, Object> args = new HashMap<String, Object>();
    @Test
    @TestDataSet(locations={"/tmp/domaininfo.xls"},dsNames={"mysqlDataSource"})
    public void test01_mysql(){
        args.put("selfdomain", "baidupc2");
        List<Map<String, Object>> result=JdbcUtil.queryData(mysqlJdbcTemplate, "domaininfo", args);
        System.out.println(result);
        assertEquals("百度合作PC端接入2",result.get(0).get("remark"));
    }

}

上面代碼中的/tmp/domaininfo.xls參見:domaininfo.xls ,其中Excel格式以Sheet名為表名,第一行定義了字段名稱,其余行為對應的數據。

技術分享


多數據集:
@TestDataDataSet(locations={"Data1.xls","Data2.xls"},dsNames={"dsNameA","dsNameB"}),Data1.xls的數據會插入dsNameA所指的數據庫中,Data2.xls的數據會插入dsNameB所指的數據庫中
2.斷言
常見的斷言方式有JUnit自帶的Assert和Hamcrest。JUnit自帶的斷言方法功能十分有限只能滿足最基本的需求。Hamcrest相對來講功能豐富一些,但是該庫已經多年不更新。而且Hamcrest和JUnit自帶的斷言方法一樣,有個致命的缺點,就是當一個case中有多個斷言時,如果其中一個斷言失敗,那麽在它之後的斷言都不會執行。這裏向大家推薦一款新的斷言神器AssertJ。
AseertJ: 號稱流式斷言。什麽是流式,常見的斷言器一條斷言語句只能對實際值斷言一個校驗點,而AseertJ支持一條斷言語句對實際值同時斷言多個校驗點,這樣使得斷言的語句更加簡潔適合閱讀。AseertJ還支持一次性執行所有斷言,然後收集所有失敗的斷言一起反饋。當然除此之外AseertJ還有很多其他特性,可以參考官方文檔慢慢挖掘。下面將舉例說明一下AseertJ的優勢:

public class TestCase extends BaseCase{

    UserProfileBO user = new UserProfileBO();

    @Before
    public void init(){
        user.setAddress("杭州");
        user.setMobile("1386800000");
        user.setUserName("測試賬號");
    }

    /*
     * JUnit 內置的斷言
     * 
     * 1、其中一個斷言失敗後,後面所有斷言將不會執行。
     * 2、支持的斷言方法較少
     * 
     */
    @Test
    public void testAssertJUnit(){

        Assert.assertEquals("地址",user.getAddress(),"寧波");
        Assert.assertEquals("手機",user.getMobile(),"13868000000");
        Assert.assertEquals("手機",user.getUserName(),"測試");

        Assert.assertNotNull(user.getMobile());
        Assert.assertTrue(user.getMobile().startsWith("138"));
        Assert.assertTrue(user.getMobile().length() == 11);

    }

    /*
     * Hamcres 斷言
     * 
     * 1、其中一個斷言失敗後,後面所有斷言將不會執行。
     * 2、支持的斷言方法豐富,但是已經多年不更新。
     * 
     */
    @Test 
    public void testHamcrestMatchers() {  

        MatcherAssert.assertThat(user.getAddress(), equalTo("寧波"));  
        MatcherAssert.assertThat(user.getMobile(), equalTo("13868000000"));  
        MatcherAssert.assertThat(user.getUserName(), equalTo("測試"));  

        MatcherAssert.assertThat(user.getMobile(), allOf(is(nullValue()),startsWith("136")));  
    }

    /*
     * AssertJ 斷言
     * 
     * 1、支持所有斷言執行後,失敗斷言統一反饋。
     * 2、支持的斷言方法豐富。
     * 3、支持流式斷言,方便閱讀。
     * 
     */
    @Test
    public void testAssertJ(){

        //斷言集合,執行所有斷言後,失敗斷言統一反饋。
        SoftAssertions.assertSoftly(softly -> {
            softly.assertThat(user.getAddress().equals("寧波"));
            softly.assertThat(user.getMobile().equals("13868000000"));
            softly.assertThat(user.getUserName().equals("測試"));
        });

        //流式斷言
        Assertions.assertThat(user.getMobile())
            .isNotNull()
            .startsWith("136")
            .hasSize(11);

    }
}

3.jenkins集成接口測試
(1)設置測試代碼的倉庫地址及身份信息

技術分享


(2)設置maven運行參數
希望執行部分接口用例,可以通過-Dtest=XXX(測試類名)的方式執行指定的case,多個類名用逗號“,”隔開

技術分享


(3)設置Job執行機制,下圖表示每天定時執行

技術分享

4.pipeline參數註入
前面寫pipeline的時候提過,我們的接口測試代碼是需要支持外部參數註入的,比如測試的服務地址,不同的分支代碼可能部署在不同測試服務器上,需要在pipeline中通過參數化的方式驅動對不同的服務器進行接口測試。
這裏我們可以使用maven的-D(Properties屬性)來實現,舉例如下:
(1)dubbo使用properties配置文件,但具體參數使用${key}占位符方式打包替換

技術分享


(2)maven的pom文件中指定對應配置文件中的參數值(此處指定的參數值會被通過maven -D傳遞過來的參數值覆蓋)

技術分享


此處註意:還需要啟動resources的filter過濾器
(3)使用maven命令行設置屬性值

技術分享

技術分享


並對該值進行參數化支持pipeline傳參
pipeline代碼

stage(‘接口自動化測試‘) {
      steps{
          echo "starting interfaceTest......"
          script {
           //為確保jetty啟動完成,加了一個判斷,確保jetty服務器啟動可以訪問後再執行接口層測試。
           timeout(5) {
               waitUntil {
                  try {
                      //確保jetty服務的端口啟動成功
                      sh "nc -z ${serverIP} ${jettyPort}"
                      //sh "wget -q http://${serverIP}:${jettyPort} -O /dev/null"
                      return true
                  } catch (exception) {
                      return false
                      }
                  }
              }
          //將參數IP和Port傳入到接口測試的job,需要確保接口測試的job參數可註入
           build job: ITEST_JOBNAME, parameters: [string(name: "dubbourl", value: "${serverIP}:${params.dubboPort}")]
          }
      }
  }

測試代碼規範(僅供參考)

1.測試項目命名規範
接口測試:
一般需要獨立測試項目,測試項目的命名規則為:“test-“+被測試的項目名,如test-kano
單元測試:
不需要重建獨立測試項目,和開發代碼放在同一項目即可。
2.測試目錄定義規範
測試代碼統一放在測試項目的“src/test/java”下。
測試配置文件統一放置在“src/test/resources”下。
3.包名定義規範
與被測試項目中的包名一致
4.測試類命名規範
測試類的命名規則是:以Test開頭,以它要測試的對象的名稱結尾,例如
Test+被測試的業務、Test+被測試的接口、Test+被測試的類
另外一種方式是:以Test結尾,以它要測試的對象的名稱開頭,例如
被測試的業務+Test、被測試的接口+Test、被測試的類+Test
視個人習慣而定,為了case定位方便,目前測試團隊一般用第一種。
5.測試用例命名規範
測試用例的命名規則是:test+用例操作_條件狀態,統一使用lowerCamelCase風格,必須遵從駝峰形式。
單詞的約定與測試類命名同
6.接口測試代碼常見約束
(1)數據清理和構造
@BeforeClass @Before中做數據準備等相關操作
--加載測試類以前需要加載所有測試用例共同的場景數據,同時在運行單個測試用例的時候加載特別的測試數據
@AfterClass @After中做測試數據清理等相關操作
--在執行完相關測試以後清理用例現場
(2)斷言
--不要做無謂的斷言
在測試模式下, 有時會情不自禁的濫用斷言. 這種做法會導致維護更困難, 需要極力避免. 僅對測試方法名指示的特性進行明確測試,因為對於一般性代碼而言, 保證測試代碼盡可能少是一個重要目標
--使用顯式斷言方式
應該總是優先使用 assertEquals(a, b) 而不是 assertTrue(a == b), 因為前者會給出更有意義的測試失敗信息. 在事先不確定輸入值的情況下, 這條規則尤為重要
--斷言的參數順序要合適
(3)單元測試用例保持獨立
--確保測試代碼獨立於項目代碼之外
--為了保證測試穩定可靠且便於維護, 測試用例之間決不能有相互依賴, 也不能依賴執行的先後次序.
(4)測試代碼要考慮錯誤處理
--如果前面的代碼執行失敗, 後續語句會導致代碼崩潰, 剩下的測試都無法執行. 任何時候都要為測試失敗做好準備, 避免單個失敗的測試項中斷整個測試套件的執行
--不要寫自己的catch代碼塊,即只有test失敗的情況,不應該存在catch情況

結語

接口測試是一個非常龐雜的體系,很難用一篇文章闡述清楚,在持續交付體系中扮演著非常重要的角色。
其他實踐如接口測試覆蓋率統計、標準協議頁面化測試平臺(提供給沒有編碼能力的產品或測試人員使用)、maven項目骨架建立標準化測試工程、dubbo/hessian/restful/webservice等接口協議具體實現等等限於篇幅也沒有做具體闡述。
不過接口測試實在已經不是一個新鮮的話題,在論壇裏已經看到很多人對接口測試各種實現方法做了介紹,本人也從中學習到很多,所以這塊的介紹就先這樣草草結尾吧,希望能提供大家一些有益的參考。

[持續交付實踐] 基於 Junit 的接口自動化測試框架實現