你替controllers寫測試嗎?

前陣子看到替controllers寫測試的文章。摸了一陣子還是沒掌握。

跑去Reddit發問,得到好多回應。

因為獲益良多,所以在此翻譯討論串內容,和各位分享。

你替controllers寫測試嗎?

嗨!
我最近在學Domain Driven Design跟幾個測試技巧。
我替repositories、services、entities、factories寫各種測試來確保程式品質。
因為網站越來越依賴前端技術跟AJAX,我覺得也替controllers寫測試比較好。
但我又覺得測試controllers根本沒從controllers類別裡面initiate或是call任何東西。
只是寫一個測試去模擬對應controller action的內容而已。
這也能叫測試?這能帶來多少好處?
你們替controllers寫測試嗎?
誰能指點迷津?
感激不盡!

dohpaz42

軟體測試是一個超複雜大怪獸。每個人測試的方法都差很多。電腦理論都這樣的。
先簡單回答你的問題:測試所有東西。
完整回答比較複雜。我試著整理出來,請見諒。
測試有分很多種:unit tests,integration test,regression tests,feature tests,等等。根據測試的對象不同,你會使用不同的測試。
舉例來說,當你寫一個model,基本上你會用到unit test。你會替每個method可能走過的每條道路都寫測試。
如果你對這觀念不熟,就這樣想:若你的method會製造X種副作用,那你就得替這X種副作用寫測試。正確執行、失敗執行的情況都算。
unit test的定義就是測試application中的一個特定單位。如果測試碰到其他models或程式碼,你就mock那些物件(利用dependency injection)來忽略它們。若有個database model會寫入資料到database,我需要去管database layer是否產出正確的output嗎?大概不需要。你只需要注意正在測試的部份系統(System Under Test, SUT)會針對database的output正確反應。這代表你應該要對所有會影響系統運作的database output寫測試。只需要pass-through、proxy(或是任何不會造成副作用的方式)這樣的簡單方法,就能有效忽略那些code的可能走法。譬如說:

function fooProxy() {
    return $this->getDatabase()->query('SELECT * FROM foo');
}

這個method只是把application中另一層layer的結果丟回去,而那層layer你應該早就測過了。所以你不需要替fooProxy寫測試。

很好,感謝你保持耐心。正式回答前,我想先釐清這些。

controllers通常會與application中的多個部份一起合作。它們透過front controller、routing機制來運作,並用上models、views。很少會有專屬於controller的logic(這不代表controllers不需要測試)。要是有的話,那就測吧。測試在不同狀況下,他們與其他code的互動是否一如預期。這需要integration tests才行。

一個integration test不怎麼在乎系統運作的細節。它只在乎:如果XYZ放進去,那麼結果應該要是ZYX。拿REST API舉例:如果我丟一個GET request到/resource/a,我應該得到一個200 response,也許再加上body content。這樣算通過測試。如果我把幾個變數跟request一起丟出去,結果得到400 response,那就沒通過測試。不在乎系統是否碰到資料庫還是檔案什麼的,只在乎response是不是200。

正式回答你的問題:對,所有東西都應該測試。你會寫很多不同的test。這可能很瑣碎,甚至很無聊。但是當所有的tests都通過的時候,你終究會理解價值何在。

whtevn

我們會替controllers寫測試。但是integration tests,不是unit tests。我們會測所有api endpoints,然後記下他們在特定input下的各種response。我們也會測資料庫是否正確更新。這樣算是驗證了整個application。
這跟unit test要求徹底獨立的概念不同。從TDD的觀點,先寫unit tests是很合理。但是先寫integration tests更棒。它能協助你弄清楚功能,又不需要像unit test那樣有個逐漸遞增的feedback loop。

我們公司強調integration tests而非unit test。我們規定每個endpoint都必須要有integration test,而unit tests由工程師自行決定。確認特定的method運作正常當然重要。但我個人認為,確認所有API endpoint都符合規格,更重要。

我寫了一個integration test套件。我放在github上,但目前只有幾個範例有寫說明。

https://github.com/whtevn/routest/blob/master/sample/tests/succeed/routest-test.js

也放在npm上了。它不是外掛,只是一個免費工具。好像也算外掛。隨便啦。

https://www.npmjs.com/package/routest

mrargh

我們通常對models寫unit test,然後用web driver integration tests 測controllers – 這樣就都測到了。

philsturgeon

沒事幹才寫吧…但我總是有事幹。
我對libraries,models之類的寫unit test,然後用integration test測其餘部份。
對controllers寫unit test不是壞事,但不需要優先考慮。

WishCow

我不寫。我盡量在controllers內少放logic,把事情都交給services跟models。替controllers寫測試只是再模擬一次他們的互動而已。那樣是可以驗證參數與結果的關係,但對我來說太花功夫了,不划算。

chadcf

如果是API application我才替controllers寫測試。那些算functional tests。web介面的話,我會讓controllers內容少到不需要寫tests。大原則是讓controller action內容少到10行以內,只讓它建立物件、呼叫物件方法,然後回傳response。