Karate framework – Automation check web-services trong 1 nốt nhạc

Karate – Web-Services Testing Made Simple

KISS – Keep it simple, stupid

Phiên bản việt hoá: hãy giữ cho mọi thứ đơn giản nhất 1 cách có thể, đến thằng ngu cũng có thể xài được =]]

Với Karate thì mọi thứ luôn tuân theo tâm pháp KISS, vạn vật đều trở nên cực kỳ đơn giản và dễ dàng hết mức có thể ( đến con gà cũng có thể sử dụng khi muốn làm automation với web-services). Đối với mình sau khi trải qua 69 cái frameworks như rest-assured, katalon hay robot framework cho tới các thư viện dành cho việc automation với web-services như requests(python), httpclient(java), client( bên C# mình không nhớ chính xác tên của nó lắm) thì có lẽ Karate là framework giúp cho mọi người làm viêc với kiểm thử tự động cho web-services ở mức dễ dàng nhất có thể.

Để hiểu rõ hơn những ưu điểm cũng như bất cập của Karate framework so với những framework khác thì mình sẽ làm bảng so sánh giữa Karate với các test framework sử dụng Cucumber, cũng như so sánh chi tiết giữa Karate và Rest-assured.

Test web-services frameworks với Cucumber vs Karate

No. Test framework sử dụng Cucumber Karate framework
Cần phải hiện thưc code cho những steps definitions (với các framewok sử dụng Cucumber thì phải thực hiện việc triển khai code cho những steps definition ở trong file *.feature) Không (Karate đã thực hiện việc triển khai code cho những steps definition liên quan tới web-services cũng như JSON hay XML)
Khả năng bảo trì với  1 layer code Không (Khi có sự thay đổi về bussiness logic đôi khi cần phải maintain 2 layer code bao gồm trong *.feature và trong step definitions)  (Khi có sự thay đổi thì chỉ cần thay đổi trong karate-scripts)
Khả năng dễ đọc của test scripts (*.feature được viết bằng ngôn ngữ tư nhiên, tuy nhiên việc khai báo và hiện thực steps definition phải chính xác) Không (Karate tuy được xây dựng trên nền tảng Cucumber nhưng nó sử dụng ngôn ngữ của riêngg mình vì thế nó có thể được coi như là 1 DSL trong test web-services và đôi khi có thể code trực tiếp JavaScript trong *.feature file)
Khả năng sử dụng *.feature trong *.feature khác Không (các framework sử dụng cucumber hiện giờ không hỗ trợ việc gọi lồng *.feature file)
Khả năng sử dụng data driven với data file bên ngoài Không (data driven với Cucumber không dễ dàng để thay đổi số lượng data cũng như sử dụng 1 data file từ bên ngoài) (dễ dàng sử dụng các data file bên ngoài)
Khả năng run test ở chế độ parallel Không (tuy nhiên mình từng build 1 fw cucumber với testNG để support việc run parallel, lúc này đòi hỏi khả năng coding cao hơn để xây dựng fw)
Khả năng thiết lập thực thi step ở chế độ 1 lần trong cả *.feature (cái này mình sẽ giải thích chi tiết ở dưới) Không (trong cucumber không thể cấu hình background steps chi run 1 lần duy nhất được)

Rest-assured vs Karate

Phần này mình sẽ tóm tắt những điểm chính còn bạn nào muốn coi chi tiết thì có thể dùng link này.

Features REST-assured Karate References / Comments
BDD Syntax
Hoàn toàn sử dụng DSL(Domain specific language) Không (Sử dụng Fluent Interface) DSL vs Fluent Interface.
Độ ổn định Cao Mới phát triển trong năm 2017. Tuy nhiên Karate đạt được 400+ GitHub “stars” trong 8 tháng là 1 dấu hiệu tốt. Ngoài ra Karate còn cung cấp các ví dụ cũng như tài liệu khá chi tiết.
JsonPath Groovy GPath JayWay JsonPath GPath có 1 vài hạn chế và việc cập nhật là không khả thi
XPath Có nhưng không thể sử dụng cho XML. W3Cstandard XPath sử dụng thư viện Java built-in XML, có thể sử dụng cho cả XML hoặ JSON.
Test-Scripting Language Java Karate-Script (Cucumber / Gherkin)
Test Scripts phải được compiled Không Tests script là là file dạng txt và ko cần phải compile như Java.
Khả năng debug Có với 3 tuỳ chọn:
Dev-mode HTML report.
Karate UI: debug và thực thi test steps v0.5.0Built-inDebugclass v0.6.0
Test Runner Sử dụng testNG hoặc JUnit Sử dụng testNG hoặc JUnit Karate không cần phải khai báo thư viện test framework trong project vì bản thân Karate core đã thực hiện việc đóng gói những thư viện này lại.
Validate All Payload values in one step Không ( cần phải sử dụng thư viện ngoài, ngoài ra thì không thể thực hiện việc kiểm tra các nested object bên trong response trả về, cũng như kiểm tra với việc loại bỏ những thông tin có tính ngẫu nhiên như datetime, timestamp.) Có ( cung cấp sẵn cơ chế kiểm tra nested object cũng như loại bỏ những thông tin có tính ngẫu nhiên khi kiểm tra)
Built-in data-type, conditional-logic và RegEx validations Không
Validate schema chỉ với 1 step Không
Built-in JSON Schema and XML Schema validation support Có. Tham khảo link sau
Hỗ trợ việc kiểm tra JSON hay XML 1 cách trực tiếp Không

“{ \”name\”: \”Billie\” }”
“”

{ name: ‘Billie’ }

Với Karate thì không cần phải thêm ký tự “\” trước những ký tự đặc biệt
Example – JSON assertions @Testpublic void

resource_returns_200() {when().

get(“/lotto/{id}”,5).

then().

statusCode(200).

body(“lotto.lottoId”, equalTo(5),

“lotto.winners.winnerId”, containsOnly(23,54));

}

Scenario:lotto resource returns 200 with expected id and winners

Given path ‘lotto’,5
When method get
Then status200
And match$.lotto.lottoId==5
And match$.lotto.winners[*].winnerIdcontains only[23, 54]

Example – GET with params given().

param(“key1”, “value1”).

param(“key2”, “value2”).

when().

get(“/somewhere”).

then().

body(containsString(“OK”));

Given param key1 = ‘value1’
And param key2 = ‘value2’
And path ‘somewhere’
When method get
Then match response contains ‘OK’
Data Driven Testing Có ( sử dụng data driven của testNG)

REST-Assured example

Có ( sử dụng data driven bằng Cucumber table hoặc là data file ở ngoài)
Karate example
SOAP support Không
HTTPS / SSL without certificates Có nhưng có vài hạn chế
File Upload / Multipart Support Có ( nhưng tồn tại nhiều bugsLibraries|Content-Type|Dependencies| ‘multipart/related’not supported|questionson ‘multipart/mixed’)
URL encoded HTML Form data
Cookies
Parallel Execution of Tests Không.
Link 1
Link 2
Số lượng dòng code khi cần viết test Nhiều Ít.

Link so sánh 1

Link so sánh 2

Test Reports Built-in Không ( sử dụng report của TestNG hoặc JUnit hoặc của thư viện ngoài) Có (Karate có thể cho riêng mình report dưới dạng log hoặc text ngoài ra thì có thể sử dụng với các test report của TestNG, JUnit hoặc của các thư viện ngoài) Có thể sử dụng thư viện cucumber-reportinglibrary.

Những tính năng chính của Karate framework

Sử dụng Cucumber với cú pháp BDD

Đây có thể xem như là 1 trong những tính năng chính của Karate framework, so với những thư viện hay framework dành cho việc test web-services. Karate framework cung cấp giải pháp đơn giản nhất cũng như giúp cho những người sử dụng không có khả năng về programming có thể đẻ ra test script 1 cách nhanh chóng. Ngoài ra với việc test scripts chỉ là những file text đơn giản thì có thể sử dụng bất kỳ công cụ nào để viết kịch bản kiểm tra tự động. Và điều đặc biệt của Karate framework chính là việc tác giả đã hiện thực hoá toàn bộ những steps definition dành cho việc check web-services, ở đây framework cung cấp gần như mọi thứ, từ các method của web-services như post,get,put, etc… Cho tới những step dành cho việc trích xuất thông tin từ JSON hay XML và dĩ nhiên không thể thiếu phần quan trọng nhất đó là những steps dành cho việc thực hiện verify hay assert.

Dưới đây là 1 mẫu test scripts khi viết bằng Karate:

Feature: Register Account With Rest Request
Background:
* def tokenPattern = “‘csrfmiddlewaretoken’ value='([\\d\\w]+)'”
* def regxFunction = read(‘../../regular-function.js’)
* def generateEmail = read(‘../../generate-email.js’)
* url ‘http://localhost:9999’
* def email = generateEmail()

Scenario: Register with valid data
Given path “/account/signup/”
And request { “email”: “test”, “pass”: “123”}
When method post
Then status 200
And match response.exist == ‘false’
And match response.userId == ‘1’

Scenario: Register with invalid data
Given path “/account/signup/”
And request { “email”: “test”, “pass”: “456”}
When method post
Then status 400
And match response.exist == ‘true’
And match response.userId == ‘null’


Parallel execution

Karate cung cấp khả năng run tests ở chế độ song song, lúc này có thể giúp bạn giảm thời gian chạy của 1 test suite đi khá nhiều (Với các framework khác hay tự build 1 framework thì việc run test với chế độ parallel luôn là thức gây đau đầu và hại não, code liên quan tới multithreading luôn là thứ gây nhức đầu hại não nhất =]] bản thân mình từng mất ăn mất ngủ và ngồi debug hết 2 3 cái bàn phím khi build 1 fw automation cho phép run ở parallel ). Để run karate ở chế độ parallel thì các bạn có thể kham thảo code mẫu dưới đây:

package demo.parallel;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.junit.Test;
import com.intuit.karate.cucumber.CucumberRunner;
import com.intuit.karate.cucumber.KarateStats;
import net.masterthought.cucumber.Configuration;
import net.masterthought.cucumber.ReportBuilder;

public class ParalellDemoRunner {

@Test
public void testParallel() {
String karateOutputPath = “target/surefire-reports”;

// Có thể tăng giảm số lượng thread tuỳ theo nhu cầu ở đây, ở ví dụ này minh run với 5 threads
KarateStats stats = CucumberRunner.parallel(getClass(), 5, “target/surefire-reports”);
generateReport(karateOutputPath);
assertTrue(“scenarios failed”, stats.getFailCount() == 0);
}

private static void generateReport(String karateOutputPath) {
Collection jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] { “json” }, true);
List jsonPaths = new ArrayList(jsonFiles.size());
for (File file : jsonFiles) {
jsonPaths.add(file.getAbsolutePath());
}
Configuration config = new Configuration(new File(“target”), “demo”);
ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);
reportBuilder.generateReports();
}
}

Còn dưới đây là sample *.feature

Feature: Register Account With Rest Request Run In Parallel
Background:
* def tokenPattern = “‘csrfmiddlewaretoken’ value='([\\d\\w]+)'”
* def regxFunction = read(‘../../regular-function.js’)
* def generateEmail = read(‘../../generate-email.js’)
* url baseUrl
* path “/account/signup/”
* method get
* string body = response
* def token = regxFunction(body,tokenPattern,1)
* def email = generateEmail()

Scenario: Register with valid data
Given path “/account/signup/”
And form field csrfmiddlewaretoken = token
And form field email = ‘sampleemail@sample.com’
And form field password = ‘123’
When method post

Scenario: Register with missing token field data
Given path “/account/signup/”
And form field email = ‘sampleemail@sample.com’
And form field password = ‘123’
When method post

Scenario: Register with missing email field data
Given path “/account/signup/”
And form field csrfmiddlewaretoken = token
And form field password = ‘123’
When method post

Scenario: Register with missing password field data
Given path “/account/signup/”
And form field csrfmiddlewaretoken = token
And form field email = ‘sampleemail@sample.com’
When method post

Khi run Karate với chế độ parallel thì có vài lưu ý sau cho mọi người:

  • Nên sử dụng 3rd test report, khi run ở chế độ parallel thì Karate sử dụng cơ chế execute bởi core engine của nó. Và default report built-in của Karate chỉ là file log dạng txt, vì thế sẽ rất khó để phân tích.
  • Không sử dụng annotation @RunWith khi muốn xài tính năng parallel execution.
  • Khi sử dụng tính năng execute parallel thì có thể sử dụng object KarateStats để lấy được toàn bộ thông tin và kết quả của test suite sau khi run.
  • Vì sử dụng core engne để thực hiện việc execution parallel nên mọi report plugin khi được cấu hình trong @CucumberOptions sẽ bị bỏ qua. Tuy nhiên những cấu hình khác trong @CucumberOptions vẫn có thể sử dụng được như tags, listener, etc…

Khả năng debug với GUI built-in trong Karate

So với những framework hay thư viện khác thì việc debug đôi khi không hề dễ dàng cho những người dùng không có kỹ năng lập trình. Với Karate thì hiện tại người dùng có thể thực hiện việc debug dễ dàng thông qua GUI built-in. Không còn phải tìm điểm để đặt break-point, chỉ cần click-and-view. Với Karate khi debug với GUI thì bạn có thể trực tiếp thay thế những variable trong từng steps.

Screen Shot 2017-12-23 at 5.41.05 PM

Khi sử dụng tính năng Debug với Karate thì có vài điều cần lưu ý như sau:

  • Không sử dụng annotation @RunWith khi muốn xài tính năng debug.
  • Tính năng này vẫn đang được phát triển nên có thể vẫn có bugs
  • Với những file external thì chế độ debug chỉ sẽ load và lưu trong memory, vì thế nếu các bạn cần thay đổi thì phải tắt và run debug lại.

Sử dụng Java/JavaScript function với Karate

Karate framework cho phép bạn sử dụng những function được khai báo trong file JavaScript hay Java classes. Đây là tính năng khá tiện lợi nếu như project có nhiều bussniess logic phức tạp và cần sử dụng programming để giải quyết. Với tính năng này các bạn có thể giải quyết những vấn đề sau:

  • Dùng để đóng gói những logic phức tạp.
  • Kết hợp với những thư viện khác của Java.
  • Dùng để tạo dynamic data.

Dưới đây là mẫu *.feature sử dụng tính năng gọi external file với JavaScript/Java

Feature: Demo call java method
Background:

* def getStatusCode =
“””
function(arg) {
var JavaDemo = Java.type(‘demo.call.JavaClass’);
var javaDemo = new JavaDemo();
return javaDemo.getResponseCode(arg);
}
“””

* def singleJs = read(‘single-function.js’)
* def multipleJs = read(‘multiple-function.js’)

Scenario: Call Java non-static method with passed
Given def result = call getStatusCode ‘Sample Test’
Then match result == ‘Return data code with append prefix: Sample Test’

Scenario: Call Java non-static method with failed
Given def result = call getStatusCode ‘Sample Test’
Then match result == ‘Return data code with append prefix: Sample Test failed’

Scenario: Call Java static method with passed
Given def JavaDemo = Java.type(‘demo.call.JavaClass’)
When def result = JavaDemo.getResponeData(‘sample args world’)
Then match result == ‘Return data with append prefix: sample args world’

Scenario: Call Java static method with failed
Given def JavaDemo = Java.type(‘demo.call.JavaClass’)
When def result = JavaDemo.getResponeData(‘sample args world’)
Then match result == ‘Return data with append prefix: sample args world failed’

Scenario: Call JS single function with passed
Given def result = singleJs(‘Sample Test JS’)
Then match result == ‘Return data with prefix: Sample Test JS’

Scenario: Call JS single function with failed
Given def result = singleJs(‘Sample Test JS’)
Then match result == ‘Return data with prefix: Sample Test JS failed’

Scenario: Call JS multiple function with print date
Given def func = multipleJs()
When def date = func.getNewDate()
And def result = func.getNumberPlus2(2)
Then match result == 4
Then match date == 5

Một số lưu ý khi sử dụng tính năng gọi external functions:

  • Để gọi external functions thì bạn sẽ dụng keyword callcallonce.
  • Với JavaScript thì Karate luôn coi 1 file .js sẽ là 1 function, để có thể thiết lập nhiều functions trong 1 JavaScript file thì các bạn có sử dụng theo sample code dưới đây:
    function() {
    return {
    getCurrentDate: function(){ return 'date' },
    getMonth: function(){ return 'month' },
    getToken: function(args){ //code to return data}
    }}
  • Nếu trong *.feature bạn chỉ gọi JavaScript/Java file với tên file thì Karate sẽ tìm kiếm trong cùng folder chứa runner class.
  • Để quản lý tốt nhất file chứa external function thì các bạn có thể config trong pom.xml, hình dưới đây sẽ copy những file external trong folder resources:

Screen Shot 2017-12-23 at 6.24.11 PM

  • callonce được dùng khi bạn muốn external function chỉ được thực thi 1 lần duy nhất trong toàn bộ *.feature file(nó giống như before suites annotation ở testNG

Data driven với dynamic data

Nếu bạn nào đã từng sử dụng nhiều với Cucumber thì cũng biết việc sử dụng data driven thì chủ yếu data sẽ thể hiện trên chính *.feature file, khi đó chúng ta thuường sẽ gặp những giới hạn về việc run test với dynamic data hay quản lý test data mà không gây ảnh hưởng *.feature file.

Với Karate thì giờ đây bạn đã có 1 framwork sử dụng Cucumber và cho phép bạn run data driven với external data files, lúc này các bạn có thể sử dụng excel, csv, hay text file để chứa test data.

Dưới đây làm sample code cho tính năng này:

call-feature-csv.feature

Feature:

Background: 

* url http://localhost:9966’

Scenario: Register with valid data

Given path “/account/login/”

And request { “username”: “#(username)”, “pass”: “#(password)”}

When method post

data-driven-karate-way.feature

Feature: Demo call Data Driven Karate

Background:

* def listAssert =

“””

function(listResult){

var errorMsg=””;

for(var index in listResult) {

if(listResult[index].indexOf(‘Failed’)===0)

{

errorMsg+=listResult[index]+”;”

}

}

return errorMsg;

}

“””

* def listRequest =

“””

function(listResult){

var errorMsg=””;

for(var index in listResult){

if(listResult[index].responseStatus === 200)

{

karate.log(listResult[index].response.isEnalbed);

if(listResult[index].response.isEnalbed===”false”)

{

karate.log(“Run append msg”);

errorMsg+= “Failed with data “+ listResult[index].data[index] + ” error: ” + listResult[index].response +”; “;

}

}

else

{

errorMsg+= “Failed with data “+ listResult[index].data[index] + ” error: ” + listResult[index].responseStatus +”; “;

}

}

return errorMsg;

}

“””

* def dataGeneratorClass = Java.type(‘demo.data.DataReader’)

Scenario: Sample karate data driven pass

Given table sampleDataPass

| a | b | c     |

| 4 | 4 | 0     |

| 7 | 5 | 2     |

| 9 | 5 | 4     |

Given def test = read(‘call-feature-table.feature’)

When def results = call test sampleDataPass

And def result = $results[*].result

Then def msg = listAssert(result)

And match msg !contains ‘Failed’

Scenario: Sample karate data driven failed

Given table sampleDataPass

| a | b | c     |

| 4 | 4 | 0     |

| 7 | 5 | 4     |

| 19| 5 | 4     |

| 9 | 5 | 4     |

| 19| 5 | 14    |

Given def test = read(‘call-feature-table.feature’)

When def results = call test sampleDataPass

And def result = $results[*].result

Then def msg = listAssert(result)

And match msg !contains ‘Failed’

Scenario: Sample karate data driven with external file

Given json data = dataGeneratorClass.getCsvData()

And def test = read(‘call-feature-csv.feature’)

When def results = call test data

Then def msg = listRequest(results)

And match msg !contains ‘Failed’


Tài liệu tham khảo


Thiệt ra nó còn nhiều cái hay ho lắm, mà trong thời gian giới hạn 
mình chỉ có thể liệt kê ra những điểm chính của nó thôi. Các bạn sử 
dụng thử có gì thắc mắc có thể liên hệ mình.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s