Trình tự để phát triển ứng dụng bằng kỹ thuật phát triển phần mềm hƣớng hành vi đƣợc chia làm bốn giai đoa ̣n chính (Hình 4-2). Tuy nhiên, để ngƣời đọc nắm rõ quá trình phát triển ƣ́ng du ̣ng , trong phần này tác giả mô tả cách thƣ̣c hiê ̣n theo tƣ̀ng bƣớc nhỏ mô ̣t cho mô ̣t tính năng cu ̣ thể.
4.3.1. Tính năng “Xem trang chủ”
Kịch bản trang chủ: Trang chủ là đầu vào của hê ̣ thống , nó chứa các khối nô ̣i dung khác nhau đƣợc quy đi ̣nh bởi các css – id, trang chủ có cơ cấu các khối nhƣ sau:
TOP NAV
LEFT CONTENT RIGHT
FOOTER
Tính năng trang chủ (homepage) chƣ́a các ki ̣ch bản kiểm tra các khối nô ̣i dung của trang chủ nhƣ sau:
Feature: View homepage In order to view homepage As a visitor
I want to see homepage content block
Từ khóa Feature: dùng để xác định một tính năng , tƣ̀ khóa này nằm ở dòng đầu tiên của tập tin mô tả tính năng. Các dòng tiếp theo đƣợc viết theo định dạng mô tả một câu chuyện ngƣời dùng , chúng chỉ có tác dụng giải thích tính năng theo các khía ca ̣nh ngƣời dùng , ý nghĩa, hoạt động chứ không có vai trò trong quá trình kiểm thƣ̉ tƣ̣ đô ̣ng . Sau khi mô tả xong phần này , ta lần lƣợt đi ̣nh nghĩa các kịch bản sử dụng tính năng.
Kịch bản nền (Background):Tính năng trang chủ đều có ngữ cảnh chung là ngƣời dùng đang truy cập địa chỉ trang chủ (index.php) Ngữ cảnh này cần đƣợc định nghĩa trong mỗi kịch bản của tính năng “Xem trang chủ”. Để tránh lă ̣p la ̣i bƣớc này trên các kịch bản , ta khai báo nó ở ki ̣ch bản nền . Khi kiểm thƣ̉ mô ̣t tính năng, kịch bản nền sẽ đƣợc thực hiện trƣớc mỗi kịch bản thông thƣờng.
Background:
Given I am on homepage
Tiếp theo cần định nghĩa các kịch bản sử dụng cho tính năng này, việc phân chia các kịch bản tùy theo từng trƣờng hợp sử dụng hoặc chia vùng để kiểm thử.Chẳng hạn nhƣ kịch bản “Xem trang chủ” ta có thể chia ra các kịch bản nhƣ là: Xem banner, Xem hệ thống điều hƣớng (navigation), Xem Footer và kịch bản kiểm tra các khối nội dung khác. Các kịch bản đƣợc mô tả nhƣ sau:
Scenario: View banner
Then I should see "SKY COMPUTER" Scenario: View Navigation
Then I should see main menu Scenario: View footer
Then I should see "WEBSITE SKY COMPUTER - DESIGN BY GAMPT" Scenario Outline: View content as expected
Then I should see "<block>" block on the "<region>" region Examples: |block |region | |sub_menu|left | |search_dm |left | |slideshow | center| |sptieubieu | center| |dang_nhap |right | |search_gia |right | |thong_ke | right|
Sau khi viết tính năng, sử dụng Behat để kiểm thử tính năng. Cách gọi lệnh behat nhƣ sau:
bin/behat features/homepage.feature
Ở lƣợt gọi lệnh behat đầu tiên, ta có kết quả nhƣ sau:
10 scenarios (10 undefined) 20 steps (20 undefined) 0m0.165s
You can implement step definitions for undefined steps with these snippets:
/**
* @Given /^I am on homepage$/ */
public function iAmOnHomepage() {
throw new PendingException(); }
/**
* @Then /^I should see "([^"]*)"$/ */
public function iShouldSee($arg1) {
throw new PendingException(); }
/**
* @Then /^I should see main menu$/ */
public function iShouldSeeMainMenu() {
throw new PendingException(); }
/**
* @Then /^I should see "([^"]*)" block on the "([^"]*)" region$/
*/
public function iShouldSeeBlockOnTheRegion($arg1, $arg2) {
throw new PendingException(); }
Công cu ̣ Behat hỗ trợ kiểm thƣ̉ bằng cách đo ̣c kết quả thƣ̣c hiê ̣n ƣ́ng du ̣ng và đặc tả tính năng bằng Gherkin. Để kiểm thƣ̉ ƣ́ng du ̣ng web thì Behat phải đo ̣c đƣợc các kết quả trả ra của trình duyê ̣t web thông qua các bô ̣ mô phỏng trình duyê ̣t. Lần gọi đầu tiên, Behat thông báo tất cả các bƣớc đều ở trạng thái
“undefined” bởi vì nó không tìm thấy các định nghĩa bƣớc tƣơng ứng với các bƣớc trong kịch bản. Trƣờng hợp này Behat sẽ sinh ra một các khung định nghĩa bƣớc còn thiếu để ngƣời dùng có thể viết các định nghĩa bƣớc phù hợp. Nhƣ đã mô tả ở mục 3.4, MinkContext đã cài đặt sẵn một số định nghĩa bƣớc phổ biến với các hành vi của trang web, do đó để giảm thiểu công việc định nghĩa bƣớc, lớp ngữ cảnh FeatureContext của ứng dụng nên đƣợc kế thừa từ lớp MinkContext. Bổ sung đoạn mã extendsMinkContext cho lớp ngữ cảnh nhƣ sau:
/**
* Features context. */
class FeatureContext extends MinkContext {
/**
* Initializes context.
* Every scenario gets it's own context object. *
* @param array $parameters context parameters (set them up through behat.yml)
*/
public function __construct(array $parameters) {
// Initialize your context here }
}
Áp dụng Behat để kiểm thử lại tính năng, ta có kết quả gọi lệnh behat lƣợt thứ hai là:
10 scenarios (8 undefined, 2 failed)
20 steps (10 passed, 8 undefined, 2 failed) 0m0.496s
You can implement step definitions for undefined steps with these snippets:
/**
* @Then /^I should see main menu$/ */
public function iShouldSeeMainMenu() {
throw new PendingException(); }
/**
* @Then /^I should see "([^"]*)" block on the "([^"]*)" region$/
public function iShouldSeeBlockOnTheRegion($arg1, $arg2) {
throw new PendingException(); }
Ở lƣợt gọi lệnh behat lần thứ hai, sau khi lớp ngữ cảnh thừa kế từ MinkContext, ta cần cài đặt hai định nghĩa bƣớc cho kịch bản. Dựa vào khung định nghĩa bƣớc Behat sinh ra, chúng ta sẽ bổ sung các định nghĩa bƣớc còn thiếu cho lớp ngữ cảnh FeatureContext. Các định nghĩa bƣớc đƣợc viết nhƣ sau:
/**
* @Then /^I should see main menu$/ */
public function iShouldSeeMainMenu() {
$session = $this->getSession(); // đọc session từ bộ điều khiển trình duyệt Mink
$mainmenu = $session->getPage()->findAll('css','div#main_menu span a');
if (empty($mainmenu)) {
throw new Exception("No main menu was found on the page"); }
/**
* @Then /^I should see "([^"]*)" block on the "([^"]*)" region$/ */
public function iShouldSeeOnThe($block, $region) {
$session = $this->getSession();
$regione = $session->getPage()->find('css',"div#{$region}"); if (empty($regione)) {
$url = $session->getCurrentUrl();
throw new \Exception(sprintf("No region '%s' on the page %s", $region, $url));
}
if(!$regione->find('css',"div#{$block}")) {
throw new \Exception(sprintf("No block '%s' on the region '%s'", $block,$region));
} }
Sau khi hoàn thiện các định nghĩa bƣớc, gọi lệnh behat lần thứ ba để kiểm thử tính năng, kết quả nhƣ sau:
Background: # features/homepage.feature:6 Given I am on homepage # FeatureContext::iAmOnHomepage()
Then I should see "SKY COMPUTER" #
FeatureContext::assertPageContainsText() The current node list is empty.
Scenario: View Navigation # features/homepage.feature:10 Then I should see main menu #
FeatureContext::iShouldSeeMainMenu() No main menu was found on the page
Scenario: View footer # features/homepage.feature:12 Then I should see "WEBSITE SKY COMPUTER - DESIGN BY GAMPT" #
FeatureContext::assertPageContainsText() The current node list is empty.
Scenario Outline: View content as expected # features/homepage.feature:14
Then I should see "<block>" block on the "<region>" region # FeatureContext::iShouldSeeOnThe()
Examples:
| block | region | | sub_menu | left |
No region 'left' on the page http://localhost/behatdemo/ | search_dm | left |
No region 'left' on the page http://localhost/behatdemo/ | slideshow | center |
No region 'center' on the page ttp://localhost/behatdemo/ | sptieubieu | center |
No region 'center' on the page http://localhost/behatdemo/ | dang_nhap | right |
No region 'right' on the page http://localhost/behatdemo/ | search_gia | right |
No region 'right' on the page http://localhost/behatdemo/ | thong_ke | right |
No region 'right' on the page http://localhost/behatdemo/ 10 scenarios (10 failed)
20 steps (10 passed, 10 failed) 0m0.262s
Kết quả lƣợt kiểm thƣ̉ thứ ba, Behat xác định các phƣơng thức kiểm thử tƣơng ứng với các bƣớc và thống báo trạng thái của từng bƣớc thông qua màu sắc và ngoại lệ mà phƣơng thức kiểm thử trả ra . Các bƣớc thất bại đƣợc đánh dấu màu đỏ, kèm với thống báo lỗi (ngoại lệ) của phƣơng thức kiểm thử tƣơng ứng, các bƣớc thành công có màu xanh lá cây, … . Dựa vào kết quả Behat đƣa ra, ta sẽ cài đặt mã nguồn tƣơng ứng cho các bƣớc chƣa thành công. Tài liệu đặc tả bƣớc và nội dung phƣơng thức kiểm thử của bƣớc là các hƣớng dẫn cần thiết
để cài đặt tính năng. Chẳng hạn với tính năng trên, ta cần viết trang HTML, có các văn bản và các khối tƣơng ứng với đặc tả tính năng. Trang chủ index.php có mã HTML nhƣ sau:
<body>
<div id="body"><!--BEGIN #body-->
<div id="content" class="container_12"> <div id="top"><!--BEGIN #top-->
<div id="logo" class="grid_2">
<a title="Website Sky Computer">Logo</a> </div>
<div id="banner" class="grid_9"> <h1>SKY COMPUTER</h1>
</div>
</div><!--END #top-->
<div id="main_menu" class="grid_12" align="center"> <?php
include_once("include/main_menu.php"); ?>
</div>
<div id="container" class="grid_12"> <div id="left" class="grid_2">
<div id="sub_menu"> </div> <div id="search_dm"> <?php include_once("../include/search_dm.php"); ?> </div> </div>
<div id="center" class="grid_7"> <div id="slideshow">
</div>
<div id="sptieubieu">center </div>
</div>
<div id="right" class="grid_2">Right <div id="dang_nhap"> </div> <div id="search_gia"> </div> <div id="thong_ke"> </div> </div> </div>
<div id="bottom" class="grid_12" align="center"> <div id="copyright"><!--BEGIN #copyright-->
</div><!--END #copyright--> <div id="add"><!--BEGIN #add--> </div>
</div> </div> </body>
Gọi Behat để kiểm thử trang chủ lƣợt thứ tƣ, ta có kết quả:
10 scenarios (10 passed) 20 steps (20 passed) 0m0.321s
Ở lƣợt kiểm thử thứ tƣ, tất cả các kịch bản của tính năng trang chủ đều ở trạng thái thành công, quá trình cài đặt hoàn tất.
Cùng một tính năng, mã cài đặt có thể khác nhau phụ thuộc vào phong cách lập trình của lập trình viên. Phần mã nguồn sau khi cài đặt hoàn thiện cũng có thể có sự tinh chỉnh để mã nguồn mạch lạc, dễ đọc.
4.3.2. Tính năng “Đăng ký thành viên”
Tính năng đăng ký cho phép n gƣời dùng trở thành thành viên của hê ̣ thống bằng cách cung cấp các thông tin hợp lê ̣ thông qua form đăng ký . Tính năng này đƣợc viết dƣới da ̣ng câu chuyê ̣n ngƣời dùng nhƣ sau:
Feature: Register an account
In order to start using additional features of the site As any user
I should be able to register on the site
Các kịch bản đều có chung bƣớc ngữ cảnh là đi đến trang đăng ký từ liên kết “Đăng ký” ở trang chủ. Khai báo kịch bản nền nhƣ sau:
Background:
Given I am on homepage
Kịch bản một: Kiểm tra các thành phần trên Form đăng ký: Form đăng ký chƣ́a các nhãn và các trƣờng nhâ ̣p theo mô tả của ngƣời dùng . Bao gồm Ho ̣ tên, Email, Tên đăng nhâ ̣p, Mâ ̣t khẩu, Nhắc la ̣i mâ ̣t khẩu , Đi ̣a chỉ, Điê ̣n thoại, Ngày sinh, Giới tính. Mỗi nhãn có mô ̣t trƣờng nhâ ̣p với kiểu dƣ̃ liê ̣u phù hợp . Kịch bản cho các thành phần của Form Đăng ký đƣợc viết nhƣ sau:
Scenario: Kiểm tra các thành phần của form đăng ký When I follow "Đăng ký"
Then I should see "Họ và tên"
And I should see input "hoten" with "text" type And I should see "Email"
And I should see "Tên đăng nhập"
And I should see input "tendn" with "text" type And I should see "Mật khẩu"
And I should see input "matkhau" with "password" type And I should see "Nhắc lại mật khẩu"
And I should see input "matkhau2" with "password" type And I should see "Avatar"
And I should see input "avatar" with "file" type And I should see "Ngày sinh"
Sau khi xác đi ̣nh ki ̣ch bản trên , ta go ̣i lê ̣nh behat để kiểm t ra tra ̣ng thái của hê ̣ thống. Kết quả lƣợt thƣ̣c hiê ̣n đầu tiên của ki ̣ch bản trên là:
Background: # features/dangky.feature:6
Given I am on homepage # FeatureContext::iAmOnHomepage() Scenario: Kiểm tra các thành phần của form đăng ký #
features/dangky.feature:9
When I follow "Đăng ký" # FeatureContext::clickLink() Link with id|title|alt|text "Đăng ký" not found. Then I should see "Họ và tên" #
FeatureContext::assertPageContainsText() And I should see input "hoten" with "text" type And I should see "Email" #
FeatureContext::assertPageContainsText() And I should see input "email" with "text" type
And I should see "Tên đăng nhập" # FeatureContext::assertPageContainsText()
And I should see input "tendn" with "text" type
And I should see "Mật khẩu" # FeatureContext::assertPageContainsText()
And I should see input "matkhau" with "password" type And I should see "Nhắc lại mật khẩu" #
FeatureContext::assertPageContainsText()
And I should see input "matkhau2" with "password" type And I should see "Avatar" #
FeatureContext::assertPageContainsText() And I should see input "avatar" with "file" type
And I should see "Ngày sinh" # FeatureContext::assertPageContainsText()
1 scenario (1 failed)
15 steps (1 passed, 7 skipped, 6 undefined, 1 failed) 0m0.104s
You can implement step definitions for undefined steps with these snippets:
/**
* @Given /^I should see input "([^"]*)" with "([^"]*)" type$/ */
public function iShouldSeeInputWithType($arg1, $arg2) {
throw new PendingException(); }
Bổ sung định nghĩa bƣớc còn thiếu ở trên cho lớp FeatureContext.
/**
* @Given /^I should see input "([^"]*)" with "([^"]*)" type$/ */
public function iShouldSeeInputWithType($input, $type) {
$session = $this->getSession(); // get the mink session $element = $session->getPage()->findField( $input); if (empty($element))
{
$url = $session->getCurrentUrl();
throw new \Exception(sprintf("No field to '%s' on the page %s", $input, $url));
}
if($element->getAttribute('type') !==$type) {
throw new \Exception(sprintf("'%s' field type is not input %s" , $input, $type));
} }
Gọi lệnh behat để kiểm thử, kết quả lƣợt go ̣i lê ̣nh behat lần thƣ́ hai là:
When I follow "Đăng ký" #FeatureContext::clickLink()
Link with id|title|alt|text "Đăng ký" not found on homepage. […]
1 scenario (1 failed)
15 steps (1 passed, 13 skipped, 1 failed) 0m0.059s
Căn cứ vào thông báo lỗi của bƣớc thất bại, bổ sung mã HTML là một liên kết “Đăng ký” trên trang chủ. Đoạn HTML cần đƣa vào trang index.php nhƣ sau:
<div>
<a href="frm_dangky.php">Đăng ký</a> </div>
Gọi Behat để kiểm tra lại kịch bản, kết quả lƣợt kiểm thử thứ ba là:
Then I should see "Họ và tên #
FeatureContext::assertPageContainsText()
The text "Họ và tên" was not found anywhere in the text of the current page.
And I should see input "hoten" with "text" type FeatureContext::iShouldSeeInputWithType()
And I should see "Email" # FeatureContext::assertPageContainsText()
And I should see input "email" with "text" type # FeatureContext::iShouldSeeInputWithType()
And I should see "Tên đăng nhập" # FeatureContext::assertPageContainsText()
And I should see input "tendn" with "text" type # FeatureContext::iShouldSeeInputWithType()
And I should see "Mật khẩu" # FeatureContext::assertPageContainsText()
And I should see input "matkhau" with "password" type # FeatureContext::iShouldSeeInputWithType()
And I should see "Nhắc lại mật khẩu" # FeatureContext::assertPageContainsText()
And I should see input "matkhau2" with "password" type # FeatureContext::iShouldSeeInputWithType()
And I should see "Avatar" # FeatureContext::assertPageContainsText()
And I should see input "avatar" with "file" type # FeatureContext::iShouldSeeInputWithType()
And I should see "Ngày sinh" # FeatureContext::assertPageContainsText()
1 scenario (1 failed)
15 steps (2 passed, 12 skipped, 1 failed) 0m0.14s
Ở lƣợt kiểm thử thứ ba, bƣớc vừa đƣợc bổ sung mã nguồn đã ở trạng thái thành công. Căn cứ vào kết quả kiểm thử, ta bổ sung mã HTML cho form Đăng ký tại trang frm_dangky.php nhƣ sau:
<form action="process.php" method="post" id="register_form"> <p class="validate_msg"></p>
<p>
<label for="name">Họ và tên</label> <input name="hoten" type="text" /> <span class="val_hoten"></span> </p>
<p>
<label for="email">Email</label> <input name="email" type="text" /> <span class="val_email"></span> </p>
<p>
<label for="tendn">Tên đăng nhập</label> <input name="tendn" type="text" />
</p> <p>
<label for="matkhau">Mật khẩu</label> <input name="matkhau" type="password" /> <span class="val_matkhau"></span>
</p> <p>
<label for="matkhau2">Nhắc lại mật khẩu</label> <input name="matkhau2" type="password" /> <span class="val_matkhau2"></span>
</p> <p>
<label for="dienthoai">Điện thoại</label> <input name="dienthoai" type="text" />
<span class="val_dienthoai"></span> </p>
<p>
<label for="ngaysinh">Ngày sinh</label> <select name="thang">
<option value="">Tháng</option> <?php
$months = array('1' => 'Jan', '2' => 'Feb', '3' => 'Mar', '4' => 'Apr', '5' => 'May', '6' => 'June', '7' => 'July ', '8' => 'Aug', '9' => 'Sept', '10' => 'Oct', '11' => 'Nov', '12' => 'Dec');
foreach($months as $m => $month) {
?>
<option value="<?php echo $m; ?>"> <?php echo $month; ?> </option> <?php } ?> </select> <select name="ngay"> <option value="">Ngày</option>
<?php for($day = 1; $day < 32; $day++) { ?>
<option value="<?php echo $day; ?>"> <?php echo $day; ?> </option> <?php } ?> </select> <select name="nam"> <option value="">Năm</option> <?php $year = date("Y"); for($j = $year; $j > 1949; $j--) {
?>
<option value="<?php echo $j; ?>"> <?php echo $j; ?> </option> <?php } ?> </select> <span class="val_ngaysinh"></span> </p> <p>
<label for="gioitinh">Giới tính</label>
<input name="gioitinh" type="radio" value="nam" /> Nam <input name="gioitinh" type="radio" value="nu" /> Nữ <span class="val_gioitinh"></span>
</p>
<input type="submit" name="submit" value="Đăng ký"> </form>
Quá trình viết mã ngu ồn có thể thực hiện dần từng dòng mã HTML và sử dụng Behat để kiểm tra thƣờng xuyên trạng thái của mã nguồn. Căn cƣ́ vào kết quả mỗi lƣợt kiểm thƣ̉ đ ể có sƣ̣ điều chỉnh mã nguồn phù hợp. Ở ví dụ này, sau khi bổ sung mã nguồn ta gọi lại lệnh behat để kiểm thƣ̉. Kết quả gọi lệnh behat lần thƣ́ tƣ nhƣ sau:
1 scenario (1 passed) 15 steps (15 passed) 0m0.243s
Nhƣ vậy, kịch bản thƣ́ nhất dùng đ ể kiểm thử các thành phần của Form đăng ký đƣợc thực hiện thành công. Quá trình viết mã HTML có thể có sự khác nhau tùy theo đặc tả tính năng, cụ thể là các bƣớc của kịch bản và phong cách