View Vietnamese version

弊社では、最近テストコードの整備をやっております。前からはもちろんテストコードを重視していますが、なかなかテストコードを書くということとそれらのメインテナンスするのはかなり非効率で得られる効果は薄いなと思いました。今回はそれらの課題を示した上で、それらの課題を解決するためにCodeceptionを使うことにしました。

 

現状

 

まず、弊社は前からテストコードを書いてプロジェクトの品質を上げることに力を入れていました。エンジニアレベルのUnit TestとQA観点のFunctional Testの両方を書きました。

1. Unit Test

PHPプロジェクトであれば、PHPUnitを利用します。これに関しては、エンジニアにある程度任しています。この辺は課題がありません。

2. Functional Test

いわゆるテスターがブラウザーを使って、Webシステムをテストするのと同じものです。これは弊社もSeleniumを使って各種ブラウザーでUI・機能テストをやっています。

もっと言うと、Selenium IDEを使って、人の手でテストした結果をPHPテストスクリプトを自動的に生成するようにやっています。

この方法は一般的な方法だと考えていますが、ここで課題があります。

 

Functional Testの課題

 

弊社が抱えている課題とはこんな感じです。

 

  1. 何をテストしているかは明記ではない
  2. メインテナンス性が悪い。テストコードはよく壊れる

その理由としては、Selenium IDEを使うのはいいだが、Selenium IDEは基本XPath Locatorを使ってHTML上の要素(Element)を特定しているからです。

その一例を見ましょう。

                public function testDelete() {
                        $this->login();
                        $this->open("management/addOrder/index/orderId/1");
                        $this->click("//div[@id='menu-list']/div/div/div[2]/p");
                        $this->click("id=btn-submit-orderDetail");
                        $this->waitForPageToLoad(3000);
                        sleep(2);
                        $this->click("xpath=(//img[@alt='Delete'])[2]");
                     $this->assertTrue((bool)preg_match('/^Bạn thật sự muốn xóa[\s\S]$/',$this->getConfirmation()));
                        sleep(2);
                        $this->click("id=btn-list-change");
                        $this->waitForTextPresent('Hủy',3);

                        $this->click("id=btn-submitchange");
                          sleep(2);
                        $this->waitForElementPresent("css=.empty");
                        $this->assertText("css=td.empty", "Không có dữ liệu.");
                    
                }

$this->click("//div[@id='menu-list']/div/div/div[2]/p"); だと、当時のテスト担当者であれば、何をクリックしたかはわかりますが、他人は分かりませんよな。まして、その数ヶ月後テスト本人もなんのクリックかは分からなくなります。

また、これだとちょっとUIレイアウトを変更してしまうとこのテストコードはすぐ壊れます。UIレイアウトが変更されてテストコードが壊れたと思っていいぐらいですけど、本当は内容まで変わっていないのにちょっとレイアウトだけ変更したら壊れたし、またどうやって正しくテストコードを修正するかは分かりません。

これで困ってしまうことになります。だから、エンジニアからはテストコードを書く事自体には抵抗を感じてしまうのです。

何をテストしているか変わらない、かつそれらをメインテナンスが難しい → テストコードを書かない → 品質を担保できない

という悪循環になりがちです。

 

課題解決のため、Codeceptionを使うことに

 

Codeceptionは2011年11月からスタートされ、2012年1月に最初の安定バージョンがリリースされました。割りと、最近のツールと言えます。

PHPUnitもあって、Seleniumもあるからなんで今さらまたテストツールを開発したのと思うかもしれません。でも最近このツールに注目が上がっています。内部でPHPUnitを使っているからPHPUnitでやっていることはそのまま再利用できます。

Yii2フレームワークも本格的に採用しています。だからこそ、何かは優れた点があるでしょう。

いろんな解釈があると思いますが、弊社から見た時にこんな良い点があります。

 

  • 書きやすい
    だから、「書くのは簡単だから、テストコードを一杯書いてよ」ってエンジニアに言えるw
  • 読みやすい
    何をテストしているのかコードそのものを読めば分かる。だから仕様変更時、テストコードの改修も簡単に
  • 受入テストの強力サポート
    Acceptance Testingってものです。これは素晴らしい。仕様通りでありのままに書ける。テスター等は嬉しいでしょう。詳細は後ほど書きます。

その他には、

  • ツール全体はパッケージ化しているので使いやすい
    インストールコマンド一発で全部必要ツールを同時にインストールされている。個別対応・設定は必要なし
  • モジュールが一杯
    DB、Mongodb、Redis、API(REST)等のテストがしやすい
  • PHPBrowserを使えるからテスト実行が早い
    若干制限があって、最終的にはネーティブなブラウザーでテストしたほうがよいですが〜
  • Functional TestはMVCフレームワークをエミュレーターしているのでWebサーバーを使わなくてもテストできる
    メジャーMVCフレームワークを対応しているので、ますますテストが楽になる
  • デバッグ情報あり、テストが失敗時スクリーンショットまで撮ってくれる
    これは嬉しい。テスト失敗時に何か悪いかすぐ見つける

等もあります。

これなら、テストコードを書くこと自体は楽しくなるでしょう。品質管理はよくなるでしょうか。

 

Acceptance Testing

 

これは強力な機能です。すごくほしいです。

Acceptance Testingはユーザー受入テスト、検収テストというものです。プロダクト納品時、発注側は実際仕様どおりに機能なっているかを確認し、検収可能かどうか判断するものです。

指定ブラウザーで実際仕様を見ながらテストしていくわけです。

開発側は前もってそれらの検収用テストケースを網羅するテストコードはあれば、間違いなく品質はよくなるでしょう。

Codeceptionの一番うりはこれの強力なサポートでしょうか。この概念を使えば、PHPプロジェクトだろうが、Javaプロジェクトだろうが、Codeceptionを使って受入テストを書けます。

 

それでは、具体的にはどんな感じか見ていきたいと思います。

ログイン機能を受入テストするケースを見ましょう。今回は簡単に

  • 正しいユーザー名とパスワードを入力して、Loginをクリックすればログインできる
  • 正しくないユーザー名とパスワードを入力して、Loginをクリックすればログインできずにエラーメッセージが表示される

をテストしましょう。今回はChromeでテストしたいと思います。

その前の準備として、まずはCodeceptionの設定とコードを書きます。Codeceptionのインストールとその使い方自体は本家サイトをご覧になってください。

Codeceptionの設定(Chromeを利用するように)。tests/acceptance.suite.ymlファイルはこんな感じ

# Codeception Test Suite Configuration

# suite for acceptance tests.
# perform tests in browser using the WebDriver or PhpBrowser.
# If you need both WebDriver and PHPBrowser tests - create a separate suite.

class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver
        - AcceptanceHelper
    config:
        WebDriver:
            url: 'http://dev.co-mit.com/orderservice/'
            browser: chrome

テストURLの指定とbrowserをchromeにするだけ。

つぎに、テストコードを書きます。

./vendor/bin/codecept generate:cept functional Login
./vendor/bin/codecept generate:cept functional LoginFail

でテストコードスクリプトを生成します。で、実際テストコードを書きます。

・tests/acceptance/LoginCept.php

<?php 
$I = new AcceptanceTester($scenario);
$I->wantTo('Correct Login');
$I->amOnPage('/login');
$I->see('Login');
$I->fillField('LoginForm[username]', 'admin');
$I->fillField('LoginForm[password]', 'admin');
$I->click('Login');
$I->wait(3);
$I->see('Logout(admin)');
$I->seeInCurrentUrl('management');

・tests/acceptance/LoginFailCept.php

<?php 
$I = new AcceptanceTester($scenario);
$I->wantTo('Fail Login');
$I->amOnPage('/login');
$I->see('Login');
$I->fillField('LoginForm[username]', 'admin');
$I->fillField('LoginForm[password]', 'admin1');
$I->click('Login');
$I->wait(3);
$I->see('Incorrect username or password.');
$I->seeInCurrentUrl('login');

PHPコードを書いているが、別に説明が必要ありませんよね。仕様書通りに書いているだけって感じ。

$Iというのは英語のI(私です)。私はこれから何をしたいかを示していくということになります。後method名も簡単な英語ですのでそのまま分かりますよね。

IDEなどを使うと、なんのmethodがあるかすぐ分かります。

 

それから、Seleniumを起動します。

java -Dwebdriver.chrome.driver=./chromedriver -jar selenium-server-standalone-2.42.2.jar

chromedriverは別途ダウンロードする必要があります。

 

これで準備完了。さて実行しています。Chromeが立ち上がって、実行結果はPASSになっていればOK。

Huynh-no-MacBook-Pro:codeception vanchung$ ./vendor/bin/codecept run acceptance --debug
Codeception PHP Testing Framework v2.0.4
Powered by PHPUnit 4.2.0 by Sebastian Bergmann.

Acceptance Tests (2) ----------------------------------------------------------------------------
Modules: WebDriver, AcceptanceHelper
-------------------------------------------------------------------------------------------------
Trying to Correct Login (LoginCept)                                                         
Scenario:
* I am on page "/login"
* I see "Login"
* I fill field "LoginForm[username]","admin"
* I fill field "LoginForm[password]","admin"
* I click "Login"
* I wait 3
* I see "Logout(admin)"
* I see in current url "management"
 PASSED 

Trying to Fail Login (LoginFailCept)                                                        
Scenario:
* I am on page "/login"
* I see "Login"
* I fill field "LoginForm[username]","admin"
* I fill field "LoginForm[password]","admin1"
* I click "Login"
* I wait 3
* I see "Incorrect username or password."
* I see in current url "login"
 PASSED 

-------------------------------------------------------------------------------------------------


Time: 10.85 seconds, Memory: 10.00Mb

OK (2 tests, 6 assertions)

これで完了ですね。テストコードを何をテストしたいかありのままに書いているって感じですよね。そうすると、他人は見ても数ヶ月後の自分は見ても問題無いようですよ。仕様変更したってすぐ書き直せると実感できます。

あれれ、結局はPHPコードじゃ、それじゃなにもわからない人はどうやってレビューできるの?それと、せっかくありのままで書いているからテストケースとして納品できないの?と思うのはありますよね。

それは大丈夫です。Codeceptionの便利な機能があります。それはテストコードを読みやすいtxtファイルに変換してくれます。コマンドは1つだけ。

Huynh-no-MacBook-Pro:codeception vanchung$ ./vendor/bin/codecept generate:scenarios acceptance
* Login generated
* Login_Fail generated

そして、上記の2ファイルのテストコードスクリプトはこんな内容にしてくれるんです。

・tests/_data/scenarios/acceptance/Login.txt

I WANT TO CORRECT LOGIN

I am on page '/login'
I see 'Login'
I fill field 'LoginForm[username]'," 'admin'
I fill field 'LoginForm[password]'," 'admin'
I click 'Login'
I wait "3"
I see 'Logout(admin)'
I see in current url 'management'

・tests/_data/scenarios/acceptance/Login_Fail.txt

I WANT TO FAIL LOGIN

I am on page '/login'
I see 'Login'
I fill field 'LoginForm[username]'," 'admin'
I fill field 'LoginForm[password]'," 'admin1'
I click 'Login'
I wait "3"
I see 'Incorrect username or password.'
I see in current url 'login'

 

これはレビューできますよね。このままテストケース仕様書にコピー&ペストして納品できるぐらいですよね。

 


 

Vietnamese Version

 

Ở công ty mình thì anh em kỹ sư thì rất ngại viết test code. Tâm lý thì hiểu, nhưng test code quan trọng. Thực tế thì vấn đề viết test code vẫn chưa được viết hiệu suất, và maintenance rất khó. Nên thật khó thấy được hiệu quả của nó. Ở bài này thì sẽ nói về các vấn đề đó, và để giải quyết vấn đề đó dùng Codeception như thế nào.

 

Hiện trạng

 

Trước hết ở công ty trước giờ có 2 kiểu viết test code.

1. Unit Test

Nếu là PHP project thì dùng PHPUnit. Cái này thì có thể để cho anh em kỹ sự tự xử lý. Chỗ này thì không có vấn đề gì.

2. Functional Test

Là loại dùng browser để test. Cái này thì dùng thông qua Selenium. Cụ thể là dùng Selenium IDE để sinh ra các đoạn test code php script. 

Cái cách này cũng là cách thông dụng mà thôi. Nhưng ở đây sinh ra vấn đề.

 

Vấn đề của Functional Test

 

Công ty chúng tôi thì gặp các vấn đề sau: 

 

  1. Không biết đang test cái chi cả
  2. Tính maintenance rất thấp, test code hay bị vỡ, failed khi thay đổi layout

Lý do là dùng Selenium IDE mà Selenium IDE thì dùng XPath Locator để định vị các element trên source HTML.

Chúng ta hãy xem ví dụ sau: 

                public function testDelete() {
                        $this->login();
                        $this->open("management/addOrder/index/orderId/1");
                        $this->click("//div[@id='menu-list']/div/div/div[2]/p");
                        $this->click("id=btn-submit-orderDetail");
                        $this->waitForPageToLoad(3000);
                        sleep(2);
                        $this->click("xpath=(//img[@alt='Delete'])[2]");
                     $this->assertTrue((bool)preg_match('/^Bạn thật sự muốn xóa[\s\S]$/',$this->getConfirmation()));
                        sleep(2);
                        $this->click("id=btn-list-change");
                        $this->waitForTextPresent('Hủy',3);

                        $this->click("id=btn-submitchange");
                          sleep(2);
                        $this->waitForElementPresent("css=.empty");
                        $this->assertText("css=td.empty", "Không có dữ liệu.");
                    
                }

$this->click("//div[@id='menu-list']/div/div/div[2]/p"); Với đoạn code này, tôi đố bạn có thể hiểu đã click cái gì. Người khác nhìn vào không hiểu gì đúng không? Ngay cả người viết ra cái test này lúc đó có thể đã hiểu mình click vào cái gì, nhưng sau vài tháng chắc chẳng nhớ nổi đúng không?

Thêm nữa, viết kiểu này thì chỉ cần thay đổi tí layout của html là đoạn test code trên bị failed là cái chắc. Failed rồi cũng chẳng dễ dàng để biết tại sao và phải sửa như thế nào. Cái gì là đúng.

Như vậy anh em kỹ sư dần dần cũng sẽ nản, và cảm thấy viết test code rất phiền phức. 

Và rồi dẫn đến cái gì, sẽ chẳng muốn viết test code nữa. Và chất lượng sản phẩm sẽ đi xuống. Công ty mình đang gặp đúng cái bệnh này.

 

Dùng Codeception để xử lý vấn đề

 

Codeception được bắt đầu phát triển từ tháng 11 năm 2011, và tháng 1 năm 2012 thì ra phiên bản stable đầu tiên. Nhưng vậy có thể nói cái test tool này còn mới mẻ.

Đã có sẵn PHPUnit, cũng đã có sẵn Selenium, thì sao tự nhiên giờ này vẫn phải phát triển thêm lõi test tool mới nhỉ? Điều này nếu chúng ta tự đặt câu hỏi cũng dễ hiểu thôi nhỉ. Nhưng hiện tại thì Codeception rất được được quan tâm. Trong lòng của nó sử dụng PHPUnit nên những gì đã làm với PHPUnit đều có thể làm được ở test tool này.

Yii2 cũng đã chính thức xài lõi này. Nên như vậy chúng ta có thể nghĩ rằng nó phải có điều gì tốt hơn các cách test cũ thông thường.

Tất nhiên sẽ có nhiều lời giải thích cho điều này, riêng với công ty chúng tôi thì cảm nhận các điều tốt sau:

 

  • Dễ viết
    Cho nên có thể nói với các anh em kỹ sư là: Dễ viết vậy nên hãy viết cho thật nhiều code nhé. Để tránh bệnh lười, thấy rắc rối www
  • Dễ đọc
    Chỉ cần đọc chính đoạn test code đó có thể hiểu là muốn test cái gì. Cho nên khi thay đổi spec của dự án thì có thể đơn giản thay đổi được đoạn test code này.
  • Support rất tốt khái niệm test nghiệm thu
    Cái này gọi là Acceptance Testing. Cái này rất tuyệt. Viết test code y chang như spec dự án. Về điều này cụ thể tôi sẽ trình bày ở dưới.

Ngoài ra còn 1 số điều tốt khác, 

  • Đóng gói thành package, nên dễ sử dụng
    Chỉ cần 1 câu lệnh có thể install được. Các gói library khác tự động được install nên không cần phải xử lý gì thêm nữa cả.
  • Module rất nhiều
    DB、Mongodb、Redis、API(REST) gì gì hầu như có hết.
  • Dùng được khái niệm PHPBrowser nên test chạy cực nhanh
    Tuy nhiên có giới hạn về chức năng, không test được cái quá khó, nên test với cái khó thì dùng qua browser chính quy.
  • Functional Test thì nhúng vào từng framework không cần phải thiết lập web site để test
    Mấy cái major MVC framework đều tích hợp được cả.
  • Dễ debug, khi test failed còn chụp cả screen shot cho mình
    Cái này thì quá tốt. Khi test failed biết ngay cái gì là vấn đề. 

v.v...

Như vậy thì việc viết test code không còn là vấn đề. Dễ viết, dễ maintenance vậy thì chất lượng dự án sẽ lên.

 

Acceptance Testing

 

Cái này thật là chức năng quá tốt. Có lẽ công ty chúng tôi quyết định xài là vì điều này.

Acceptance Testing là test nghiệm thu. Khi nộp hàng thì bên đặt hàng sẽ phải kiểm tra xem sản phẩm đó có đúng theo yêu cầu của dự án hay không? để có thể phán đoán là nghiệm thu ok hay không?

Tức họ sẽ dùng browser đã yêu cầu đối ứng, và sẽ test sản phẩm đó. 

Về phía develop thì trước khi nộp hàng có tất cả các loại test code acceptance này, bao trùm tất cả các yêu cầu của dự án thì chắc chắn dự án đó phải có chất lượng cao rồi.

Có lẽ điểm mạnh nhất của Codeception chính là ở chức năng này. Nếu sử dụng cái khái niệm Acceptance Testing này thì ngay cả các dự án của Java cũng sẽ có thể áp dụng lõi này để test được. Bản chất chỉ dùng browser và test URL mà thôi.

 

Vậy sau đây chúng ta cùng xem ví dụ cụ thể.

Để ví dụ hãy nghĩ là chúng ta sẽ test chức năng Login và đơn giản yêu cầu như sau: 

  • Nhập đúng username & password, sau đó click vào Login thì sẽ login được. 
  • Nhập không đúng username & password, sau đó click Login thì sẽ không login được, và sẽ nhìn thấy error lỗi.

Chúng ta sẽ test yêu cầu trên với Chrome.

Trước tiên chúng ta làm vài khâu chuẩn bị, trước hết là thiết định Codeception và viết test code. Về việc install hay cách sử dụng Codeception thì các bạn hãy tham khảo ở site của họ, ở đây tôi xin giản lược.

Thiết định Codeception(Để có thể sử dụng Chrome). File thiết định là: tests/acceptance.suite.yml

# Codeception Test Suite Configuration

# suite for acceptance tests.
# perform tests in browser using the WebDriver or PhpBrowser.
# If you need both WebDriver and PHPBrowser tests - create a separate suite.

class_name: AcceptanceTester
modules:
    enabled:
        - WebDriver
        - AcceptanceHelper
    config:
        WebDriver:
            url: 'http://dev.co-mit.com/orderservice/'
            browser: chrome

Chỉ cần định nghĩa test site url và browser sử dụng là chrome mà thôi. 

Sau đó chúng ta viết test code. Dùng câu lệnh dưới sẽ gen file test ra, sau đó chúng ta viết nội dung.

./vendor/bin/codecept generate:cept functional Login
./vendor/bin/codecept generate:cept functional LoginFail

 

・tests/acceptance/LoginCept.php

<?php 
$I = new AcceptanceTester($scenario);
$I->wantTo('Correct Login');
$I->amOnPage('/login');
$I->see('Login');
$I->fillField('LoginForm[username]', 'admin');
$I->fillField('LoginForm[password]', 'admin');
$I->click('Login');
$I->wait(3);
$I->see('Logout(admin)');
$I->seeInCurrentUrl('management');

・tests/acceptance/LoginFailCept.php

<?php 
$I = new AcceptanceTester($scenario);
$I->wantTo('Fail Login');
$I->amOnPage('/login');
$I->see('Login');
$I->fillField('LoginForm[username]', 'admin');
$I->fillField('LoginForm[password]', 'admin1');
$I->click('Login');
$I->wait(3);
$I->see('Incorrect username or password.');
$I->seeInCurrentUrl('login');

Viết bằng PHP, tuy nhiên chẳng cần phải giải thích nhỉ. Nó cứ như viết tiếng Anh và đúng theo yêu cầu của dự án.

$I là I của tiếng Anh, tức là tôi. Tôi muốn làm cái gì? và tôi viết đúng như vậy. Ý là vậy. Các method name cũng dùng tiếng Anh đơn giản dễ hiểu.

Nếu bạn dùng IDE thì các method name càng dễ tìm.

 

Sau đó là khởi động Selenium

java -Dwebdriver.chrome.driver=./chromedriver -jar selenium-server-standalone-2.42.2.jar

※Chúng ta cần download thêm driver để kích hoạt chrome: chromedriver.

 

Như vậy là chuẩn bị xong. Giờ chạy test code. Nếu bạn thấy Chrome được khởi động và kết quả thực thi là PASS thì tức đã ok.

Huynh-no-MacBook-Pro:codeception vanchung$ ./vendor/bin/codecept run acceptance --debug
Codeception PHP Testing Framework v2.0.4
Powered by PHPUnit 4.2.0 by Sebastian Bergmann.

Acceptance Tests (2) ----------------------------------------------------------------------------
Modules: WebDriver, AcceptanceHelper
-------------------------------------------------------------------------------------------------
Trying to Correct Login (LoginCept)                                                         
Scenario:
* I am on page "/login"
* I see "Login"
* I fill field "LoginForm[username]","admin"
* I fill field "LoginForm[password]","admin"
* I click "Login"
* I wait 3
* I see "Logout(admin)"
* I see in current url "management"
 PASSED 

Trying to Fail Login (LoginFailCept)                                                        
Scenario:
* I am on page "/login"
* I see "Login"
* I fill field "LoginForm[username]","admin"
* I fill field "LoginForm[password]","admin1"
* I click "Login"
* I wait 3
* I see "Incorrect username or password."
* I see in current url "login"
 PASSED 

-------------------------------------------------------------------------------------------------


Time: 10.85 seconds, Memory: 10.00Mb

OK (2 tests, 6 assertions)

Như vậy là xong. Bạn có thể nhận ra rằng, test code mà đúng là nghĩ sao, muốn test gì thì viết y thế đúng không? Vậy thì có gì là khó khăn? Quá nhẹ nhàng. Ai đọc cũng hiểu cả.

Có thể bạn sẽ có ý kiến sau: Vậy thì nó cũng là PHP, người không biết PHP thì sao review test case đây? Hay đã viết dễ hiểu đến đó thì có thể export thanhf test case để nộp luôn cho khách hàng được không?

Câu hỏi đó chính xác và Codeception có thể làm được điều đó. Dùng 1 câu lệnh sau thì sẽ tạo ra được các nội dung đó. 

Huynh-no-MacBook-Pro:codeception vanchung$ ./vendor/bin/codecept generate:scenarios acceptance
* Login generated
* Login_Fail generated

Vậy chúng ta hãy xem nội dung của các file này.

・tests/_data/scenarios/acceptance/Login.txt

I WANT TO CORRECT LOGIN

I am on page '/login'
I see 'Login'
I fill field 'LoginForm[username]'," 'admin'
I fill field 'LoginForm[password]'," 'admin'
I click 'Login'
I wait "3"
I see 'Logout(admin)'
I see in current url 'management'

・tests/_data/scenarios/acceptance/Login_Fail.txt

I WANT TO FAIL LOGIN

I am on page '/login'
I see 'Login'
I fill field 'LoginForm[username]'," 'admin'
I fill field 'LoginForm[password]'," 'admin1'
I click 'Login'
I wait "3"
I see 'Incorrect username or password.'
I see in current url 'login'

 

Cái này thì dù là ai cũng hiểu nhỉ. Vậy chúng ta có thể viết báo cáo dựa trên các nội dung này rồi.

Mình nghĩ dùng cái này thật tốt, mong ý kiến đóng góp của các bạn. 


One comment

#847
Elric
2017-05-14 20:39
That's really thkniing out of the box. Thanks!

Leave a Comment

Fields with * are required.