Trong lĩnh vực kiểm thử API automation, REST Assured nổi lên như một thư viện mạnh mẽ và phổ biến. Bài viết này sẽ cung cấp cho bạn cái nhìn sâu sắc về REST API, API Testing, API Automation Testing và cách REST Assured có thể đơn giản hóa quy trình kiểm thử của bạn.
Mục Lục
API Là Gì?
API (Application Programming Interface) hay Giao diện lập trình ứng dụng, là một tập hợp các hàm (functions) cho phép các ứng dụng khác nhau giao tiếp và trao đổi dữ liệu. API đóng vai trò là cầu nối giữa các hệ thống phần mềm, cho phép chúng tương tác mà không cần hiểu chi tiết về cách hệ thống kia hoạt động.
API Testing Là Gì?
Trong kiến trúc phần mềm hiện đại, mô hình 3 lớp (3-tier architecture model) rất phổ biến. Mô hình này bao gồm:
- Presentation Tier (Tầng trình bày): Tương tác trực tiếp với người dùng, hiển thị giao diện và tiếp nhận thông tin.
- Logic Tier (Tầng logic): Xử lý nghiệp vụ (business logic) của hệ thống và điều phối dữ liệu giữa tầng Presentation và Data.
- Data Tier (Tầng dữ liệu): Lưu trữ và truy xuất dữ liệu từ cơ sở dữ liệu hoặc các file hệ thống.
Các lớp này giao tiếp với nhau thông qua các dịch vụ (services) được cung cấp bởi mỗi lớp. Tầng Logic, với sự phức tạp trong xử lý nghiệp vụ, là trọng tâm của API Testing.
API Testing tập trung vào việc gửi các yêu cầu (request) đến API và kiểm tra xem kết quả trả về (response) có đáp ứng mong đợi hay không. Dữ liệu trả về thường ở định dạng JSON (với RESTful API) hoặc XML (với SOAP).
Để hiểu rõ hơn về REST và SOAP, bạn có thể tham khảo các bài viết chuyên sâu về so sánh REST và SOAP.
REST Assured Là Gì?
REST Assured là một thư viện Java DSL (Domain Specific Language) được xây dựng trên nền HTTP Builder, giúp đơn giản hóa việc gửi request và kiểm tra response trong kiểm thử API. Thư viện này cung cấp một giao diện trực quan, dễ sử dụng, cùng với cộng đồng hỗ trợ lớn mạnh, khiến nó trở thành lựa chọn hàng đầu cho các nhà kiểm thử.
Kiểm Thử REST API với REST Assured: Ví Dụ Thực Tế
Hãy xem xét một dự án xây dựng hệ thống tìm kiếm video cho thị trường Nhật Bản. Ngoài website, dự án còn cung cấp API cho phép truy cập từ bên ngoài. Tester cần đảm bảo các API endpoint hoạt động chính xác.
Test case 1: Tìm kiếm video theo từ khóa, giới hạn số lượng video trả về.
- Từ khoá: API Testing
- Tham số:
{tukhoa}
: Từ khóa tìm kiếm video{soluongvideo}
: Số lượng video trả về (ví dụ: 4)
- URL Endpoint:
sentayho.com.vn/search/{tukhoa}/videos.json?num_of_videos={soluongvideo}
- Kết quả mong muốn: Dữ liệu JSON chứa link, tiêu đề và mô tả video.
- Điều kiện pass:
- Response trả về HTTP Status Code 200 (OK).
- Kết quả chứa từ khóa tìm kiếm.
- Tối đa 4 video được trả về.
- Không có video nào bị trùng lặp.
Test case 2: Tìm kiếm video theo ID, trả về thông tin chi tiết và video liên quan (suggestion video), giới hạn số lượng video liên quan.
- Tham số:
{video_id}
: ID của video{soluongvideo}
: Số lượng video liên quan (ví dụ: 4)
- URL Endpoint:
sentayho.com.vn/video/list/info.json?video_ids={video_id}&num_related_return={soluongvideo}
- Kết quả mong muốn: Dữ liệu JSON chứa thông tin chi tiết video và danh sách video liên quan.
- Điều kiện pass:
- Response trả về HTTP Status Code 200 (OK).
- Kết quả liên quan đến video được tìm kiếm.
- Tối đa 4 video liên quan được trả về.
- Không có video liên quan nào bị trùng lặp.
Làm thế nào để giải quyết bài toán này? Hãy cùng tìm hiểu cách xây dựng framework kiểm thử với REST Assured.
Xây Dựng Framework Kiểm Thử REST API với REST Assured
Để giải quyết các bài toán trên, tester cần xây dựng một framework kiểm thử tích hợp REST Assured, đảm bảo tính tái sử dụng (re-usability) để có thể áp dụng cho các API endpoint khác mà không cần viết lại code.
1. Cấu trúc Project:
Khởi tạo một Maven project với cấu trúc thư mục như sau:
Utils
: Chứa các class hỗ trợ.HelperMethods.java
: Các hàm helper có thể tái sử dụng.RestUtil.java
: Các method liên quan đến REST Assured.
ApiTests
: Chứa các class test case.Example1Test.java
: Test case 1.Example2Test.java
: Test case 2.
TestSuite
: Chứa class chạy toàn bộ test suite.AllApiTest.java
: Test Runner.
2. Cài Đặt Dependencies:
Thêm các dependencies sau vào pom.xml
:
- JUnit: Test framework.
- Hamcrest: Thư viện assertion.
- REST Assured: Thư viện chính cho kiểm thử API.
<dependencies>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-junit</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>2.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.restassured</groupId>
<artifactId>rest-assured</artifactId>
<version>2.8.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<includes>
**/AllApiTest.class
</includes>
</configuration>
</plugin>
</plugins>
</build>
3. Viết Code:
RestUtil.java
: Class chứa các method chung để thao tác với REST Assured.
package Utils;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import static io.restassured.RestAssured.*;
public class RestUtil {
//Global Setup Variables
public static String path; //Rest request path
/**
* ***Sets Base URI***
* Before starting the test, we should set the RestAssured.baseURI
*/
public static void setBaseURI(String baseURI) {
RestAssured.baseURI = baseURI;
}
/**
* ***Sets base path***
* Before starting the test, we should set the RestAssured.basePath
*/
public static void setBasePath(String basePathTerm) {
RestAssured.basePath = basePathTerm;
}
/**
* ***Reset Base URI (after test)***
* After the test, we should reset the RestAssured.baseURI
*/
public static void resetBaseURI() {
RestAssured.baseURI = null;
}
/**
* ***Reset base path (after test)***
* After the test, we should reset the RestAssured.basePath
*/
public static void resetBasePath() {
RestAssured.basePath = null;
}
/**
* ***Sets ContentType***
* We should set content type as JSON or XML before starting the test
*/
public static void setContentType(ContentType Type) {
given().contentType(Type);
}
/**
* ***search query path of first example***
* It is equal to "barack obama/videos.json?num_of_videos=4"
*/
public static void createSearchQueryPath(String searchTerm, String jsonPathTerm, String param, String paramValue) {
path = searchTerm + "/" + jsonPathTerm + "?" + param + "=" + paramValue;
}
/**
* ***Returns response***
* We send "path" as a parameter to the Rest Assured’a "get" method and "get" method returns response of API
*/
public static Response getResponse() {
//System.out.print("path: " + path +"n");
return get(path);
}
/**
* ***Returns JsonPath object***
* First convert the API’s response to String type with "asString()" method.
* Then, send this String formatted json response to the JsonPath class and return the JsonPath
*/
public static JsonPath getJsonPath(Response res) {
String json = res.asString();
//System.out.print("returned json: " + json +"n");
return new JsonPath(json);
}
}
HelperMethods.java
: Class chứa các hàm helper để sử dụng lại trong các test case.
package Utils;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import org.junit.Assert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class HelperMethods {
/**
* Verify the http response status returned. Check Status Code is 200?
* We can use Rest Assured library’s response’s getStatusCode method
*/
public static void checkStatusIs200(Response res) {
assertEquals("Status Check Failed!", 200, res.getStatusCode());
}
/**
* Get Video Ids (For example 1)
* We can use get method of Rest Assured library’s JsonPath Class’s get method
* Part of a response is shown below:
* "items": [{
* "id": 519377522,
* ….
* We can get all id’s with this code -> “jp.get(“items.id”);” this will return all id’s under “items” tag.
*/
public static ArrayList getVideoIdList(JsonPath jp) {
ArrayList videoIdList = jp.get("items.id");
return videoIdList;
}
/**
* Get Related Video Ids (For example 2)
* Structure of response is shown below:
* items:
* "related": [{
* "id": 519148754,
* ….
* In order to get all id’s under related tag,
* We can use JsonPath’s get method like “jp.get(“items.related.id”);”
* It will give us all id’s under related tag.
*/
public static ArrayList getRelatedVideoIdList(JsonPath jp) {
//jp.get method returns all ids
ArrayList relatedVideoList = jp.get("items.related.id");
/*
Result of relatedVideosList:
[[519148754, 519115214, 519235328, 519235341]]
I have to convert above result in this format:
[519148754, 519115214, 519235328, 519235341]
In order to split first element of “relatedVideosList” and assign it to a new ArrayList (as splittedRelatedVideoList)
I did below operation.
*/
ArrayList splittedRelatedVideoList = (ArrayList) relatedVideoList.get(0);
return splittedRelatedVideoList;
}
//Merge videoIdList and relatedVideoIdList as mergedVideoList
public static ArrayList mergeLists(ArrayList videoList, ArrayList relatedVideoList) {
ArrayList mergedVideoList = new ArrayList(videoList);
mergedVideoList.addAll(relatedVideoList);
return mergedVideoList;
}
//Find Duplicate Videos
public static boolean findDuplicateVideos(List videoIdList) {
for (int i = 0; i < videoIdList.size(); i++) {
if (Collections.frequency(videoIdList, videoIdList.get(i)) > 1) {
System.out.println("This video id is duplicated: " + videoIdList.get(i));
return false;
}
}
return true;
}
}
Example1Test.java
&Example2Test.java
: Các class chứa các test case cụ thể, sử dụng các hàm assertion để kiểm tra kết quả trả về từ API.
package ApiTests;
import Utils.*;
import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) //For Ascending order test execution
public class Example1Test {
//First, I declared Response and JsonPath objects.
private Response res = null; //Response object
private JsonPath jp = null; //JsonPath object
/*
Second, we should do setup operations, get JSON response from the API and put it into JsonPath object
Then we will do query and manipulations with JsonPath class’s methods.
We can do all of the preparation operations after @Before Junit annotation.
*/
@Before
public void setup() {
//Test Setup
RestUtil.setBaseURI("http://api.5min.com"); //Setup Base URI
RestUtil.setBasePath("search"); //Setup Base Path
RestUtil.setContentType(ContentType.JSON); //Setup Content Type
RestUtil.createSearchQueryPath("barack obama", "videos.json", "num_of_videos", "4"); //Construct the path
res = RestUtil.getResponse(); //Get response
jp = RestUtil.getJsonPath(res); //Get JsonPath
}
@Test
public void T01_StatusCodeTest() {
//Verify the http response status returned. Check Status Code is 200?
HelperMethods.checkStatusIs200(res);
}
@Test
public void T02_SearchTermTest() {
//Verify the response contained the relevant search term (barack obama)
assertEquals("Title is wrong!", ("Search results for "barack obama""), jp.get("api-info.title"));
//assertThat(jp.get("api-info.title"), containsString("barrack obama"));
}
@Test
public void T03_verifyOnlyFourVideosReturned() {
//Verify that only 4 video entries were returned
assertEquals("Video Size is not equal to 4", 4, HelperMethods.getVideoIdList(jp).size());
}
@Test
public void T04_duplicateVideoVerification() {
//Verify that there is no duplicate video
assertTrue("Duplicate videos exist!", HelperMethods.findDuplicateVideos(HelperMethods.getVideoIdList(jp)));
}
@Test
public void T05_printAttributes() {
//Print video title, pubDate & duration
printTitlePubDateDuration(jp);
}
@After
public void afterTest() {
//Reset Values
RestUtil.resetBaseURI();
RestUtil.resetBasePath();
}
/***************************************************/
/**************Local Methods*************************/
/***************************************************/
//Prints Attributes
private void printTitlePubDateDuration(JsonPath jp) {
for (int i = 0; i < HelperMethods.getVideoIdList(jp).size(); i++) {
System.out.println("Title: " + jp.get("items.title[" + i + "]"));
System.out.println("pubDate: " + jp.get("items.pubDate[" + i + "]"));
System.out.println("duration: " + jp.get("items.duration[" + i + "]"));
System.out.println("n");
}
}
}
package ApiTests;
import Utils.*;
import io.restassured.http.ContentType;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@FixMethodOrder(MethodSorters.NAME_ASCENDING) //For Ascending order test execution
public class Example2Test {
private Response res = null; //Response
private JsonPath jp = null; //JsonPath
/*
We should do setup operations, get JSON response from the API and put it into JsonPath object
Then we will do query and manipulations with JsonPath class’s methods.
We can do all of the preparation operations after @Before Junit annotation.
*/
@Before
public void setup() {
//Test Setup
RestUtil.setBaseURI("http://api.5min.com"); //Setup Base URI
RestUtil.setBasePath("video"); //Setup Base Path
//In this example, I assigned full path manually in below code line.
RestUtil.path = "list/info.json?video_ids=519218045&num_related_return=4";
RestUtil.setContentType(ContentType.JSON); //Setup Content Type
res = RestUtil.getResponse(); //Get response
jp = RestUtil.getJsonPath(res); //Set JsonPath
}
@Test
public void T01_StatusCodeTest() {
//Verify the http response status returned. Check Status Code is 200?
HelperMethods.checkStatusIs200(res);
}
@Test
public void T02_SearchTermTest() {
//Verify the response contained the relevant search term (519218045)
assertEquals("Id does not match!", "519218045", HelperMethods.getVideoIdList(jp).get(0).toString());
}
@Test
public void T03_verifyExtraFourVideosReturned() {
//Verify that extra 4 video entries were returned as related videos
assertEquals("Related video Size is not equal to 4", 4, HelperMethods.getRelatedVideoIdList(jp).size());
}
@Test
public void T04_duplicateVideoVerification() {
//Check duplicate videos exist?
assertTrue("Duplicate videos exist!", HelperMethods.findDuplicateVideos(getMergedVideoLists()));
}
@Test
public void T05_printAttributes() {
//Print attributes
printAttributes(jp);
}
@After
public void afterTest() {
//Reset Values
RestUtil.resetBaseURI();
RestUtil.resetBasePath();
}
/***************************************************/
/**************Local Methods*************************/
/***************************************************/
//Returns Merged Video Lists (Video List + Related Video List)
private ArrayList getMergedVideoLists() {
return HelperMethods.mergeLists(HelperMethods.getVideoIdList(jp), HelperMethods.getRelatedVideoIdList(jp));
}
//Prints Attributes
private void printAttributes(JsonPath jp) {
for (int i = 0; i < HelperMethods.getVideoIdList(jp).size(); i++) {
System.out.println("Title: " + jp.get("items.title[" + i + "]"));
System.out.println("Description: " + jp.get("items.description[" + i + "]"));
System.out.println("n");
}
}
}
AllApiTest.java
: Class Test Suite để chạy tất cả các test case.
package TestSuite;
import ApiTests.Example1Test;
import ApiTests.Example2Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
Example1Test.class,
Example2Test.class,
})
public class AllApiTest {
}
4. Chạy Test Cases:
Có hai cách để chạy test cases:
-
Cách 1: Sử Dụng Command Line:
Trong file
pom.xml
, phần<plugin>
đã định nghĩaAllApiTest.class
là main test suite. Do đó, khi chạy bằng command line, Java sẽ chạy file này trước.- Mở CMD.
cd
vào thư mục gốc của source code.- Chạy command:
mvn test -PallApiTests
-
Cách 2: Chạy từ IntelliJ hoặc Eclipse:
Kết Luận
Bài viết này đã cung cấp một hướng dẫn chi tiết về cách sử dụng REST Assured để kiểm thử API. Hy vọng rằng, với những kiến thức này, bạn có thể áp dụng vào các dự án thực tế và xây dựng các framework kiểm thử API mạnh mẽ, hiệu quả.
Bạn có thể tham khảo source code đầy đủ tại [đây](địa chỉ source code tham khảo).
Tham khảo:
- REST Assured Documentation
- [Chọn web service tốt nhất: REST vs SOAP](liên kết đến bài viết so sánh REST và SOAP)