顯示具有 test 標籤的文章。 顯示所有文章
顯示具有 test 標籤的文章。 顯示所有文章

2015年9月22日 星期二

『在 PHP 上學會自動化測試與實戰 TDD』 ... 下課後


前言
我自己剛開始在使用 phpunit 遇到的一些問題: (嚴格來說也不能說奇怪... 要說自己涉世未深..)
1. 效能問題 (越跑越慢, 甚至慢的奇怪).
2. Database 沒有設定好,設定錯誤導致應該要以 SQLite 而不小心跑成 MySQL.
3. 類別的 Mock 不會寫.
4. tearDown 的地方有時候會忘記.
5. 測試碼太多支時,會有點不知道怎麼組織
6. 會丟 Exception 的 function 不知道怎麼測,又不希望被標成 risky tests (phpunit 如果發現你的  測試 function 根本沒有 assert 時會出現的訊息)
7. 知道的 assert 太少 (雖然這完全是沒唸書的關係...)


上週末,去上了兩天小鐵(大澤木小鐵)的 TDD 課程,更了解 TDD 的連續兩天 workshop,算是非常有收獲,尤其七,八月以來,工作上的團隊開發也漸漸開始使用 TDD 開發,雖然很不熟練,用法用的亂七八糟,但是也是因為有過這些錯的經驗的接觸之後,來上課才更能瞭解什麼是 better way,我還蠻喜歡這種感覺的,就是我不喜歡從0開始,然後一直讓別人來教我什麼是好的, 什麼是對的,這樣的感受太不夠深刻了 :P 不犯錯的學習只會讓我覺得這件事情好像不難?! 然後挑戰性就失去了...


第一天講單元測試, unit test 算是很小的單元,一次只做一件事情的測法,這個算是剛學習測試最好上手 and 理解的。再來一些原則,像是 3A ... 還有教到一些重構,因為寫測試的時候,難免會邊重構, 邊寫測試,所以學測試,跟重構也有一點關係。(我也想起自從開始學 TDD 之後,也會開始對重構很有興趣,這真的是不知道為什麼 XD) 因為這堂課的關係,我才發現 phpstorm 好棒啊! 這些 refactor 的功能 sublime 都沒有.. 雖然有些殘念,不過接下來可以在專案重構時,也趁機學學使用 phpstorm。

(圖: 主辦單位準備的點心,這紅茶綠茶讓人想到測試就是紅燈, 綠燈不斷循環阿... 但記得最後 commit 上去要綠燈就是了~)

另外 phpunit 產出 coverage 報表非常的不錯,看到程式碼開始有一點覆蓋率之後,感覺就像是有在上班啊 (誤)

第二天著重在 BDD,只能說 BDD 非常有趣,大概是因為感覺很像照著 spec 來寫測試,那些腳本寫起來,雖然很抽象...,但是我竟然是在用程式語言寫一個 spec 的感覺,很神奇+詭異,測試工具真的是什麼怪功能都有 @@ ~ 只是剛開始寫,覺得不太順暢,還需要多練習就是了...尤其 Example 用 Laravel 來講解,又更貼近於我的日常了 :P ,心得是: 相較於 TDD,我覺得團隊要導入 BDD 比 TDD 還難...,只是『感覺』,說不上很特別的理由,大概是因為 spec 本身就很難有(存在),就算有也會變動,如果今天的需求是改 spec,那麼 BDD 要改,程式改了,TDD 也要改,一個測流程,一個測功能跟程式,超 high 的 XD 感覺 commit 會很多~

在專案上的導入測試這件事情來說,我反而覺得是誰先來開始帶起這個風氣比較重要。

回家 TODO :
* 學習使用 mockery 去 Mock 物件
* behat (BDD)
* 等 91 哥開下一梯 TDD 的課程 :)

為什麼要上 TDD 實戰,跟上面的前言7點有很大的關係,因為有些情況不知道自己做得對不對,有些內心還沒有解法,所以想要更了解多一點,這樣比較扎實 :D 這兩天真的很有回饋感 XD 感謝主辦單位,也謝謝小鐵 ~

2015年8月5日 星期三

Laravel 5.1 測試時模擬 user 登入

最近這幾天都在寫測試,花蠻多時間的,不熟之外,資源也不夠齊(不好找,有時 L4, L5 參雜,自己都搞混了)。

如果你是用 Laravel 4,那麼請參考 4 的說明 (有一段:『 You may set the currently authenticated user using the be method』從這邊開始 )。

在 Laravel 5.1 測試 user 登入狀態,要用 Model Factories 的方式,在這裡也要感謝小鐵大大,幫了我很多忙 :P。


大概的重點如下
* 在 phpunit 測試 code 裡面,使用 factory 去 fake 一個 $user,如官方文件說明,看你想要用 make() 還是 create()。另外我也意外地發現 $faker 蠻方便的,很開心的在 database/factories/ModelFactory.php 盡量學習用 $faker XD

我的 sample code,不是很完美,大概描述一下作法類似這樣子而已:
因為之後有很多模組或是功能都會需要這個假裝有 user 登入的測試情況,所以把它寫在外面 (TestCase去),繼承 TestCase 的測試 php 就都可以使用。
// TestCase.php

protected function demoUserLoginIn()
{
    $user = factory(App\User::class)->make();
    // Use model in tests...
    // 登入 user
    $this->be($user);
}

繼承 TestCase 的測試程式:
//  某隻 extends 自 TestCase 的測試.php

public function testLogout()
{  
    // 使用 TestCase 的 demoUserLoginIn
    $this->demoUserLoginIn();

    // 檢查登入狀態
    $this->assertTrue(Auth::check());
    // do other things...
}

* 另外一種測法是,只是測試資料帶入 form 然後登入的情況,一樣也可以模擬出登入的情況,只是每次都要繞到登入頁,這種測法就比較適合單純測登入表單的情況。

測試 form 登入,資料帶入 POST 的情況,要小心 csrf 的問題,還要在測試加上 Session::start();。
public function testLogin()
{
    Log::info('testLoginSuccess');
      Session::start();
    
    $this->call('POST', '/login', [
        'username' => 'winwuloveyou',
        'password' => '123456',
        '_token' => csrf_token(),
    ]);

    $this->assertRedirectedTo('dashboard');
}


參考
書目:「Basic TDD in Laravel 5」作者: Jace Ju.
Testing Laravel 5 Routes with CSRF Protection Using PHPUnit
https://laracasts.com/series/whats-new-in-laravel-5-1/episodes/5


2015年8月2日 星期日

O'REILLY『可測試的 JavaScript』 讀書心得

(我好幾次呆坐在螢幕前,不知道該分享些什麼...)

第一次在書店看到這本書時還蠻興奮的,又是 JavaScript,又講測試,感覺就應該讀一下,立刻帶回家 XD (敗家)。

幾個心得:
*測試是有方法可以遵循,有經驗可以參考,但是寫測試是要練出來的,只有自己練出來才是最真實的...。

 *  TDD (Test-Driven Development) 測試驅動開發,是敏捷開發的推薦做法,就是先寫測試,再寫相對應的程式。 

 * BDD (Behavior-Driven Development) 行為驅動開發,建立在 TDD 之上,測試描述一個程式或是模組的行為。 

*要測試你的程式碼,首先程式碼要先可以被測試才是重點,畢竟程式可以很複雜,也可以很簡單,可以測試的程式碼要盡量是低耦合, 獨立的。(多做程式的拆解練習,把複雜的程式慢慢拆解為一隻隻比較單純, 單一任務的程式)。

*第三章講的事件導向架構,我沒有看得很懂就是,但是重點跟上一點接近。

*第四章講單元測試,單元測試算是我第一次接觸測試的方式,他需要工程師去思考每個單元測試需要哪些測試案例(腦補一些可能出現的弱點,就好像某隻程式明明需要帶入一個 值為 email 的參數,但是只有測到這個參數帶進去,卻沒有測試 email 的格式是否正確之類的...),因此,單元測試算是不能百分之百找到 bug 的測試,但是單元測試的重點是在於測試每隻程式的功能是否有符合需求(功能確認導向)。
   另外單元測試的 code 我覺得蠻好讀的,他的重點就是斷言(assert),直接去驗證某個值是否符合預期。

* 程式碼涵蓋率(Code Coverage),用來測量已經執行的測試程式碼之於未執行過的程式碼的涵蓋數,結果會用百分比來表示。 通常跟單元測試有關。書中 P.95 有個涵蓋率的參考:
    - 低於 50% 的 code coverage,是個低標區 (red flag)
    - 介於 60%~80% 的 code coverage,是甜蜜區 (sweet spot)
    - 大於 80% 的 code coverage,單元測試的效能會符合收益遞減率(<- 基本上我看不懂這個意思是什麼) ( code coverage 過高可能有誤導之嫌)。
   另外,code coverage 最好是自動化產生。

* 測試環境的模擬,也是一個很重要的測試技能。也許你在測試某隻程式時,需要重新設定資料庫的狀態,或是某隻程式可能相依於另一隻程式(相依性,是單元測試不想要測試的部分,可以利用 mocks 或是 stubs)。

*  mocks 跟 stubs 的不同在於 mocks 用於命令, stubs 用於查詢(參考 p.95)
  
*  mocks 用來被驗證 function 正確呼叫外部的 api。

*  stubs  用來假造回傳值到需要被測試的 function。

*  Doubles (測試替身),用來模擬相依性所需要用的物件,他也可同時扮演 mock 或是 stub。

*  前端 JavaScript,可參考 PhantomJS, Selenium。因為這兩個,方向都很大,我也沒摸熟過,不方便提...

*  第六章講效能測試,瀏覽器效能測試可以參考 HAR,書中有說明如何產生 HAR 檔案,如何查看。負載測試(Load Testing)  最常聽到的就是 ab (Apache Bench)。

*  效能測試可以用來辨別會阻礙效能的瓶頸,負載測試用來測試程式可以處理多少事情。

最後,我是覺得剛接觸測試的話,可以翻翻這本書,但是不用帶著墨在書本範例的程式碼,概念上先瞭解一下,這本書用蠻多 YUI 的 code 來解說,大概看一下就好,只是像我這種沒用過 YUI 的人,會覺得有點失落 XDD,因為書本有時候會提到一些 solution 是針對 YUI 的解法,所以有時候也會蠻疑惑的 XD。

前陣子在工作上是使用 Mocha, Chai 寫測試,有空再來分享這一塊。

以前看到別人講測試會覺得有點複雜,工具太多,要測試的方法也很多,嘗試過 chai, mocha 之後我才漸漸了解,原來這些測試工具都是環環相扣的,而且很有可能會上癮,也許你單元測試寫完之後,你就會想了解 code coverage,然後你也會想要用一下 phantom 或是 selenium,然後測試的項目,就會越來越齊全...,也會越有成就感。



參考:


2015年7月30日 星期四

Laravel phpunit 報錯 Cannot redeclare .. previously declared ...

今天在 laravel 的專案下跑 phpunit 突然報了一堆 [Symfony\Component\Debug\Exception\FatalErrorException] 的錯誤。

可能的原因有很多。

以我的問題來說,一追才知道原來是因為我在 blade.php 的 view 下,寫了兩隻現成的 function,但是 phpunit 不能這樣做,測試被執行之後,重複執行相同的函數,就等於函數被重新定義,所以好方法就是,還是得乖乖的把這兩個 function 封裝到某個 class 下。

原本我的 xx.blade.php 是這樣:
function myViewHelper() {
      return ...;
}

<a href="{{ myViewHelper() }}">Go!</a>

最後我寫了一支類似 helper 或是 service 的 php,把 class use 進來,再 call 那個 function,得救 :P 。

類似這樣:
use XXX\XXX\ myViewHelper as myViewHelper;

<a href="{{ myViewHelper::getSortLinks()  }}">Go!</a>


應該還有更好的方式,可能 use 不應該放在 view(xx.blade.php) 的檔案裡,不過重點就是... 不能直接定義 function 在 view 裡。



2015年7月15日 星期三

[chai]The ChromeDriver could not be found on the current PATH

在使用 chai 的 webdriver 時出現了一點問題,其 demo code 如下,基本上同官方網站:
// test/index.js
var sw = require('selenium-webdriver');
var chai = require('chai');
var chaiWebdriver = require('chai-webdriver');

// Start with a webdriver instance:
var driver = new sw.Builder()
  .withCapabilities(sw.Capabilities.chrome())
  .build();

// And then...
chai.use(chaiWebdriver(driver));

// And you're good to go!
driver.get('http://github.com');
chai
  .expect('#site-container h1.heading')
  .dom.to.not.contain.text("I'm a kitty!");

然而在執行的時候出現這個問題:
node test/index.js
XXXX/node_modules/selenium-webdriver/chrome.js:54
        'http://chromedriver.storage.googleapis.com/index.html and ensure ' +
                                                                            ^
Error: The ChromeDriver could not be found on the current PATH. Please download the latest version of the ChromeDriver from http://chromedriver.storage.googleapis.com/index.html and ensure it can be found on your PATH.
    at Error (native)
而且其實我的 chromeDriver 已經是最新版了...

後來在 stackoverflow 找到了這個解法,還蠻有用的:
http://stackoverflow.com/questions/27733731/passing-requirechromedriver-path-directly-to-selenium-webdriver

修改結果如下:
var sw = require('selenium-webdriver');
var chai = require('chai');
var chaiWebdriver = require('chai-webdriver');

// Start with a webdriver instance:
var chrome = require('selenium-webdriver/chrome');
var path = require('chromedriver').path;

var service = new chrome.ServiceBuilder(path).build();
chrome.setDefaultService(service);

var driver = new sw.Builder()
    .withCapabilities(sw.Capabilities.chrome())
    .build();

// And then...
chai.use(chaiWebdriver(driver));

// And you're good to go!
driver.get('http://github.com');
chai
  .expect('#site-container h1.heading')
  .dom.to.not.contain.text("I'm a kitty!");

FYI.


Vue multiselect set autofocus and tinymce set autofocus

要在畫面一進來 focus multiselect 的方式: 參考: https://jsfiddle.net/shentao/mnphdt2g/ 主要就是在 multiselect 的 tag 加上 ref (例如: my_multiselect), 另外在 mounted...