PHPUnit and Dependency Injection

在寫程式的時候,Unit Test 是頗為重要的一環,Working Effectively with Legacy Code 裡面甚至提出了一個概念:「Legacy Code is Code Without Tests」

呃,所以,還是直接看 code 好了…

以下示範 Dependency Injection 的寫法:

以本例來說: foo 類別裡面的 foo_run() 方法是無法被測試的,因為在這個方法裡面有使用 new bar() 建立了一個 $b 物件,並且執行 $b->bar_run() 。我們可以看出來這個 $b->bar_run() 會呼叫 Web Service 。所以當我們執行到 foo_run() 的時候,可能會因為環境的因素而無法測試。

這種程式之間的相依性問題往往會對 Unit Test 造成很大的困擾。如果沒有排除程式之間的相依性,就很容易會讓我們在進行測試時卡住。(除了呼叫 Web Service 以外,也可能是存取資料庫、寫檔案、或是做其他動作。而這些動作可能會因為不同的環境而讓程式無法執行。)。

所以,既然 bar 類別並不在我們需要的測試範圍內,我們就可以透過 Dependency Injection 來降低程式之間的相依性。

假設我們只要測試 foo 類別裡面的 foo_run() 方法,而不想測試 bar 類別的 bar_run() 方法,那可以改採取以下的策略:

這個方式,讓 $bar 物件得以在外面產生,並且注入到 foo 類別中,從此我們可以依照不同的環境建造出不同的 bar 物件。

要建立一個物件,我們至少要寫好幾行 code 來宣告它才會動,而為了測試寫這些 code 就顯得不太聰明,於是 PHPUnit 提供了 Mock Object 的功能。假設我們在 PHPUnit 中要測試上面的 foo 類別,可以採取以下的方法:

另外也可以針對測試環境做一些調整…

我們可以在 foo 類別中,產生一個新的 get_bar() 方法,當要真正要使用 bar 的時候,再呼叫一次 $this->get_bar() 來產生所需要的物件。

假設之前沒有被 $this->set_bar() 做過 Dependency Injection 的話,就會產生預期中的 bar 物件。

以上是簡單的範例介紹,用簡單的方法改寫原本無法被測試的程式 🙂

Posted in Programming

Leave a Reply

Your email address will not be published.