微服務場景下的自動化測試

atiz9753 8年前發布 | 43K 次閱讀 微服務 數據庫 自動化測試

新的挑戰

微服務和傳統的單塊應用相比,在測試策略上,會有一些不太一樣的地方。簡單來說,在微服務架構中,測試的層次變得更多,而且對環境的搭建要求更高。比如對單塊應用,在一個機器上就可以setup出所有的依賴,但是在微服務場景下,由于依賴的服務往往很多,要搭建一個完整的環境非常困難,這對團隊的 DevOps 的能力也有比較高的要求。

相對于單塊來說,微服務架構具有以下特點:

  • 每個微服務在物理上分屬不同進程
  • 服務間往往通過 RESTful 來集成
  • 多語言,多數據庫,多運行時
  • 網絡的不可靠特性
  • 不同的團隊和交付周期

上述的這些微服務環境的特點,決定了在微服務場景中進行測試自然會面臨的一些挑戰:

  • 服務間依賴關系復雜
  • 需要為每個不同語言,不同數據庫的服務搭建各自的環境
  • 端到端測試時,環境準備復雜
  • 網絡的不可靠會導致測試套件的不穩定
  • 團隊之間的 溝通成本

測試的分層

相比于常見的 三層測試金字塔 ,在微服務場景下,這個層次可以被擴展為5層(如果將UI測試單獨抽取出來,可以分為六層)。

  • 單元測試
  • 集成測試
  • 組件測試
  • 契約測試
  • 端到端測試

和測試金字塔的基本原則相同:

  1. 越往上,越接近業務/最終用戶;越往下,越接近開發
  2. 越往上,測試用例越少
  3. 越往上,測試成本越高(越耗時,失敗時的信息越模糊,越難跟蹤)

單元測試

單元測試,即每個微服務內部,對于領域對象,領域邏輯的測試。它的隔離性比較高,無需其他依賴,執行速度較快。

對于業務規則:

  1. 商用軟件需要License才可以使用,License有時間限制
  2. 需要License的軟件在到期之前,系統需要發出告警
@Test
public void license_should_expire_after_the_evaluation_period() {
    LocalDate fixed = getDateFrom("2015-09-03");
    License license = new License(fixed.toDate(), 1);

boolean isExpiredOn = license.isExpiredOn(fixed.plusYears(1).plusDays(1).toDate());
assertTrue(isExpiredOn);

}

@Test public void license_should_not_expire_before_the_evaluation_period() { LocalDate fixed = getDateFrom("2015-09-05"); License license = new License(fixed.toDate(), 1);

boolean isExpiredOn = license.isExpiredOn(fixed.plusYears(1).minusDays(1).toDate());
assertFalse(isExpiredOn);

} </code></pre>

上面這個例子就是一個非常典型的單元測試,它和其他組件基本上沒有依賴。即使要測試的對象對其他類有依賴,我們會Stub/Mock的手段來將這些依賴消除,比如使用 mockito / PowerMock

集成測試

系統內模塊(一個模塊對其周邊的依賴項)間的集成,系統間的集成都可以歸類為集成測試。比如

  • 數據庫訪問模塊與數據庫的集成
  • 對外部 service 依賴的測試,比如對第三方支付,通知等服務的集成

集成測試強調模塊和外部的交互的驗證,在集成測試時,通常會涉及到外部的組件,比如數據庫,第三方服務。這時候需要盡可能真實的去與外部組件進行交互,比如使用和真實環境相同類型的數據庫,采用獨立模式(Standalone)的 WireMock 來啟動外部依賴的RESTful系統。

通常會用來做模擬外部依賴工具包括:

其中,mountbank還支持Socket級別的Mock,可以在非HTTP協議的場景中使用。

組件測試

貫穿應用層和領域層的測試。不過通常來說,這部分的測試不會訪問真實的外部數據源,而是使用同 schema 的內存數據庫,而且對外部service的訪問也會使用Stub的方式:

比如使用 h2 來做內存數據庫,并且自動生成schema。使用WireMock來Stub外部的服務等。

@Test
public void should_create_user() {
    given().contentType(ContentType.JSON).body(prepareUser()).
            when().post("/users").
            then().statusCode(201).
            body("id", notNullValue()).
            body("name", is("Juntao Qiu")).
            body("email", is("juntao.qiu@gmail.com"));
}

private User prepareUser() { User user = new User(); user.setName("Juntao Qiu"); user.setEmail("juntao.qiu@gmail.com"); user.setPassword("password"); return user; } </code></pre>

如果使用Spring,還可以通過 profile 來切換不同的數據庫。比如下面這個例子中,默認的profile會連接數據庫 jigsaw ,而 integration 的profile會連接 jigsaw_test 數據庫:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/jigsaw
    driver-class-title: com.mysql.jdbc.Driver
    username: root
    password: password


spring: profiles: integration

datasource: url: jdbc:mysql://localhost:3306/jigsaw_test driver-class-title: com.mysql.jdbc.Driver username: root password: password </code></pre>

組件測試會涉及到的組件包括:

  • URL路由
  • 序列化與反序列化
  • 應用對領域層的訪問
  • 領域層對數據的訪問
  • 數據庫訪問層

前后端分離

除了后端的測試之外,在目前的前后端分離場景下,前端的應用越來越復雜,在這種情況下,前端的組件測試也是一個測試的重點。

一個前端應用至少包括了這樣一些組件:

  • 前端路由
  • 模板
  • 前端的MVVM
  • 攔截器
  • 事件的響應

要確保這些組件組合起來還能如預期的執行,相關測試必不可少。這篇文章詳細討論了前后端分離之后的測試及開發實踐。

契約測試

在微服務場景中,服務之間會有很多依賴關系。根據 消費者驅動契約 ,我們可以將服務分為消費者端和生產者端,通常消費者自己會定義需要的數據格式以及交互細節,并生成一個契約文件。然后生產者根據自己的契約來實現自己的邏輯,并在持續集成環境中持續驗證。

Pact 已經基本上是 消費者驅動契約 (Consumer Driven Contract)的事實標準了。它已經有多種語言的實現,Java平臺的可以使用 pact-jvm 及相應的 maven / gradle 插件進行開發。

(圖片來源: Why you should use Consumer-Driven Contracts for Microservice integration tests )

通常在工程實踐上,當消費者根據需要生成了契約之后,我們會將契約上傳至一個公共可訪問的地址,然后生產者在執行時會訪問這個地址,并獲得最新版本的契約,然后對著這些契約來執行相應的驗證過程。

一個典型的契約的片段是這樣的(使用pact):

"interactions": [
    {
        "description": "Project Service",
        "request": {
            "method": "GET",
            "path": "/projects/11046"
        },
        "response": {
            "status": 200,
            "headers": {
                "Content-Type": "application/json; charset=UTF-8"
            },
            "body": {
                "project-id": "004c97"
            },
            "matchingRules": {
                "$.body.project-id": {
                    "match": "type"
                }
            }
        },
        "providerState": "project service"
    }
]

端到端測試

端到端測試是整個微服務測試中最困難的,一個完整的環境的創建于維護可能需要花費很大的經歷,特別是當有多個不同的團隊在獨立開發的場景下。

另一方面,從傳統的測試金字塔來看,端到端測試應該覆蓋那些業務價值最高的Happy Path。也就是說,端到端測試并不關注異常場景,甚至大部分的業務場景都不考慮。要做到這一點,需要在設計測試時,從最終用戶的角度來考慮,通過 用戶畫像 和 User Journey 來確定測試場景。

在端到端測試中,最重要的反而不是測試本身,而是環境的自動化能力。比如可以通過一鍵就可以將整個環境 provision 出來:

  • 安裝和配置相關依賴
  • 自動將測試數據Feed到數據庫
  • 自動部署
  • 服務的自動重啟

隨著容器技術和容器的編排技術的成熟,這部分工作已經可以比較好的自動化,依賴的工具包括:

一個典型的流程是:

  1. 搭建持續發布流水線
  2. 應用代碼的每一次提交都可以構建出docker鏡像
  3. 將docker鏡像發布在內部的docker-hub上
  4. 觸發部署任務,通過rancher的upgrade命令將新的鏡像發布
  5. 執行端到端測試套件

端到端測試還可以細分為兩個不同的場景:

  • 沒有用戶交互的場景,如一系列的微服務組成了一個業務API
  • 有用戶交互的場景

UI測試

最頂層的UI測試跟傳統方式的UI測試并無二致。我們可以使用BDD與實例化需求( Specification By Example )的概念,從用戶使用的角度來描述需求,以及相關的驗收條件。這里我們會使用WebDriver來驅動瀏覽器,并通過諸如 Capybara 等工具來模擬用戶的操作。

 

來自:http://icodeit.org/2016/10/testing-in-microservice-context/

 

 本文由用戶 atiz9753 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!