分類彙整:programming

一堆dependency怎麼測試?把替身叫出來。

我任職的翻譯公司需要線上報價功能。

其中,上傳文件到專案的功能,會同時計算字數、備份檔案到遠端。

程式碼大概是這樣:

upload($file);
        
        $reader = new Reader();
        
        $words = $reader->count($file);
        
        $document = new Document();
        
        $document->remote_file_id = $id;
        
        $document->words = $words;
        
        $document->save();
    }
}

由於這是個核心功能,我希望多寫幾個測試確保它的運作正常。

這樣的function該怎麼測試呢?

我是用Laravel + PHPUnit開發。首先,寫個基本測試,確保function沒有打錯變數名字、語法、不會有莫名exception噴出來:

addDocument($file);
                
        $this->assertGreaterThan(0, $project->words);
        
        $this->assertNotNull($project->remote_file_id);        
    }
    
}

問題來了,這個測試跑得非常慢。因為:

  • 它真的把檔案上傳到資料備份主機
  • 它真的用Reader數字數

我只想確認基本邏輯正常,並不在乎備份、計算字數功能是否正常。(那兩個應該另外測試。)

該怎麼辦呢?

是時候叫替身出來幫忙了!

Test Double

Test Double,我翻譯為「測試替身」,是Gerard Meszaros在2007年提出來的名詞。
它是軟體測試的常見手法之一,會做出類別的替身供測試使用。
又可細分為Fake, Mock, Stub, Dummy… 等等。
我們先不管差別何在,想辦法解決眼前的狗屁就好!

首先,我們得將原本的function改寫,把dependency改用參數傳入:

class Project
{
    public function addDocument($file, $remote, $reader){        
        //....
        //....

        // $remote = new RemoteBackup();
        
        $id = $remote->upload($file);
        
        // $reader = new Reader();
        
        $words = $reader->count($file);
        
        $document = new Document();
        
        $document->remote_file_id = $id;
        
        $document->words = $words;
        
        $document->save();
    }
}

呼叫這個function的相關code,記得改寫成:

$project->addDocument($file, new RemoteBackup(), new Reader());

重點來了,測試怎麼寫?怎麼做出測試替身?

我想要一個不管收到什麼,一律回傳5555當作假id的$remote替身;
還要一個不管收到什麼,都說它計算出6666個字數的$reader替身。

PHPUnit本身就支援Test Double,只要這樣即可:

class ProjectTest extends TestCase {
    
    public function testAddDocument()
    {
        // 先設法做出測試用的$project與$file
        // ...

        $remote = $this->getMockBuilder('RemoteBackup')
                     ->getMock();

        $remote->method('upload')
             ->willReturn(5555);
             
        $reader = $this->getMockBuilder('Reader')
                     ->getMock();

        $reader->method('count')
             ->willReturn(6666);
             
        $project->addDocument($file, $remote, $reader);
                
        $this->assertGreaterThan(0, $project->words);
        
        $this->assertNotNull($project->remote_file_id);        
    }
    
}

搞定!
成功避開遠端備份與計算字數的dependency!

測試完基本邏輯,接著可以寫各種不同的測試,去檢驗你想確認的部份,
全都不會真的上傳檔案到備份主機與計算字數!

利用test double的手法,你可以避開一大堆的dependency,測試到你原本以為不可能測試的地方。

很神奇吧!


Q&A

Q1: 把物件當dependency傳入真的好帥喔,這招叫什麼?

這招只是dependency injection的一種。

為了讓code能夠被測試,這招很常用到。

Q2: function傳入檔案$file又傳入$remote跟$reader。我怎麼覺得醜到爆啊?

這樣寫確實很醜。

通常物件dependency是先在constructor傳入,再從function內拿來用:

        // $id = $remote->upload($file);
        $id = $this->remote->upload($file);

        // $words = $reader->count($file);
        $words = $this->reader->count($file);

你常常看到的這種pattern就是在幹這件事:

$stuff = new GoodClass(new ClassOne(), new ClassTwo());
    public function __constructor($dependencyOne, $dependencyTwo)
    {
        $this->dependencyOne = $dependencyOne;
        $this->dependencyTwo = $dependencyTwo;
    }

但是我懶得改寫所有相關code,而且Laravel的Eloquent建構式又很難搞定物件dependency。

學到精神就好,寫法隨機應變。

Q3: 我還滿好奇,你提到的翻譯公司網站長怎樣?

http://wordcorp.net/

(Photo via AlmaArte Photography, CC licensed. )

你替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。

MVC是一個巨大誤會

我是web工程師,從剛開始學MVC就深感困惑:

  • 怎麼每個地方說的MVC都不太一樣?
  • 有些文章講的MVC,跟我正在用的MVC,怎麼像完全不同的東西?

Model、Controller、View三者到底如何互動?真是一個定義不明、含糊不清的名詞。

這讓我研究了很久。最後,發覺它是一個嚴重的誤會。

這個誤會導致了學習和溝通上的代價,請聽我娓娓道來。

哪些是MVC?

web領域,不論前端(client-side)、後端(server-side)、不論什麼程式語言,幾乎所有framework都自稱、或被認為是「MVC」。

有哪些呢?

前端:Backbone.js、AngularJS、Ember.js…

後端:Ruby on Rails、CodeIgniter、Laravel、Django…

真的是這樣嗎?它們全都是MVC嗎?

MVC是什麼?

該怎麼定義MVC呢?

我們先來看看維基百科怎麼說:

MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、檢視(View)和控制器(Controller)。

嗯,跟大家說的一樣。我們繼續往下看:

模型(Model) 用於封裝與應用程式的業務邏輯相關的資料以及對資料的處理方法。「 Model 」有對資料直接存取的權力,例如對資料庫的存取。「Model」不依賴「View」和「Controller」,也就是說, Model 不關心它會被如何顯示或是如何被操作。但是 Model 中資料的變化一般會通過一種重新整理機制被公布。為了實作這種機制,那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式))

看起來有些陌生,整段描述跟你的web開發經驗完全不同,對嗎?

最大的疑問來自這句:

那些用於監視此 Model 的 View 必須事先在此 Model 上註冊,從而,View 可以了解在資料 Model 上發生的改變。(比較:觀察者模式(軟體設計模式))

後面直接叫你去看觀察者模式(observer pattern)。

問題來了:你有在view跟model之間實作observer pattern嗎?

也就是說,你的Model在資料改變之後,能主動通知View嗎?

沒有的話,就根本不符合MVC的定義。

全都不是MVC?

我們現在發現MVC有observer pattern這個必要條件了。

事情嚴重了起來。

client-side framework或許能夠符合這個條件。

Backbone.js官網範例來說,我們可以這樣在Model上註冊:

book.on({
  "change:title": titleView.update,
  "change:author": authorPane.update,
  "destroy": bookView.remove
});

它的確實作了observer pattern。

但server-side framework呢?

你的Model如何能在發生改變之後去「主動通知」View?

你平常開發web哪有用到server push的技術?

所有server-side framework,從Ruby的Rails;PHP的CodeIgniter、Laravel;到Python的Django,他們全都不是MVC。

它們實作的,是昇陽電腦在1998年提出的「Model 2」。

什麼是Model 2?

Model 2名氣不大,在維基百科連中文條目都沒有。我們看看英文條目怎麼講

Model 2 is a complex design pattern used in the design of Java Web applications which separates the display of content from the logic used to obtain and manipulate the content.

In a Model 2 application, requests from the client browser are passed to the controller. The controller performs any logic necessary to obtain the correct content for display.

它針對web而設計,讓controller進行必要的程序之後,將資料塞進view去呈現。

正是我們server-side框架在做的事情。

也就是說,server-side目前只能實作Model 2;client-side可以實作Model 2,也可以實作MVC。

巨大的代價

web工程師最常碰的就是client-side跟server-side框架。結果整個業界把MVC跟Model 2混為一談,都說成MVC。

這帶來了什麼後果呢?

MVC變成一個從初學者到業界工程師,永遠說不清楚、下不了定義的名詞。

這件事對於學習和討論,造成了非常巨大的成本。(參考下方的Q1和Q2)

那該怎麼辦?

下次有初學者詢問「什麼是MVC」的時候,怎麼回答才不會害他回家之後「越查資料越混亂」?

Rails is not MVC的作者提出了三種解決方法:

第一個方法是聲稱MVC已經從原始意義改變了,Model 2也可以稱為MVC。如此一來,我們可以用「傳統MVC」或「真MVC」來描述原始的MVC。這是現在普遍的作法,但我不認為改變定義是一個好主意。這幾乎是越搞越亂。

第二個方法是到處推廣Rails其實是Model 2,MVC就留給…MVC吧。這很困難,但至少能保持定義不變。

第三個是直接忽略這些混亂。管它那麼多?

我個人覺得MVC這個詞已經沒救了,不管怎麼解釋都會帶給別人混亂。

當對方同時學習client-side跟server-side時,混亂更強烈。

我選擇這樣回答:


MVC有分很多種喔!網路上全部沒寫清楚,你一定看不懂。
沒關係,你只要知道View可以抽出來就好。
C跟M先別管,你先隨便瞎搞吧。


Q&A

Q1: 怎麼可能各大server-side framework都搞錯?

確實有人腦袋清醒得很,它就是Python的Django。

Django的官方文件內根本沒有「Controller」這個名詞。

看看Django官網的常見問題

Q: Django似乎是一個MVC框架,但你們把Controller命名為「View」,把View命名為「Template」。你們幹嘛不用標準命名啊?

A: (前略)…如果你真的很想要一個縮寫,你就說Django是一個MTV框架吧。Model、Template、View。這樣分比較有道理些。

Django不想變成搞亂MVC的幫凶,只好委屈地又發明了一個名詞「MTV」。

Q2: 那client-side框架有受影響嗎?

有。client-side框架也必須為MVC巨大誤會浪費一堆時間解釋。

看看Backbone.js官網的常見問答

Backbone跟「傳統MVC」的關聯何在?

(上略)
…我們來比較一下Backbone跟像是Rails這種server-side MVC框架的差別…
(下略)

Backbone實作了「傳統MVC」,卻被迫用「傳統MVC」來描述server-side的Model 2,然後花一堆篇幅解釋。

Q3: 知道Model 2的存在又如何?我現在依然一片混亂!

沒錯,Model 2跟MVC都用到Model、Controller、View三個名詞,所以看起來類似。

但是,我們不應該再把時間花在思考「MVC怎麼如此難懂」。

我們討論的重點,應該是「如何分辨MVC與Model 2」、「在server-side如何實作Model 2才漂亮」、「在client-side實作MVC跟Model 2的優劣分別何在」。

Q4: 好,那你現在回答我,「如何分辨MVC與Model 2」?

OK,就讓我拋磚引玉一下。
分別談談Model、View、Controller吧:

View

  • Model 2: 不具有行為,只是等別人塞資料進去的模板(template)。
  • MVC: 具有監視Model的行為,並以此去改變呈現(presentation)。

兩種View有沒有很像?跟張飛、岳飛一樣像。

看看Backbone.js官網的View範例。你server-side的View哪是長這樣?

var DocumentRow = Backbone.View.extend({

  tagName: "li",

  className: "document-row",

  events: {
    "click .icon":          "open",
    "click .button.edit":   "openEditDialog",
    "click .button.delete": "destroy"
  },

  initialize: function() {
    this.listenTo(this.model, "change", this.render);
  },

  render: function() {
    ...
  }

});

Controller

  • Model 2: 接收請求與參數,轉交給Model處理,再把結果(最新的資料)塞進View。
  • MVC: 接收請求與參數,轉交給Model處理。沒其他事了。

兩種Controller有沒有很像?跟小狗、熱狗一樣像。

MVC的View跟Model 2的Controller可能還比較像一點。(隨便說說,千萬別這樣類比。)

Model

  • Model 2: 接收Controller傳來的參數,回傳結果。
  • MVC: 接收Controller傳來的參數,將結果通知View。

Model倒是有些類似。

總之,Model 2跟MVC除了三個部份的名字一樣之外,沒什麼關聯了。

Q5: 我決定徹底鑽研MVC跟Model 2的定義了。給我一些延伸閱讀?

MVC與Model 2的變異與結合

Rails is not MVC

Django appears to be a MVC framework, but you call the Controller the “view”, and the View the “template”. How come you don’t use the standard names?

How does Backbone relate to “traditional” MVC?

Model-View-Confusion part 1: The View gets its own data from the Model(2015-2-28新增。謝謝網友「王兲玐」的分享。)


一些社群的看法(2015-2-28)

附上幾個社群的連結,裡面有許多很棒的討論。

歡迎提供更多連結給我。

JavaScript.tw

PHP 台灣

Python Taiwan

劉依語 (Mosky)


良葛格針對本文的延伸討論(2015-4-19補充)

技術名詞紛爭多

(Photo via  Julia Wolf, CC License)

提高React component重用性:Composition

這陣子寫React的components,常常很快就完成了期待的效果。
但是總覺得沒有重用性、無法從application獨立出來。

我想將元件中可重用的部份獨立出來,成為基本元件,接著針對application另寫元件去擴充這些基本元件。
有辦法讓一個component去繼承另一個component嗎?似乎不行。

我首先在開發React-Lightbox時遇到了上述問題。

我想製作一個Lightbox效果的plugin,提供Lightbox、LightboxTrigger、LightboxModal三個component,讓developer自訂觸發Lightbox的按鈕、Lightbox框框內的內容。

用法類似這樣:

        React.renderComponent(
            
                
                    
                
                
                    
                
            ,                
            document.getElementById('react-canvas')
        );

範例中的ToggleButton跟MyPanel是由developer自訂。

該怎麼達成這個效果呢?

毫無頭緒之下,我寫出像這樣的code:

        for (j in this.props){
            if (j !== 'children'){
                this.props.children.props[j] = this.props[j];                
            }
        }
        for(i in this.props.children){
            this.props.children[i].props.openLightbox = this.openLightbox;
            this.props.children[i].props.closeLightbox = this.closeLightbox;
            this.props.children[i].props.setLightboxState = this.setLightboxState;
            for (j in this.state){
                this.props.children[i].props[j] = this.state[j];                
            }
        }

簡單地說,就是硬著頭皮傳遞某些props、達到類似「繼承」的「擴充」效果。

眼前的問題解決了,但是tomchentw指出這樣寫法的問題所在(感謝指點):

‘At my first glance it looks pretty magic.’

該怎麼辦呢?

我最後終於在這篇SO的問答找到了一線希望:

Extending React.js components

由component去wrap住基本component來達到擴充的效果,然後利用JSX Spread Attributes的幫助來傳遞props。

利用這些,我開發出了第二個React的plugin:Tag Manager

所謂「使用者自訂component去擴充基本component」的code,大概長這樣:

/** @jsx React.DOM */

var MyCustomTagManager = React.createClass({

    addTagCallback: function(tagName, setStateCallback){
        alert('Add a tag! Maybe you should send an ajax!');
        setStateCallback({id: 99, name: tagName});
    },

    removeTagCallback: function(tag){
        alert('Remove a tag! Maybe you should send an ajax!');
    },

    render: function(){
        return(
            
        )
    }

});            

反省

再看一眼,發現前面的寫法跟後面的寫法其實都是「用component去包住component」,意義上其實類似。
(都是叫composition?我理論沒學好,對專有名詞不熟@@”)

後面的寫法看起來更explicit了,maintainability跟readability應該也更高了吧?
您覺得呢?

別急著用那些新奇玩意兒

一個正要用PHP開始工作的朋友焦慮地問我:同事說我應該要用NetBeans。它是一種叫IDE的東西?我查了資料,發現它提供很多功能:字詞自動補完、全文搜索…等等。但我裝了之後覺得好難用,壓力好大!

我看了看他的電腦:他用Ubuntu當作業系統、Sublime當編輯器、git做版本管理、Apache當伺服器。這些工具夠他解決幾乎所有問題了。

我問他是否看得懂那些文章提到的「優點」在說什麼?「看不太懂。」
我問他同事有否解釋要拿NetBeans來做什麼?「好像可以設定連線、好像很方便、好像歐洲人都用NetBeans。」

這讓我回想起自己踏入這行後困惑很久的煩惱:為了一個別人大力提倡、但是自己看不出來有什麼用的東西而感到焦慮。

對於這種看到新工具、新觀念、新知識而產生的焦慮,我最後養成了一種態度:隨便看看、知道有這個東西存在就好了。之後遇到瓶頸、困難時再回頭,看看它們能否解決我的麻煩。

別浪費時間煩惱這個東西到底能解決哪個問題了;也別為了這種困擾感到挫折、覺得自己很笨。

課本上的一個名詞、一個Design Pattern、一個時髦的工具、一個軟體開發方法、一個軟體測試技巧、一個抽象化思考方法、一個程式語言的某個語法…。看不出來有什麼用,就別急著用。

時間一久,你還可能發現一件更難相信的事情:你從頭到尾都是對的。

那些新奇的狗屁也許根本就有某些缺點。你第一眼就看到了,但是所有人都不去提。

國王裸體在街上走!你的眼睛說不定從來沒看錯。

我不是在說學習沒有價值,我想說的是這兩件事:

* 碰到麻煩再去學習進階的,很多時候這樣才學得透徹。

* 所有東西都可能有缺點、或是只在某些情況適用,也許你只是很早就察覺缺點。

我再換句話說吧:

當你為了新奇玩意兒感到焦慮的時候,

或許,學習它的時候未到;

又或許,那東西真的沒什麼屁用。

(Photo via Alessandra, CC licensed.)

PHP這個程式語言

PHP是web領域的知名程式語言,沒有資訊背景的人可能也聽過這個名字。

不像其他語言在設計上有所堅持,PHP只堅持找到解決web問題的最短路徑

這個語言的內容雜亂,會飽受批評完全可以理解。它除了專心「把事情搞定」之外,幾乎什麼都不管。

我從事PHP開發以來,發現它門檻低、使用者多、允許多種寫法、允許不同程度的人用自己的方式開發。

也因為社群龐大,PHP產出的程式碼平均品質低、開發者程度參差不齊。

那麼PHP到底解決了多少人的web問題呢?

關於PHP的缺點、批評,都是事實,但PHP社群知道自己在幹什麼。

正準備開始,不知去哪找人討論嗎?到批踢踢的PHP版、Facebook的PHP台灣發問吧!

學習了一陣子,覺得很多文章過時、想參考業界流行的開發慣例嗎?看看PHP: The Right Way吧!

對英文能力有自信,想跟全球的網友一起討論嗎?來Reddit的PHP版吧!

工作一段時間,想找些高品質的函式庫參考嗎?逛逛the PHP League吧!

好奇才華洋溢的PHP工程師到底有多少生產力嗎?看看symfony的Fabien Potencier的commits吧!

想自學寫網站、親手搞定某個web的問題嗎?試試看PHP吧!

軟體測試:我不在新創公司用mocks

為了確保軟體品質,多少會寫點測試。

常會看到文章提倡使用mocking的手法,說是要獨立測試程式的各個部份比較好。

我觀察這個手法很久了,遲遲不願意真正應用。

我總覺得mocking讓測試的code變得很冗長、不好讀。

最近終於發現,其實不少人跟我抱持同樣看法。

這邊舉Testing on the Toilet: Don’t Overuse Mocks一文中提到的例子。

不使用mocks、寫出有相依性的test長這樣:

public void testCreditCardIsCharged() {
  paymentProcessor = new PaymentProcessor(creditCardServer);
  paymentProcessor.processPayment(creditCard, Money.dollars(500));
  assertEquals(500, creditCardServer.getMostRecentCharge(creditCard));
}

使用mocking達到測試環境獨立的test長這樣:

public void testCreditCardIsCharged() {
  paymentProcessor = new PaymentProcessor(mockCreditCardServer);
  when(mockCreditCardServer.isServerAvailable()).thenReturn(true);
  when(mockCreditCardServer.beginTransaction()).thenReturn(mockTransactionManager);
  when(mockTransactionManager.getTransaction()).thenReturn(transaction);
  when(mockCreditCardServer.pay(transaction, creditCard, 500)).thenReturn(mockPayment);
  when(mockPayment.isOverMaxBalance()).thenReturn(false);
  paymentProcessor.processPayment(creditCard, Money.dollars(500));
  verify(mockCreditCardServer).pay(transaction, creditCard, 500);
}

不但可讀性下降、還得多花時間撰寫mocks。

在新創公司,我不寫mocks的原因有兩個:

一、新創公司,軟體越快上線越好。花時間寫mocks,太不划算了。
二、既然軟體架構不大,反正任何一個測試fail,整個application都會壞掉。tests無論如何都得全部pass才行。

想來想去,只有在用到第三方library或API、不方便在tests中真正呼叫時,才會想用mocks。

話雖如此,有看法認為應該用interface再加上一層薄layer來解決test第三方library的問題:

Test Smell: Everything is mocked

如果是在開發framework就另當別論了。要確實分別測試各個部份才行。

Laravel的tests

話雖如此,也許更資深、接觸的軟體架構更大之後,我會改變想法也說不定。