Bài viết được sự cho phép của tác giả Trần Văn Dem
1. Giới thiệu
Trong quá trình phát triển các sản phẩm phần mềm, việc kiểm thử là bước vô cùng quan trọng. Không có kiểm thử thì phần mềm phát triển không đủ sự tin tưởng.
Hiện nay với sự phức tạp của các nghiệp vụ do đó các Entity trong các sản phẩm phần mềm ngày càng trở lên phức tạp :
- Có rất nhiều trường (field) > 20 fields
- Lưu trữ vật lý tại nhiều bảng khác nhau hoặc nhiều loại DB khác nhau
- Cấu trúc của một Entity thường sẽ phức tạp: chứa nhiều object khác, array object,…
Khi viết iteration test thông thường chúng ta sẽ chỉ thực hiện assert cho một vài trường nghĩ là sẽ thay đổi sau khi gọi qua API điều này là đúng nhưng chưa đủ.
Ví dụ khi ta lập trình gọi API cho phép thay đổi giá của sản phẩm nhưng không may trong quá trình đó lại thực hiện cập nhật nhầm một thuộc tính nào đó như số lượng tồn kho. Việc này là rất nguy hiểm thường sảy ra khi code của project đã quá lớn và là người mới join vào quá trình phát triển chưa nắm rõ. Khi thực hiện testing nếu chỉ thực hiện việc assert cho trường giá thì vẫn chưa đủ.
Một cách dễ dàng có thể khắc phục là thực hiện compare toàn bộ 2 entity dưới dạng json. Expected sẽ được query từ DB, actual sẽ được lưu trong 1 file text. Nếu 2 object này giống nhau hoàn toàn thì có thể chắc chắn được API chúng ta đang hoạt động đúng.
Thư viện sử dụng có thể là:
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.0</version>
</dependency>
Hoặc dùng chính project để thực hiện compare này.
Cách này khá dễ thực hiện nhưng sẽ có một số nhược điểm :
- Khi ta thêm, xóa trường trong Entity thì tất cả các file json sẽ bị ảnh hưởng và bắt buộc phải thay đổi theo.
- Khi đọc test thì người đọc sẽ rất khó biết được API thực sự thay đổi điều gì
2. Cách giải quyết
Project này đề xuất phương pháp compare 2 json object, trích xuất sự khác biệt và lưu nó dưới dạng một cấu trúc json để dễ đọc hiểu.
Cấu trúc json được đề xuất như sau:
{
"updated": {
},
"inserted" : {
},
"deleted": {
}
}
Project sẽ sử dụng thuật toán DFS để duyệt qua và thực hiện compare 2 Json. Project sử dụng Json thay vì reflection vì Json sẽ dễ đọc hơn khi thực hiện lưu kết quả của compare.
2.1. Giải thích cấu trúc output
updated
Chứa các field mang giá tri nguyên thủy, có thay đổi giá trị trước và sau updated.
inserted
Chứa các field mang giá trị là object hoặc array được thêm mới vào object
Các field mới xuất hiện tại object sau khi thực hiện action, trước đó object không tồn tại field này.
deleted
Chứa các field mang giá trị là object hoặc array được xóa đi trong object sau khi update
2.2. Cách sử dụng
Chi tiết về cách sử dụng tham khảo các test được viết sẵn :
Ví dụ ta có object sau :
Entity trước khi thực hiện action.
{
"employee":
{
"id": "1212",
"fullName":"John Miles",
"age": 35
},
"dem" : 16
}
Entity sau khi thực hiện action
{
"employee": {
"employId": "12122",
"fullName": "John 1",
"age": 35,
"contact": {
"email": "john@xyz.com",
"phone": "9999999"
}
}
}
Sau khi thực hiện compare sẽ được kết quả như sau :
var diffJson = new DDiffJsonBuilder()
.insertBuilder( new InsertObjectBuilder())
.updateBuilder(new UpdateObjectBuilder())
.deleteBuilder(new DeleteFlattenKeyBuilder())
.build();
diffJson.diffScan(beforeObject, afterObject);
var output = diffJson.toJsonFormatString();
{
"updated": {
"employee": {
"fullName": "John 1"
}
},
"inserted": {
"employee": {
"employId": "12122",
"contact": {
"email": "john@xyz.com",
"phone": "9999999"
}
}
},
"deleted": {
"employee.id": 1,
"dem": 1
}
}
Với Object nhỏ thì việc thực hiện chạy qua DiffJson thì không nhận thấy được ưu điểm nhưng nếu là một Object lớn và phức tạp, khi thực hiện API chỉ thay đổi vài trường thì sẽ thấy được lợi ích.
Tìm ngay việc làm Tester Hồ Chí Minh HOT tại TopDev!
2.3. Exclude fields
Khi thực hiện API có một số trường chúng ta không muốn compare như updated timestamp, trường timestamp này có thể có ở tất cả object nên việc loại trừ được các trường này cũng rất cần thiết.
Before Object :
{
"employee":
{
"id": "1212",
"updateTime" : 132245124312,
"fullName":"John Miles",
"age": 35
},
"dem" : 16,
"createTime" : 12412412412
}
After Object :
{
"employee": {
"employId": "12122",
"fullName": "John 1",
"updateTime" : 13225124312,
"age": 35,
"contact": {
"email": "john@xyz.com",
"phone": "9999999"
}
},
"createTime" : 1241312412
}
Code excludes timestamp
JsonNode beforeObject = DJacksonCommon.loadJsonFromFile("before_timestamp.json");
JsonNode afterObject = DJacksonCommon.loadJsonFromFile("after_timestamp.json");
var diffCompare = new DDiffJsonBuilder()
.insertBuilder( new InsertObjectBuilder())
.updateBuilder(new UpdateObjectBuilder())
.excludeCompareFieldPath("createTime")
.excludeCompareFieldPath("employee.updateTime")
.deleteBuilder(new DeleteFlattenKeyBuilder())
.build();
diffCompare.diffScan(beforeObject, afterObject);
String output = diffCompare.toJsonFormatString();
System.out.println(output);
Output Object :
{
"updated": {
"employee": {
"fullName": "John 1"
}
},
"deleted": {
"employee.id": 1,
"dem": 1
},
"inserted": {
"employee": {
"employId": "12122",
"contact": {
"email": "john@xyz.com",
"phone": "9999999"
}
}
}
}
2.4. Insert/Delete Object In Array
Một tính năng nữa của DDiff là có thể xác định được object nào đã bị xóa, thêm vào, update trong json array
Before Object :
{
"plants": [
{
"plantId": "1",
"name": "plant1"
},
{
"plantId": "2",
"name": "plant2"
},
{
"plantId": "3",
"name": "plant3"
}
],
"demtv": 11
}
After Object :
{
"plants": [
{
"plantId": "1",
"name": "plant11"
},
{
"plantId": "3",
"name": "plant3"
},
{
"plantId": "4",
"name": "plant4"
}
],
"demtv": 11
}
Code for object have array:
JsonNode beforeObject = DJacksonCommon.loadJsonFromFile("array_json_sample/before_have_array_plant.json");
JsonNode afterObject = DJacksonCommon.loadJsonFromFile("array_json_sample/after_have_array_plant.json");
diffJson.registerObjectKeyInArrayByPath("plants","plantId");
diffJson.diffScan(beforeObject, afterObject);
String output = diffJson.toJsonFormatString();
Output Object :
{
"updated": {
"plants": [
{
"plantId": "1",
"name": "plant11"
}
]
},
"inserted": {
"plants": [
{
"plantId": "4",
"name": "plant4"
}
]
},
"deleted": {
"plants.plantId.2": 1
}
}
3. Tổng kết
Các cách dùng kỹ hơn về thư viện này vui lòng tham khảo tại thư mục test. Test của Project đạt đến 83% line vậy nên có thể tin tưởng để sử dụng.
Bài viết gốc được đăng tải tại demtv.hashnode.dev
Xem thêm:
- Sử dụng Hibernate Tools tạo các Entity và DAO class
- Todo App ASP.NET MVC x Entity Framework
- .NET core vs ASP.NET core: Phân biệt .NET Framework, .NET Core và Mono
Hàng loạt việc làm IT hấp dẫn trên TopDev đang chờ bạn ứng tuyển.