NỘI DUNG BÀI HỌC
✅ Hướng phát triển BDD trong Automation Test
✅ Tại sao lại dùng khung BDD?
Phương thức phát triển phần mềm Agile là một tập hợp các phương thức phát triển lặp và tăng dần trong đó các yêu cầu và giải pháp được phát triển thông qua sự liên kết cộng tác giữa các nhóm tự quản và liên chức năng. Agile là cách thức làm phần mềm linh hoạt để làm sao đưa sản phẩm đến tay người dùng càng nhanh càng tốt càng sớm càng tốt và được xem như là sự cải tiến so với những mô hình cũ như mô hình “Thác nước (waterfall)” hay “CMMI”.
Aglie được coi là vòng xoắn ốc của Mác: sự vật hiện tượng phát triển theo hình xoắn ốc, đến 1 lúc nào đó sẽ quay lại hình thức cũ nhưng ở cấp độ cao hơn.
Test tự động là yếu tố sống còn của Agile, trong vòng xoắn ốc luôn phải có test. Vì sao ư! Vì để có thể tự tin sửa mã chương trình, đảm bảo sau khi thay đổi nếu phát sinh bug thì phát hiện và sửa được ngay, đó là lý do phải có test tự động.
1. Hướng phát triển TDD trong Automation Test
TDD (Test-Driven Development) là mô hình phát triển với trọng tâm hướng về việc kiểm thử. TDD được xây dựng theo hai tiêu chí: Test-First (Kiểm thử trước) và Refactoring (Điều chỉnh mã nguồn)
TDD là một quá trình phát triển lặp đi lặp lại. Mỗi lần lặp lại bắt đầu với một tập hợp các bài kiểm tra được viết cho một phần chức năng mới. Các thử nghiệm này được cho là không thành công trong khi bắt đầu lặp lại vì sẽ không có mã ứng dụng tương ứng với các thử nghiệm.
Vì trong giai đoạn tiếp theo của quá trình lặp lại, Mã ứng dụng được viết với mục đích vượt qua tất cả các bài kiểm tra được viết trước đó trong quá trình lặp lại. Khi mã ứng dụng đã sẵn sàng, các bài kiểm tra sẽ được chạy.
TDD đáp ứng “Tuyên ngôn về Agile” khi bản thân quy trình TDD thúc đẩy tính thực tiễn của sản phẩm, tương tác với người dùng. Để phát huy tối đa những lợi ích mà TDD mang lại, độ lớn của 1 đơn vị tính năng phần mềm (unit of function) cần đủ nhỏ để kịch bản kiểm thử dễ dàng được xây dựng và đọc hiểu, công sức debug kịch bản kiểm thử khi chạy thất bại cũng giảm thiểu hơn.
Theo TDD thì đa phần các test do Developer viết. Tester cũng có khi viết nhưng ít hơn.
✳️ Lợi ích của TDD:
- Bài kiểm tra đơn vị chứng minh rằng mã code của ứng dụng thực sự hoạt động
- Có thể điều khiển thiết kế của chương trình
- Tái cấu trúc cho phép cải thiện thiết kế của mã
- Bộ kiểm tra hồi quy cấp thấp
- Kiểm tra đầu tiên giảm chi phí của các lỗi
✳️ Hạn chế của TDD:
- Nhà phát triển có thể coi đó là một sự lãng phí thời gian
- Bài kiểm tra có thể được nhắm mục tiêu vào việc xác minh các lớp và phương thức chứ không phải những gì mã thực sự nên làm
- Kiểm tra trở thành một phần của chi phí bảo trì của một dự án
- Viết lại bài kiểm tra khi yêu cầu thay đổi
Nếu chúng ta tóm tắt điều này dưới dạng các giai đoạn trong quá trình phát triển thì gồm các giai đoạn sau: Xác định yêu cầu, Thực hiện các bài kiểm tra, Sửa/Thêm/Tái cấu trúc mã.
An sẽ mô phỏng các giai đoạn thông qua Code nhé.
🔆 Giai đoạn 1: Xác định yêu cầu
Chúng ta sẽ lấy một ví dụ đơn giản về ứng dụng máy tính và chúng ta sẽ xác định các yêu cầu dựa trên các tính năng cơ bản của máy tính. Để đơn giản hơn, chúng ta sẽ cô đọng ứng dụng máy tính thành một lớp java đơn giản:
package com.anhtester;
public class Calculator {
public int add(int number1, int number2) {
return 0;
}
}
Trong giai đoạn 1, các yêu cầu ứng dụng được thu thập và xác định. Lấy ví dụ về một máy tính đơn giản, chúng ta có thể nói rằng trong lần lặp 1 chúng ta muốn thực hiện 3 phép tính là:
- Phép cộng hai số
- Phép trừ hai số
- Phép nhân hai số
Vì vậy, như đã nói ở trên, TDD bắt đầu bằng việc xác định các yêu cầu dưới dạng các bài kiểm tra. Ví dụ yêu cầu đầu tiên của chúng ta về các bài kiểm tra:
Yêu cầu: Máy tính phải có khả năng cộng hai số.
Test 1: Cho hai số dương (10 và 20) máy tính có thể cộng hai số đó lại và cho ta kết quả đúng (30)
Test 2: Cho hai số âm (-10 và -20) máy tính cộng hai số đó lại và cho ta kết quả đúng (-30)
Trong giai đoạn 1, tất cả những gì chúng ta phải làm là viết các bài kiểm tra cho tất cả các yêu cầu tại thời điểm đó.
Yeah như vậy Developer sẽ viết các bài kiểm thử cho chức năng của Calculator theo yêu cầu cộng hai số gồm 2 test cases.
🔆 Giai đoạn 2: Thực hiện các bài kiểm tra
Bây giờ An cũng dùng Java để viết test cases nhé. Sử dụng TestNG Framework để thực hiện Unit Test cho 2 test cases trên.
package com.anhtester;
import org.testng.Assert;
import org.testng.annotations.Test;
public class TestCalculator {
private Calculator myCalculator = new Calculator();
@Test
public void testAddTwoPositiveNumbers()
{
int expectedResult = 30;
int actualResult = myCalculator.add(10, 20);
Assert.assertEquals(actualResult, expectedResult, "Sai. Kết quả không chính xác.");
}
@Test
public void testAddTwoNegativeNumbers()
{
int expectedResult = -30;
int actualResult = myCalculator.add(-10, -20);
Assert.assertEquals(actualResult, expectedResult, "Sai. Kết quả không chính xác.");
}
}
Kết quả khi chạy testAddTwoPositiveNumbers
:
java.lang.AssertionError: Sai. Kết quả không chính xác.
Expected :30
Actual :0
Như vậy sau khi chạy test case trên cần xem lại phương thức "add
" của chương trình máy tính Calculator.
..... một tiếng sau....
Và sau khi Dev xem lại thì phát hiện là nó return 0 nghĩa là nó luôn luôn bằng 0. Sai phải rồi 😄
(lúc đó chắc đang hẹn ghệ đi ăn hơi vội xíu @@)
Tương ứng với sai đó thì Dev cần sửa lại cho đúng với thuần phong mỹ tục 😝
🔆 Giai đoạn 3: Sửa/Thêm/Tái cấu trúc mã
Mã ở đây là cái mã code của ứng dụng phần mềm á nhen. Không phải cái mã code auto test đâu đà @@
Sau khi kiểm tra thất bại ở bước trước, chúng ta vào ứng dụng chỉ cần chỉnh lại phần return kết quả của phương thức "add". Bây giờ lớp Calculator của chúng ta sẽ thế này:
package com.anhtester;
public class Calculator {
public int add(int number1, int number2) {
return (number1 + number2);
}
}
Với thay đổi này, chúng ta sẽ chạy lại Giai đoạn 2 đã đề cập trước đó nghĩa là chạy lại các test cases cho yêu cầu đó sau khi chỉnh sửa lại.
Kết quả của hai bài kiểm tra là:
===============================================
Default Suite
Total tests run: 2, Passes: 2, Failures: 0, Skips: 0
===============================================
À pass cả 2 test cases. Yeah như vậy là xong phần Unit Test rồi.
Tương tự viết thêm các test cho các yêu cầu còn lại như ví dụ bên trên.
Khi tất cả các bài kiểm tra vượt qua, nó báo hiệu sự kết thúc của quá trình lặp lại. Nếu có nhiều tính năng hơn cần được triển khai trong sản phẩm của bạn, thì sản phẩm sẽ lại trải qua các giai đoạn tương tự nhưng lần này với bộ tính năng mới và có nhiều thử nghiệm hơn.
Tóm tắt hướng TDD nó thế này:
Tiếp theo, chúng ta sẽ chuyển sang BDD. Phần này sẽ tạo cơ sở để hiểu Gherkin và cuối cùng là Cucumber.
2. Hướng phát triển BDD trong Automation Test
Chúng ta đã thảo luận về cách TDD là một quy trình phát triển tập trung vào thử nghiệm, trong đó chúng ta bắt đầu viết thử nghiệm trước. Ban đầu, các thử nghiệm này không thành công nhưng khi chúng ta thêm nhiều mã ứng dụng hơn, các thử nghiệm này sẽ vượt qua. Điều này giúp chúng ta theo nhiều cách:
- Chúng ta viết mã ứng dụng dựa trên các bài kiểm tra. Điều này mang lại môi trường thử nghiệm đầu tiên để phát triển và mã ứng dụng được tạo ra không có lỗi.
- Với mỗi lần lặp lại, chúng ta viết các bài kiểm tra và kết quả là với mỗi lần lặp lại chúng ta nhận được một gói hồi quy tự động. Điều này hóa ra rất hữu ích vì với mỗi lần lặp lại, chúng ta có thể chắc chắn rằng các tính năng cũ hơn đang hoạt động.
- Các thử nghiệm này đóng vai trò là tài liệu về hành vi của ứng dụng và tham chiếu cho các lần lặp lại trong tương lai.
Như vậy, trong mô hình TDD nhiệm vụ kiểm thử do Developer đảm nhiệm và vai trò chuyên hóa của người Tester gần như không còn nữa. Chắc hẳn các bạn sẽ tự hỏi: “Vậy một Acceptance Tester như chúng ta có vai trò gì trong mô hình?”, “Tại sao tôi phải hiểu về TDD khi người ta ko cần tôi trong quy trình đó?”
Quả thực, trong mô hình TDD người Acceptance Tester thực sự đã chết. Tuy nhiên, việc cộng gộp vai trò phát sinh vấn đề quá tải cho người developer. Để làm tốt công việc, xuyên suốt chu trình người developer phải chú ý thêm những vấn đề thuần túy của kiểm thử (test) như: “Cái gì cần test và cái gì không?” “Viết bao nhiêu kịch bản là đủ?” “Làm sao để hiểu là test đó thất bại?” “Bắt đầu test từ đâu?” …
Để giải quyết vần đề phát sinh mà vẫn tận dụng triệt để lợi ích mà TDD mang lại, Dan North phát triển một mô hình mới với tên gọi: Behavior-Driven Development – BDD (hoặc ta có thể hiểu là Acceptance Test-Driven Development – ATDD). Trong đó, một vai trò mới trong việc thiết kế kiểm thử (Test Design) được đặt ra:
Thay vì chờ đợi sản phẩm hoàn thành và kiểm thử, người tester/analyst tham gia vào quá trình xây dựng mã nguồn với vai trò phân tích và xây dựng hệ thống kịch bản kiểm thử dưới góc độ ngôn ngữ tự nhiên dễ hiểu từ các yêu cầu (requirement). Đồng thời, họ giúp đỡ developer trong việc giải thích và đưa ra các phương án xây dựng mã nguồn mang tính thực tiễn với người dùng ngay trước khi bắt tay xây dựng.
Trong BDD thì người developer liên hệ mật thiết với người tester và xây dựng mã nguồn với những phương án mà tester cung cấp theo mô hình TDD.
Kịch bản kiểm thử được phân chia làm 2 lớp: Lớp chấp nhận (feature/acceptance test) và Lớp đơn vị (unit test). Theo đó, kịch bản kiểm thử lớp đơn vị mang thuần tính thiết kế và phục vụ cho việc kiểm thử lớp đơn vị (Unit test) còn kịch bản kiểm thử lớp chấp nhận có thể được tái sử dụng cho quá trình kiểm thử hồi quy về sau (Regression Test).
Kiểm thử hướng hành vi (BDD) là một phần mở rộng của TDD. Giống như trong TDD thì BDD chúng ta cũng viết các bài kiểm tra trước và thêm mã ứng dụng. Sự khác biệt chính mà chúng ta có thể thấy ở đây là:
- Các bài kiểm tra được viết bằng ngữ pháp tiếng Anh mô tả đơn giản
- Các thử nghiệm được giải thích là hành vi của ứng dụng và tập trung vào người dùng hơn
- Sử dụng các ví dụ để làm rõ các yêu cầu
Sự khác biệt này dẫn đến nhu cầu phải có một ngôn ngữ có thể định nghĩa với định dạng dễ hiểu.
🔆 Các tính năng của BDD
- Chuyển từ suy nghĩ trong "bài kiểm tra" sang suy nghĩ trong "hành vi"
- Hợp tác giữa các bên liên quan trong Kinh doanh: Nhà phân tích nghiệp vụ, Nhóm QA và nhà phát triển (developer, designer,...)
- Ngôn ngữ phổ biến, rất dễ để mô tả (tiếng anh phổ biến, ở VN thì giờ có cả framework hỗ trợ tiếng Việt)
- Mở rộng Phát triển dựa trên thử nghiệm (TDD) bằng cách sử dụng ngôn ngữ tự nhiên mà các bên liên quan phi kỹ thuật có thể hiểu được
- Các khung BDD như Cucumber hoặc JBehave là một công cụ hỗ trợ, đóng vai trò là "cầu nối" giữa Ngôn ngữ Kinh doanh và Kỹ thuật
BDD phổ biến và có thể được sử dụng cho các trường hợp kiểm thử cấp Đơn vị (Unit Test) và cho các trường hợp kiểm thử cấp UI.
Các công cụ như TestNG hoặc JUnit (dành cho Java), RSpec (dành cho Ruby) hoặc trong .NET như MSpec hoặc SpecUnit phổ biến cho Kiểm tra đơn vị theo cách tiếp cận BDD.
Ngoài ra, bạn có thể viết thông số kỹ thuật kiểu BDD về tương tác giao diện người dùng. Giả sử bạn đang xây dựng một ứng dụng web, có thể bạn sẽ sử dụng thư viện tự động hóa trình duyệt như Playwright hoặc Selenium và tạo tập lệnh cho nó bằng cách sử dụng một trong các khung mà An vừa đề cập hoặc một công cụ với format given/when/then chẳng hạn như Cucumber (cho Java) hoặc SpecFlow (cho .NET).
Trong nội dung chính của chúng ta là dùng Cucumber TestNG với Selenium Java nhé @@
✅ Công cụ Cucumber trong BDD
🔆 Cucumber là gì?
Cucumber là một công cụ kiểm thử hay là một Testing Framework hỗ trợ Behavior Driven Development (BDD), cho phép người dùng định nghĩa hành vi của hệ thống với tiếng anh có ý nghĩa đơn giản bằng cách sử dụng một ngữ pháp được xác định bởi một ngôn ngữ gọi là Gherkin.
Cucumber hướng tới việc viết test case mà bất kỳ ai cũng có thể hiểu cho dù họ không có chuyên môn kĩ thuật.
Ví dụ như các nền tảng quen thuộc như Selenium thì thường chỉ người viết test hoặc có kĩ năng lập trình mới hiểu được những gì đang test, còn khách hàng hoặc các bên liên quan thì không đọc ngay code để hiểu mà họ cần hiểu qua tài liệu.
Cucumber ban đầu được thực hiện dành riêng cho ngôn ngữ Ruby và sau đó được mở rộng sang Java, cả Ruby và Java đều sử dụng Junit để chạy test. Sau này là với nhiều ngôn ngữ khác và khung sườn khác như C#, Javascript, Python và cùng với framework như TestNG, specflow,...
🔆 Tại sao phải sử dụng Cucumber?
Một số lý do sau nên dùng Cucumber:
- Selenium và Cucumber là 2 công nghệ phổ biến
- Hầu hết các dự án sử dụng Selenium để kiểm thử chức năng, họ muốn tích hợp Cucumber vì Cucumber dễ đọc và dễ hiểu luồng ứng dụng hơn.
- Cucumber dựa trên phát triển hướng hành vi đóng vai trò là cầu nối giữa: Software Engineer và Business Analyst, Manual Tester và Automation Tester, Manual Tester và Developers.
🔆 Lợi ích của Cucumber
- Giúp cho các bên liên quan đến dự án (stakeholders) có thể theo dõi hoạt động test mà không cần kiến thức kĩ thuật chuyên môn
- Cucumber tập trung vào trải nghiệm người dùng cuối
- Cách viết mã dễ bảo trì và thực hiện
- Công cụ hiệu quả cho kiểm thử
✅ Test Cases dạng Gherkin trong Cucumber
🔆 Features
Feature có thể được hiểu là một đơn vị hoặc chức năng độc lập của một dự án. Ví dụ như một trang web thương mại điện tử, một vài tính năng (features) có thể xác định như:
- Đăng nhập bằng tài khoản hệ thống hoặc mạng xã hội
- Lựa chọn hàng hóa
- Thanh toán
- Đăng xuất
Trong Cucumber mỗi feature có thể hiểu là mỗi chứ năng độc lập của sản phẩm. Trước khi viết test scripts chúng ta nên xác định trước các features cần test để mang lại hiệu quả cao. Các tests xây dựng trong Cucumber được gọi là các feature files và có dạng .feature, mỗi feature cần test nên đặt trong 1 file feature tương ứng.
Features trong Cucumber được thể hiện bằng ngôn ngữ Gherkin bao gồm các thành phần sau:
- Feature: Mô tả test script hiện tại sẽ được chạy
- Scenario: Mô tả các bước thực hiện và kết quả đầu ra mong muốn cho một test case cụ thể
- Scenario Outline: Scenario thực hiện nhiều tập dữ liệu (sets of data). Dữ liệu được lưu dưới dạng cấu trúc, phân cách nhau bằng kí hiệu | |
- Given: Chỉ ra ngữ cảnh để thực thi
- When: Chỉ ra hành động đã được thực hiện
- Then: Kết quả đầu ra mong muốn của một test
Và còn các thành phần khác nữa. Mình học chi tiết ở bài sau nhé.
Ví dụ test cases dạng Gherkin:
🔆 Step Definitions
Mặc dù đã có file feature nhưng Cucumber chưa thực sự biết đoạn mã nào sẽ được thực thi cho từng scenario cụ thể được nêu trong file feature. Nó cần một file trung gian Step Definition, file này ánh xạ các bước thực hiện (step), features (Given,When,Then) trong scenario với đoạn mã (code) chức năng cần thực thi. Step được định nghĩa trong file java chẳng hạn như "stepdefinitions/StepLogin.java".
Ví dụ tương ứng với Test Cases Feature trên Login to HRM:
🔆 Scenario
Scenario là cấu trúc lõi của Gherkin. Kịch bản test khai báo với từ khóa "Scenario:" và theo sau là tên kịch bản. Mỗi tính năng có thể có một hoặc nhiều scenarios, mỗi scenario bao gồm một hoặc nhiều steps.
Ví dụ cái Calculator bên trên từ TDD: