Khi triển khai tự động hóa kiểm thử chẳng hạn Acceptance testing, chỉ với vài chục test case thì chúng ta dùng JSON object, ví dụ: const appointmentInfo = {"title": "Octans daily meeting", "facility": "Room 201"}
là giải pháp phù hợp nhất vì triển khai nhanh, đơn giản và rõ ràng
Còn nếu automated E2E testing project với vài nghìn test case, nó được liên tục mở rộng theo thời gian thì quả thật JSON không còn phù hợp, vì tốn kém thời gian không thật sự cần thiết, rủi ro và khó maintain test code.
Cybozu VN khi triển khai test data cho e2e project bằng JSON cũng gặp khá nhiều vấn đề và đã khắc phục nó. Do đó, trong bài viết này xin chia sẻ kinh nghiệm thực tế thông qua topic “Data Generator - Giải pháp tối ưu cho test data”.
Bài viết này thuộc chuỗi bài How to build an E2E testing project. Để đảm bảo giới hạn độ dài thời gian tôi chỉ đề cập tới những thông tin cốt lõi của Data Generator.
Trong bài này sử dụng Cybozu Garoon (
username: brown
password: brown
) để làm minh họa, ví dụCybozu Garoon là một sản phẩm làm việc cộng tác. Nó là một trong các sản phẩm chủ lực của tập đoàn Cybozu. Bạn có thể thử trải nghiệm online để biết thêm về sản phẩm
Data generator | Cybozu là gì?
Data generator là một tool có nhiệm vụ tạo ra dữ liệu tĩnh cho test case theo một quy định cụ thể.
Ví dụ: chúng ta dùng SchedulerDataGenerator
để khởi tạo dữ liệu tĩnh và gán vào biến số tên regularAppointment
Hình 1.0: Schedule application triển khai theo Data generator
Cách triển khai data generator
Mỗi project khác nhau, mỗi công ty có cách triển khai test data generator khác nhau do đó không tồn tại một chuẩn chung hay một chuẩn mực để đánh giá chi tiết. Bên dưới là cách triển khai của Cybozu VN trên Garoon’s Scheduler application.
Bối cảnh: Garoon’s Scheduler application có nhiều loại Appointment khác nhau như: Regular appointment
, All day appointment
và Repeating appointment
. Chúng ta sẽ triển khai data generator cho Regular appointment, các loại khác cũng tương tự. Code snippet bên dưới sử dụng JS phiên bản ES6
Bước 1: Triển khai Base class của Data generator (lớp cơ sở)
File name: e2e-core/shared/provider/DataGeneratorBase.js
File code: Trong class này, chúng ta chỉ implement những logic tổng thể và không nhằm mục đích phục vụ cho một module hay test suite nào cả. Và cũng như trong lập trình phần mềm, chúng ta phải thật sự cân nhắc ưu nhược trước khi quyết định kế thừa class base này.
export default class DataGeneratorBase {
/**
*
* @returns {string}
*/
static _createPart4Chars() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
/**
*
* @param {int=} length maximum values of length is 9
* @returns {string}
*/
static randomStr(length = 9) {
const lengthFiltered = length > 9 || length < 1 ? 9 : length;
return `${this._createPart4Chars()}-${this._createPart4Chars()}`.slice(-lengthFiltered
);
}
/**
*
* @param {int} min
* @param {int} max
* @returns {int}
*/
static randomNumWithinRange(min, max) {
return Math.floor(Math.random() * max + min);
}
}
Bước 2: Triển khai business data generator (lớp dẫn xuất)
Filename: acceptance/shared/regular-appointment-generator/index.js
File code: Trong class này chúng ta sẽ implement test data generator cho từng module, từng test suite với những value cho từng thuộc tính. Triển khai class này tới mức, khi test data được generated là có thể dùng được ngay mà không cần phải làm gì thêm.
import { RegularAppointment } from '#e2e-core/scheduler/entity';
import DataGeneratorBase from '#e2e-core/shared/generator/DataGeneratorBase';
import { DateTimeGenerator } from '#e2e-core/shared/utils';
const currentDate = new DateTimeGenerator().adjustDateTime({ day: 0 });
class RegularDataGenerator extends DataGeneratorBase {
constructor() {
super();
}
/**
* @returns {RegularAppointment}
*/
generateRegularAppointment() {
let currently = new Date().getHours();
const appointment = new RegularAppointment();
appointment.appointmentType = APPOINTMENT_TYPE.regular;
appointment.eventMenu = '打合';
appointment.subject =
`${RandomUtils.randomizeString()} regular appointment title`;
appointment.startMonth = currentDate.getMonth();
appointment.startDay = currentDate.getDay();
appointment.startYear = currentDate.getYear();
appointment.startHour = (currently + 1).toString();
appointment.startMinute = '00';
appointment.endMonth = currentDate.getMonth();
appointment.endDay = currentDate.getDay();
appointment.endYear = currentDate.getYear();
appointment.endHour = (currently + 2).toString();
appointment.endMinute = '00';
appointment.attendees = attendees;
appointment.facilities = [];
appointment.attachments = [];
appointment.visibility = 'Public';
appointment.notes = `${RandomUtils.randomizeString()} note`;
return appointment;
}
}
module.exports = new SchedulerDataGenerator();
Bước 3: Sử dụng và "ghi đè" giá trị nếu cần tại nơi sử dụng Data generator này
File name: added-new-appointment.data.js
File code: Ở ví dụ bên dưới chúng ta ghi đè giá trị của thuộc tính attendees
và facilities
import RegularDataGenerator from '#acceptance/scheduler/shared/regular-appointment-generator';
// ...
const appointment = RegularDataGenerator.generateRegularAppointment();
appointment.attendees = [{username: 'end-user1'}, {username: 'end-user2'}];
appointment.facilities = ["Room 201"]; // overriding attribute value
export { appointment as default };
// ...
Kết quả tổ chức source code Data generator
acceptance
| |---scheduler
| | |---pages
| | | |---add.po.js
| | | |---index.js
| | |---test-flows
| | | |---adding-appointment.flow.js // test flow file
| | | |---index.js
| | |---test-specs
| | | |---added-new-regular-appoiment
| | | | |---added-new-appointment.spec.js|.data.js
| | |---shared
| | | |---regular-appointment-generator // data generator
| | | | |---index.js
| |---system
| | |---test-flows
| | | |---logging.flow.js
| | | |---index.js
|---e2e-core
| |---shared
| | |---generator // Base class data generator
| | | |---DataGeneratorBase.js
Các điểm để triển khai test data generator
Từng đề cập phía trên của bài viết và một lần nữa chúng ta thừa nhận rằng
…mỗi công ty có cách triển khai data generator khác nhau do đó không tồn tại một chuẩn chung hay một chuẩn mực để đánh giá chi tiết.
Tuy nhiên, các bạn có thể tham khảo một số tiêu chí như bên dưới để tạo ra data generator đáp ứng tốt việc kiểm thử phần mềm.
-
Có thể mở rộng
Khi triển khai test case, từ các phương thức tạo test data được xây dựng ban đầu, developer có thể mở rộng các phương thức này để có thể tạo ra test data mới. Những phương thức mới này vẫn đáp ứng được nhu cầu test mà không phải xây dựng lại từ đầu.
Cụ thể: từ DataGenerator basic, developer có thể mở rộng cho từng mô-đun khác nhau mà không gặp bất kỳ trở ngại nào.
Cũng lưu ý là khi mở rộng chúng ta nên trả lời các câu hỏi liên quan tới OOP như: vì sao phải kế thừa? Kế thừa có hạn chế? và còn cách nào khác đạt được mục đích mà không cần kế thừa? Điều này giúp chúng ta thiết kế chương trình đảm bảo nghiệp vụ và đảm bảo nguyên lý phần mềm. -
Có tính nhất quán
Đúng hoặc sai là kết quả của test data được tạo ra từ data generator. Tất cả các test data tạo ra cho test case dù là hiện tại hay tương lai đều có chung một cấu trúc lưu trữ dữ liệu JSON, Object literal hay là Entity. -
Có tính trừu tượng
Dù cố gắng tạo ra một công cụ data generator cực kỳ linh hoạt và tinh tế đi nữa thì chúng không thể đáp ứng hết cho các test case e2e phức tạp. Nên chúng ta lưu ý chỉ tập trung nỗ lực để tạo ra những hàm/phương thức mang tính tổng thể hoặc đáp ứng đại đa số test data mà mô-đun đó cần.Trường hợp những test case có nhu cầu test data phức tạp sẽ tự chuẩn bị vì đây là thiểu số, nếu chúng ta cố gắng triển khai data generator để đáp ứng cho thiểu số sẽ dẫn đến rất nhiều hệ lụy về sau.
-
Và cuối cùng là tính đơn nhiệm
Data Generator chỉ nên làm 1 nhiệm vụ duy nhất, đó chính là tạo ra test data, phục vụ cho test case.
Và mỗi test data được sinh ra từ Data Generator thì chỉ nên được sử dụng bởi chính những test case liên quan. Ví dụ: phương thứcgenerateRegularAppointment
chỉ được tạo ra test data phục vụ cho các test case liên quan đến regular appointment.
Sử dụng data generator cho thêm mới appointment
Yêu cầu
Viết test case “thêm mới regular appointment" cho ứng dụng Scheduler | Cybozu Garoon, với các mong đợi như sau
Test data KHÔNG sử dụng test data generator
Test data file: adding-new-regular-appointment.data.js
const userCredentials = {username: 'cybozu', password: 'cybozu'}
const currentDate = new Date();
const regularAppointment = {
subject: 'Event 01',
startMonth: currentDate.getMonth(),
startDay: currentDate.getDate(),
startYear: currentDate.getFullYear(),
startHour: '09',
startMinute: '00',
endMonth: currentDate.getMonth(),
endDay: currentDate.getDate(),
endYear: currentDate.getFullYear(),
endHour: '10',
endMinute: '00',
attendees: [userCredentials.username],
facilities: [],
attachments: [],
visibility: 'Public',
notes: 'Note',
};
const testData = {
userCredentials: userCredentials,
appointmentInfo: regularAppointment
}
export { testData as default };
Test data sử dụng data generator
File name: added-new-appointment.data.js
import SchedulerDataGenerator from '#acceptance/scheduler/shared/regular-appointment-generator';
const userCredentials = {username: 'cybozu', password: 'cybozu'};
const regularAppointment = SchedulerDataGenerator.generateRegularAppointment();
regularAppointment.attendees = [userCredentials.username];
const testData = {
userCredentials: userCredentials,
appointmentInfo: regularAppointment
}
export { testData as default };
Test spec
File name: added-new-appointment.spec.js
import { AuthenticatingGaroonFlow } from '#e2e-core/system/test-flows';
import { AddingAppointmentFlow } from '#e2e-core/scheduler/test-flows';
import testData from './adding-new-regular-appointment.data.js';
describe('Adding New Regular Appointment', () => {
before('Authenticate the Garoon system', () => {
new AuthenticatingGaroonFlow(testData.userCredentials)
.login()
.verifyLoggedInSuccessfully();
});
it('should add a new regular appointment successfully', () => {
new AddingAppointmentFlow(testData.appointmentInfo)
.addRegularAppointment()
.verifyAppointmentDetails();
});
});
Như các bạn có thể thấy, việc triển khai test case mới khi sử dụng data generator sẽ đơn giản hơn nhiều, chúng ta sẽ không phải đi khai báo lại toàn bộ thuộc tính của test data. Mà chúng ta chỉ cần cập nhật một số thuộc tính mình mong muốn.
Câu trả lời dành cho bạn
Bạn có muốn dùng data generator khi triển khai tự động hóa test case?
Ưu điểm và nhược điểm của test data generator
Ưu điểm
- Khởi tạo test data nhanh giúp rút ngắn thời gian tổng thể viết source code cho test case.
- Cấu trúc test data nhất quán (tức là một mô hình lưu trữ dữ liệu: Object literal hay Entity, v.v... được chọn ngay từ đầu) giúp source code hạn chế lỗi tiềm ẩn (chỉ phát sinh lỗi ở những ngữ cảnh nhất định trong lúc thực thi chương trình, mà compiler không thể phát hiện)
- Maintain test data dễ dàng vì chỉ có một nơi định nghĩa và duy nhất một “cỗ máy” sinh ra test data
Nhược điểm
- Tốn thời gian xây dựng và maintain data generator source code
- Chỉ phù hợp với những dự án tự động hóa lớn và thường xuyên mở rộng, bổ sung test case và loại test
- Đòi hỏi có kiến thức software principle nhất định
- Data generator chỉ áp dụng cho bộ test case lớn và “có tính tái sử dụng cao”
Lời kết
Test data generator có rất nhiều ưu điểm nhưng bản thân nó cũng tồn tại nhược điểm như phân tích ở trên. Việc áp dụng data generator chỉ phù hợp cho dự án có quy mô(sản phẩm vài nghìn tính năng cần tự động hóa kiểm thử), và thường xuyên mở rộng.
Data generator là một giải pháp không phải là một kỹ thuật do đó chúng ta triển khai trên bất kỳ ngôn ngữ nào từ JS đến Python điều được.
Thuật ngữ trong bài viết
Lớp cơ sở (lớp cha): Là một class trong OOP định nghĩa những thuộc tính, phương thức chung, tổng thể để các lớp kế thừa nó thừa hưởng những phương thức, thuộc tính này.
Thuật ngữ này thường được sử dụng để mô tả hình thức kế thừa trong lập trình hướng đối tượng. Ví dụ:
class DataGeneratorBase {
// ...
}
Lớp dẫn xuất (lớp con/lớp phái sinh): Là một class trong OOP kế thừa lớp cơ sở để thừa hưởng những thuộc tính và phương thức. Ví dụ:
class SchedulerDataGenerator extends DataGeneratorBase {
// ...
}