Nếu chúng ta thực hiện việc "ABC" một lần duy nhất thì dù có khó khăn, tốn kém lẫn vất vả cũng không bận tâm nhiều. Nhưng nếu việc đó lặp đi lặp lại nhiều lần thì ắt hẳn ai trong chúng ta cũng tìm cách tối ưu nó.
Trong tự động hóa kiểm thử ở Cybozu cũng vậy: trải nghiệm → tìm kiếm bất tiện → tối ưu hệ thống. Trong bài viết này chúng ta sẽ tìm hiểu test flow - là kỹ thuật được đúc kết của Cybozu và cũng là một trong loạt bài How to build an E2E testing project
Trong bài này sử dụng Address module | 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
Test flow là gì?
Là một kỹ thuật triển khai test code ở Cybozu mà trong đó test flow class
cung cấp test flow methods
cho test spec sử dụng nó mà không quan tâm test flow methods
được triển khai như thế nào. Kỹ thuật này giúp cho việc triển khai test spec
được nhẹ nhàng hiệu quả.
test flow methods
đảm nhiệm nhiệm vụ gọi actions method trên một hoặc nhiều trang
Ví dụ: test flow method:
loginGaroonSystem(){
// ...
}
gọi các "actions method" tác trên web element như: inputUsername(this.userName)
, inputPassword(this.password)
, click vào nút “Login” - clickLoginButton()
.
Test flow giải quyết vấn đề gì?
Để trả lời cho câu hỏi này chúng ta xem qua một số điểm chính bên dưới:
Giúp test spec
chỉ thể hiện tổng quát
Tại tầng test spec
, khi implement hay maintain chúng ta chỉ đối diện với những test flow methods
với tên đại diện phản ánh chi tiết kịch bản nó đảm nhiệm. Điều này giúp chúng ta nắm bắt nhanh và hiệu quả vì test code ít nhất; đồng thời dễ dàng nhận ra sự dư thừa hay thiếu xót của implement test case, ngoài ra khi cần chúng ta sẽ đi vào chi tiết test flow methods
. Ví von một tí giống như chúng ta tìm một chiếc điện thoại bỏ quên trong căn phòng ít đồ và một căn phòng nhiều đồ vậy ;)
Ví dụ bên dưới mô tả SỬ DỤNG test flow và KHÔNG sử dụng test flow.
import { LoginPage } from '#e2e-core/system/pages';
import { AddingAddressBookFlow } from '#e2e-core/address/test-flows';
describe('SharedAddress thêm mới', () => {
it('sẽ thêm mới một AddressBook thành công',() => {
// Đăng nhập vào hệ thống sử dụng trực tiếp Page Object
LoginPage
.openUrl('https://onlinedemo2.cybozu.info/scripts/garoon/grn.exe/index?')
.inputUsernameTextbox("user-1")
.inputPasswordTextbox("123456")
.clickLoginButton()
.getDisplayUserInfo()
.verifyDisplayUserInfo();
// Thêm mới một adddressBook vào module Address sử dụng kỹ thuật Test flow
new AddingAddressBookFlow(addressBookInfo) // Test flow class tên AddingAddressBookFlow
.addBook() // Test flow method tên addBook
.verifyBookDetails(); // Test flow method tên verifyBookDetails
});
});
Code snippet 1.0: test spec
dòng 7-13 dùng trực tiếp page object
; dòng 17-19 dùng kỹ thuật test flow
Nếu một test case phức tạp hơn tí, có nhiều bước hơn thì lúc này test spec
sẽ bùng nổ số dòng; hoặc khi làm việc test spec
ta cũng "không quan tâm lắm" việc đăng nhập gồm những bước gì. Như vậy test flow
giải quyết vấn đề ẩn đi sự chi tiết chỉ quan tâm tới tổng thể ở tầng test spec
.
Gia tăng hiệu suất làm việc thông qua tái sử dụng test flow
Việc lặp đi lặp lại các action methods
của page object
ở test spec
một cách nhỏ lẻ thật sự tốn cost và không giúp chúng ta làm việc hiệu quả trong automation test project.
Vì End-to-end testing đâu chỉ có một hoặc vài test case. Đôi khi lên đến hàng trăm thậm chí hàng ngàn. Mỗi test case trong test suite thường có chung một hoặc vài nghiệp vụ (ví dụ đăng nhập). Nên việc tái sử dụng test flow giúp chúng ta implement nhiều test case hơn trong cùng một khoản thời gian.
Ví dụ bên dưới ta sử dụng test flow methods new LoggingSystem(accountNormal).loggin();
cho đăng nhập thay vì dùng Page object
trực tiếp với nhiều action methods.
import { LogingSystemFlow } from '#e2e-core/system/test-flows';
import { AddingAddressFlow, OpeningAddressEntryFlow, DeletingAddressBookFlow } from '#e2e-core/address/test-flows';
import AddressBookPage from '#e2e-core/address/pages/AddressBook';
describe('SharedAddress thêm mới', () => {
it('sẽ thêm mới một AddressBook thành công', () => {
// Login hệ thống dùng test flow
new LogingSystemFlow(accountNormal).loggin();
// Test flow
new AddingAddressBookFlow(addressBookInfo)
.addBook()
.verifyBookDetails();
});
it('sẽ thêm mới một AddressBookProxy thành công', () => {
// Login hệ thống dùng test flow
new LogingSystemFlow(accountProxy).loggin();
// Thêm mới một address
new AddingAddressEntryFlow(addressBookProxyInfo)
.addAddressEntry()
.verifyAddressEntryDetails();
});
after('Xóa tất cả test data đã phát sinh ở test case', () => {
new LogingSystemFlow(accountProxy).loggin();
new OpeningAddressEntryFlow(bookName).openDetailAddressBook();
// Sử dụng trực tiếp action method của page object
AddressBookPage.waitForPageReady();
new DeletingAddressBook(bookName).deleteAddressBook();
});
});
Như vậy càng có nhiều test spec
triển khai thì test flow
càng phát huy tác dụng. Đến đây test flow giải quyết vấn đề thứ 2 là hiệu suất của developer thấp nếu gọi trực tiếp action methods từ page object.
Cải thiện test code thành test flow
như thế nào?
Ví dụ cải thiện test code cho "đăng nhập"
Chuyển các action methods
của LoginPage
object thành test flow
LoggingSystem class
và bao gồm login
test flow method cho test spec
sử dụng.
Test flow class
Test flow
class có constructor
để nhận dữ liệu và gán vào property this._account
cho tất cả test flow
method sử dụng nếu có nhu cầu.
Như code snippet bên dưới chúng ta có this._account
được khởi tạo ở constructor
;
import LoginPage from './login.js';
import IndexPage from './index.js';
export default class LogingSystem {
constructor(account) {
this._account = account;
}
// ...
}
Implement test flow methods
Tương tác trên một hoặc nhiều trang để triển khai nghiệp vụ mà test flow đảm nhiệm. Test flow methods phần lớn return this
; tương tự như action methods
của Page Object. Ví dụ login methods return this
dòng số 11
bên dưới
/**
*
* @returns {LoggingSystem}
*/
login() {
LoginPage.openPage()
.inputUsername(this._account.userName)
.inputPassword(this._account.userName)
.clickLoginButton();
return this;
}
/**
*
* @returns {LoggingSystem}
*/
verifyLoggedInSuccessful() {
const actualLoginName =
IndexPage.waitForPageReady().getDisplayUserInfo();
assert.equal(this._account.userName, actualLoginName, `Mong đợi ${this._account.userName} không thể là ${actualLoginName}`);
return this;
}
Kết quả test flow cuối cùng
Bên dưới là kết quả cuối cùng gồm constructor, flow methods.
import LoginPage from './login.js';
import IndexPage from './index.js';
export default class LogingSystem {
constructor(account) {
this._account = account;
}
/**
*
* @returns {LoggingSystem}
*/
login() {
LoginPage
.openPage()
.inputUsername(this._account.userName)
.inputPassword(this._account.userName)
.clickLoginButton();
return this;
}
/**
*
* @returns {LoggingSystem}
*/
verifyLoggedInSuccessful() {
const actualLoginName =
IndexPage
.waitForPageReady()
.getDisplayUserInfo();
assert.equal(this._account.userName, actualLoginName, `Mong đợi ${this._account.userName} không thể là ${actualLoginName}`);
return this;
}
}
Một số điểm lưu ý
-
Trong test flow class chỉ implement những
test flow method
liên quan "mật thiết" với test flow class.- Ví dụ
adding-appointment.js
file cóAddingAppointment class
bao gồmaddRegularAppointment
methods;addRepeatingAppointment
method;addAllDayAppointment
method; NHƯNG không implementdeleteAppointment
method trongAddingAppointment class
; - Thiết kế
test flow
nên áp dụng nguyên lý “tính đơn nhiệm”. Không nên “kiêm nhiệm” trong một test flow ví dụ nhưloginAndAddUser()
,addAndDeleteUser()
. Điều này sẽ giúp ích cho việc tái sử dụng một cách linh động, dễ bảo trì và dễ cải tiến source code.
Chúng ta có thể hiểu như: chỉ có một lý do để test flow tồn tại và cũng chỉ có một lý do để test flow mất đi
- Ví dụ
-
Tên file và tên class bắt đầu là một "danh động từ" (trong tiếng Anh là v+ing). Vì chúng ta đang đối tượng hóa hành động trong kiểm thử; ví dụ
logging.js
cóLogging class
; -
Có thể sử dụng action methods ở
test spec
Test flows
methods không thể phủ toàn bộ được tất cả test case. Do đó chúng ta có thể triệu gọiaction methods
của page object ởtest spec
(nhưng hạn chế nhất có thể) -
Mỗi
test flow
nên hoàn chỉnh về nghiệp vụ
Mộttest flow
nên được thiết kế để thực hiện một tính năng hoặc nghiệp vụ hoàn chỉnh và độc lập. Ví dụ test flowlogin()
gồm hai phương thức:inputUsername()
,inputPassword()
. Test flow này vẫn chưa hoàn thành việc login (nên có thêmclickLoginButton()
nên khi dùng developer phải viết thêm các phương thức còn lại dẫn đến không phát huy được lợi ích của test flow. -
Chỉ nên tạo mới một test flow khi nó có khả năng tái sử dụng.
Như đã giới thiệu ở trên, lợi ích chính của việc phát sinh test flow là tái sử dụng, nên những test flow được dùng ít (một hoặc hai lần) thì không nhất thiết được tạo ra.
Phân bổ test flow
trong cấu trúc dự án
Trong dự án test automation; test flow được phân bổ dưới test-flows folder. Xem thêm ví dụ bên dưới cho hai test flow adding-address-entry.js
và opening-address-entry.js
acceptance
|---address
| |---test-flows
| | |---adding-address-entry.js
| | |---opening-address-entry.js
| | |---// ...
| | |---index.js
| |---test-specs
| |---page-objects
|---schedule
| |---test-flows
| | |---adding-appointment.js // test flow file
| | |---// ...
| | |---index.js
| |---test-specs
| | |---added-new-appoiment
| | | |---added-new-appointment.spec.js|.data.js
|---system
| |---test-flows
| | | |---logging.js // test flow file
// ...
Những nhược điểm khi sử dụng test flows
Xét về ưu điểm khi sử dụng test flow, lợi ích đã liệt kê trên. Ngoài những ưu điểm của test flow thì nó cũng có những khuyết điểm:
-
Cấu trúc chương trình sẽ trở nên phức tạp hơn. Xét về quy trình hoạt động từ lúc khởi tạo để thực thi
test spec
đến lúc chương trình tương tác trên browser sẽ thông qua 4 tầng thay vì 3 tầng nếu không sử dụng test flow (xem Hình 01)Hình 01: Mô tả chương trình thực thi khi có thêm test flow
-
Tăng gánh nặng cho developer vì phải nhớ và hiểu kỹ lưỡng từ nguyên lý đến định nghĩa. Nếu tuân thủ nửa vời sẽ dẫn đến nhiều biến thể trong chương trình.