接口測試框架之Karate

Karate是什麼

Karate是一款將接口自動化測試、mock、性能測試集合到一起的測試框架。採用BDD語法,對於無編程能力的人也很容易;另外提供強大的JSON、XML斷言功能及併發執行。以上的內容翻譯自Karate官網,也許你看到這些描述時仍然不能直觀感受到Karate和其他接口測試框架的區別,接下來讓我們看一個Karate編寫的接口測試demo。以下是一個Graphql的接口,下面是利用Karate實現該接口測試的代碼
image.png

利用Karate編寫接口測試腳本

demo.Feature代碼

Feature: test a graphql demo    //feature描述信息,一個feature可以包含多個scenario
  Scenario Outline: test a graphql demo         //scenairo描述信息
    * text queryString =                  //定義graphql的查詢語句
    """{
    product(id: <id>) {
    name
    description
    category {
      name
    }
  }}"""
    Given url 'https://demo.getsaleor.com/graphql/'    //調用接口採用given-when-then格式
    And request {query : '#(queryString)'}
    When method post
    Then status 200                  //校驗接口返回200狀態碼
    And  match response.data.product.category.name == <expectValue>   
    //match是Karate中的關鍵字,常用於接口response的校驗
    Examples:                                    //可以看到Karate支持data-driven
      | id                 | expectValue |
      | "UHJvZHVjdDo4Nw==" | 'Footwear'  |
      | "UHJvZHVjdDo3Nw==" | 'Juices'   

DemoRunner代碼,以下代碼爲固定寫法,作用是運行feature中編寫的內容

package examples.demo;
import com.intuit.karate.junit5.Karate;

public class DemoRunner {
    @Karate.Test
    Karate testDemo() {
        return new Karate().feature("demo").relativeTo(getClass());
    }
}

生成美觀的測試報告,且能查看接口調用的Request和Responseimage.png通過上面的demo可以看到正如Karate官網所介紹的那樣,即便是無任何編程經驗的人只要稍加學習就能編寫Feature中的代碼實現接口調用。另外,該工具生成的測試報告不僅美觀還能查看接口調用的Request和Response,利於Debug。看到這裏感覺Karate似乎確實優於其他工具,但真實項目中實現接口測試時除接口調用外,還需考慮其他內容,Karate是否真的優於其他測試工具,還得看看在這些方面是否支持良好,首先讓我們看看接口測試中需要考慮的其他內容。

  • 1.測試數據管理,即能自動準備和清理測試數據

  • 2.配置信息管理,便於切換到不同環境運行

  • 3.接口Response Schema的校驗

  • 4.默認等待機制,保證接口測試的穩定性。因爲接口調用完成後,需要對接口調用結果進行校驗,可能是校驗接口Response Body中的內容是否於數據庫數據相等,也可能是直接查看數據庫數據是否正確,而數據落入一般晚於接口調用完成,所以在很多地方需要添加默認等待機制。

  • 5.對接口測試中用到的敏感數據進行脫敏處理等等

第1,4,5點歸納起來是“接口測試框架與編程語言結合”,便於對數據庫數據進行增刪改查,便於調用編程語言包好的方法,這些方法可能是對敏感數據的脫敏處理,可能是默認等待,可能是數據庫數據的二次處理等等。

接下來讓我們看看Karate在“與編程語言結合”、“處理數據庫數據”、“配置信息管理”以及“Response Schema校驗”方面支持是否良好?

Karate調用Java方法Demo(Karate只支持Java)

名稱爲“Service”的Java Class,該代碼中包含了兩個方法

package util;
import static java.lang.Thread.sleep;
public class Service {
    public void defaultWait(int i) throws InterruptedException {
        sleep(1000 * i);
        System.out.println("wait " + i + " seconds");
    }

    public String returnHello(String name) {
        return "Hello " + name;
    }
}

在Feature文件中調用編寫好的Java方法,可以看到因爲只能在Feature文件中調用,所以可讀性方面有點差。

Feature: call java function demo
  Scenario: call java function demo
    * def service =
  """
  function(arg) {
    var Service = Java.type('util.Service');
    var jd = new Service();
    return jd.returnHello(arg);
  }
  """
# in this case the solitary 'call' argument is of type string
    * def result = call service 'qiaotl'    //第一種調用方式
    * match result == 'Hello qiaotl'

# using a static method - observe how java interop is truly seamless !
    * def Service = Java.type('util.Service')
    * def javaService = new Service()
    * javaService.defaultWait(1)   //第二種調用方式

利用Karate操作數據庫數據Demo

實際Karate調用數據庫有兩種方式,第一種是利用Java編寫好增刪改查數據的方法,然後在Feature文件中調用Java方法,第二種是直接利用Karate提供的方法操作數據庫數據。第一種調用Java方法的方式上面的Demo已演示,這裏就演示如何利用Karate直接操作數據庫數據。Feature文件中直接連接數據庫查詢數據Demo代碼如下所示,可以看到和調用Java方法類似,在可讀性方面有點差。

Feature: call database demo
  Scenario: call database
# use jdbc to validate
* def config = { username: 'sa', password: '', url: 'jdbc:h2:mem:testdb', driverClassName: 'org.h2.Driver' }
* def DbUtils = Java.type('com.intuit.karate.demo.util.DbUtils')
* def db = new DbUtils(config)
# since the DbUtils returns a Java Map, it becomes normal JSON here !
# which means that you can use the full power of Karate's 'match' syntax
* def dogs = db.readRows('SELECT * FROM DOGS')
* match dogs contains { ID: '#(id)', NAME: 'Scooby' }

* def dog = db.readRow('SELECT * FROM DOGS D WHERE D.ID = ' + id)
* match dog.NAME == 'Scooby'

* def test = db.readValue('SELECT ID FROM DOGS D WHERE D.ID = ' + id)
* match test == id

利用Karate管理配置信息

在配置信息管理方面,Karate提供了原生支持,初始化項目時就會自動生成配置信息管理文件“karate-config.js”。如果有其他環境相關的配置信息,只需在此文件中添加即可,所以在配置信息管理方面Karate支持的還算比較好。

function fn() {    
  var env = karate.env; // get system property 'karate.env'
  karate.log('karate.env system property was:', env);
  if (!env) {
    env = 'dev';
  }
  var config = {
    env: env,
    myVarName: 'someValue'
  }
  if (env == 'dev') {
    // customize
    // e.g. config.foo = 'bar';
  } else if (env == 'e2e') {
    // customize
  }
  return config;
}

利用Karate校驗Response Schema

按官網的介紹Karate提供了一種比JSON-schema更簡單且功能更強大的方式來驗證接口的Response Schema,即利用Karate對Response Schema進行校驗,需要先學習Schema校驗中的語法規則。例如如下小例子

* def foo = ['bar', 'baz']
# 校驗foo是一個數組
* match foo == '#[]'
# 校驗foo是一個長度爲2的數組
* match foo == '#[2]'
# 校驗foo是一個長度爲2的數組,且數組的值都是string類型
* match foo == '#[2] #string'
# 數組中每個element都有個length 屬性,且length的值都是3
* match foo == '#[]? _.length == 3'
# 校驗數組的每個值都是string且長度都等於3
* match foo == '#[] #string? _.length == 3'

如果對一個接口的Response Schema進行校驗,Feature中的代碼如下,可以看到相較於直接採用Json Schema的接口測試工具(例如Rest-Assured),利用Karate進行Response Schema校驗需要單獨學習Karate的語法,有一定的學習成本。

* def oddSchema = { price: '#string', status: '#? _ < 3', ck: '##number', name: '#regex[0-9X]' }
* def isValidTime = read('time-validator.js')
When method get
Then match response ==
  """
  { 
    id: '#regex[0-9]+',
    count: '#number',
    odd: '#(oddSchema)',
    data: { 
      countryId: '#number', 
      countryName: '#string', 
      leagueName: '##string', 
      status: '#number? _ >= 0', 
      sportName: '#string',
      time: '#? isValidTime(_)'
    },
    odds: '#[] oddSchema'  
  }
  """

可以看到使用Karate,接口調用、校驗的核心腳本都在Feature文件中。另外支持很多自定義關鍵字,例如match、contain等,以此進一步降低接口測試編寫腳本成本,真正讓無任何編程經驗的人也能快速上手。如果只是編寫“調用一個接口”這樣簡單的場景,Karate的簡單易用以及原生支持BDD確實很不錯。但對於一個複雜系統,接口測試中需要覆蓋的場景不緊緊是接口調用本身,而Karate中“核心腳本都在Feature文件中”的特點恰恰讓該工具出現了局限性,例如調用Java方法,連接數據庫等。正所謂成也蕭何敗也蕭何。

開篇提到ThoughtWorks的技術雷達中有推薦該框架,那具體信息如何呢?該框架確實出現在2019年上半年的技術雷達中,處於Access。技術雷達中對該框架的詳細描述是“Karate是一個API測試框架,其特殊之處是直接用Gerkin編寫而不依賴任何通用編程語言。Karate實際是一個描述API 測試的域語言,儘管這種方法很有趣,並且爲簡單測試提供了可讀性很強的文檔,但用於match和校驗payload的特定語言可能變得語法繁重和難於理解。從長遠來看以這種風格編寫的複雜測試是否易用閱讀和易用理解還有待觀察”。可以看到技術雷達中即提到該工具的亮點同時也提到這種風格的編寫對複雜測試可能不易閱讀和難於理解。

除此之外,開篇我們還提到這個框架總是和Graphql接口測試綁定在一起介紹,那麼該工具在Graphql接口測試中有特殊優勢麼?接下來讓我們看看利用Karate調用Graphql接口和利用Rest-Assured(另外一款接口測試工具)調用Graphql接口的對比,使用的被測接口是第一個Demo中的接口。

Karate調用Graphql接口

demo.Feature代碼

Feature: test a graphql demo    //feature表述信息,一個feature可以包含多個scenario
  Scenario Outline: test a graphql demo         //scenairo描述信息
    * text queryString =                       //定義graphql的查詢語句
    """{
    product(id: <id>) {
    name
    description
    category {
      name
    }
  }}"""
    Given url 'https://demo.getsaleor.com/graphql/'   //調用接口採用given-when-then格式
    And request {query : '#(queryString)'}
    When method post
    Then status 200                  //校驗接口返回200狀態碼
    And  match response.data.product.category.name == <expectValue>  
 //match是Karate中的關鍵字,常用於接口response的校驗
    Examples:                     //可以看到Karate支持data-driven寫法
      | id                 | expectValue |
      | "UHJvZHVjdDo4Nw==" | 'Footwear'  |
      | "UHJvZHVjdDo3Nw==" | 'Juices'   

Rest-Assured調用Graphql接口

package com.github.graphqlDemo
import groovy.json.JsonSlurper
import org.junit.Assert
import spock.lang.Specification
import static io.restassured.RestAssured.given

class GraphqlTest extends Specification {

    def "call graphql demo"() {
        given: "get graphql query string"    //採用spock框架實現BDD
        def queryString = new QueryBody()
                .setId(id)
                .getQueryBody()    //通過引入模版引擎工具參數化接口的request body
        when: "call the api"
        def res = given().baseUri("https://demo.getsaleor.com")
                .header("Content-Type", "application/json")
                .body(queryString)
                .post("/graphql")
                .then().statusCode(200)
                .extract().response().getBody().asString()     
             //調用接口並獲取response body
        def jsonResponse = new JsonSlurper().parseText(res)    
       //將string類型response body轉換爲數據對象

        then: "should return correct response"
        Assert.assertEquals(jsonResponse.data.product.category.name, expectValue)  
       //校驗response body內容
        where:
        id                 | expectValue     //採用了data-driven
        "UHJvZHVjdDo4Nw==" | "Footwear"
        "UHJvZHVjdDo3Nw==" | "Juices"
    }
}

可以看到Rest-Assured也可以實現Graphql接口測試,因爲Graphql接口實際也是Http請求。那Karate是否有特殊優勢呢?實際沒有,例如Karate支持在請求的Request Body中傳入參數,Rest-Assured雖然不原生支持,但可以藉助模版引擎工具實現參數化。Karate支持BDD,Rest-Assured雖然不原生支持,但可以套用Groovy官網的BDD框架Spock實現BDD。看起來Rest-Assured使用過程中需要套用其他框架,增加了使用成本,但正是因爲Rest-Assured沒有集成各種其他框架讓其保持了靈活性,可以和多種編程語言、其他測試框架無縫銜接。

結束語

如果在接口測試工具中一定要做一個選擇,對於Java技術棧的同學來說還是強烈建議使用Rest-Assured,第一該工具2010年就推出了第一個release版本,github上的star數已超過4000+,使用非常廣泛。第二工具名稱雖然叫Rest-Assured,但可以利用該工具完成Graphql的接口測試。第三該工具支持和多種語言結合使用,例腳本語言Groovy。就接口測試而言最好還是採用腳本語言,因爲接口測試本身沒有複雜的邏輯處理,腳本語言足夠了,選用Java這一類語言重了些,會增加接口測試維護成本。如果你對Rest-Assured感興趣可以訂閱Gitchat中的“接口測試實戰”專欄進行學習(https://gitbook.cn/gitchat/column/5dbbe297e29af65d1d01b8fc)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章