# 單元測試
> 單元測試是對單獨的代碼對象進行測試的過程,比如對函數、類、方法進行測試。單元測試可以使用任意一段已經寫好的測試代碼,也可以使用一些已經存在的測試框架,比如`PHPUnit`、`Phake`或者`SimpleTest`,單元測試框架提供了一系列共同、有用的功能來幫助人們編寫自動化的檢測單元,例如檢查一個實際的值是否符合我們期望的值的斷言。單元測試框架經常會包含每個測試的報告,以及給出你已經覆蓋到的代碼覆蓋率。
單元測試對于團隊開發而言,好處不言而喻,主要作用包括:
- 盡可能減少開發中的BUG;
- 幫助提高應用(或者接口)設計;
- 協助代碼文檔的編寫;
- 減少開發過程的代碼修改帶來的錯誤;
`ThinkPHP5.0`目前支持`PHPUnit`進行單元測試,這是最常用的單元測試工具,ThinkPHP單元測試擴展還針對控制器和模型的單元測試做了完善支持,添加了額外的單元測試方法和斷言。
`ThinkPHP5.0`核心也是采用`PHPUnit`作為單元測試工具,本文主要講述如何對應用進行`PHPUnit`單元測試,但不打算詳細介紹單元測試的用法,如果你還不了解`PHPUnit`以及什么是單元測試,我們建議首先閱讀了解下[PHPUnit手冊](http://www.hmoore.net/manual/phpunit-book)或者相關書籍。
## 安裝擴展
首先安裝`ThinkPHP5`的單元測試擴展,進入命令行,切換到應用根目錄下面后,執行:
```
<pre class="calibre18">
```
composer <span class="hljs-keyword">require</span> topthink/think-testing
```
```
你不需要單獨安裝`PHPUnit`包,安裝單元測試擴展的時候會自動安裝相關的依賴包,其中就包括`PHPUnit`。
> 由于單元測試擴展的依賴較多,因此安裝過程會比較久,請耐心等待。
安裝完成后,會在應用根目錄下面增加`tests`目錄和`phpunit.xml`文件。
`tests`目錄是應用的單元測試用例目錄,該目錄下面的`TestCase.php`文件是所有單元測試類必須基礎的基礎類,**不能刪除**,`ExampleTest.php`是測試示例文件,可以刪除。
## 運行測試
由于單元測試擴展默認自帶了一個`tests/ExampleTest.php`單元測試文件,內容如下:
```
<pre class="calibre18">
```
namespace tests;
<span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">ExampleTest</span> <span class="hljs-keyword"><span class="hljs-operator">extends</span></span> <span class="hljs-title">TestCase</span></span>{
public function testBasicExample()
{
$<span class="hljs-keyword">this</span>->visit('/')->see(<span class="hljs-operator">'ThinkPH</span>P');
}
}
```
```
表示測試訪問網站首頁的響應輸出內容中是否包含`ThinkPHP`內容,因為我們的默認。
因此我們可以直接在命令行下面運行單元測試:
```
<pre class="calibre18">
```
<span class="hljs-title">php</span> think unit
```
```
如果你是第一次安裝ThinkPHP5的話,或者沒有改動過Index模塊Index控制器的index方法的話,測試是OK的,會顯示類似下面的結果:
```
<pre class="calibre18">
```
PHPUnit <span class="hljs-number">4.8</span><span class="hljs-number">.27</span> by Sebastian Bergmann and contributors.
.
Time: <span class="hljs-number">221</span> ms, Memory: <span class="hljs-number">6.00</span>MB
```
```
如果改過index方法,并且頁面輸出內容沒有ThinkPHP的話,測試就會失敗。
```
<pre class="calibre18">
```
PHPUnit <span class="hljs-number">4.8</span><span class="hljs-number">.27</span> by Sebastian Bergmann and contributors.
F
<span class="hljs-operator">
Time:</span> <span class="hljs-number">230</span> ms, <span class="hljs-string">Memory:</span> <span class="hljs-number">6.00</span>MB
There was <span class="hljs-number">1</span> <span class="hljs-string">failure:</span><span class="hljs-number">1</span>) tests\<span class="hljs-string">ExampleTest:</span>:testBasicExample
Failed asserting that <span class="hljs-string">'Hello,world'</span> matches PCRE pattern <span class="hljs-string">"/ThinkPHP/i"</span>.
<span class="hljs-operator">
D:</span>\www\tp5\vendor\topthink\think-testing\src\InteractsWithPages.<span class="hljs-string">php:</span><span class="hljs-number">67</span><span class="hljs-string">D:</span>\www\tp5\tests\ExampleTest.<span class="hljs-string">php:</span><span class="hljs-number">18</span><span class="hljs-string">D:</span>\www\tp5\vendor\topthink\think-testing\src\command\Test.<span class="hljs-string">php:</span><span class="hljs-number">39</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\console\command\Command.<span class="hljs-string">php:</span><span class="hljs-number">195</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">728</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">188</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">125</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\library\think\Console.<span class="hljs-string">php:</span><span class="hljs-number">94</span><span class="hljs-string">D:</span>\www\tp5\thinkphp\console.<span class="hljs-string">php:</span><span class="hljs-number">20</span>
FAILURES!
<span class="hljs-string">Tests:</span> <span class="hljs-number">1</span>, <span class="hljs-string">Assertions:</span> <span class="hljs-number">2</span>, <span class="hljs-string">Failures:</span> <span class="hljs-number">1.</span>
```
```
> 請始終使用以上命令進行單元測試,而不是直接用`phpunit`來運行單元測試。
## 添加單元測試用例
我們來添加一個控制器類的單元測試文件,為了便于管理,我們按照模塊名創建單元測試的子目錄和應用類庫的目錄結構對應起來,該單元測試文件為`tests/index/IndexTest.php`,內容如下:
```
<pre class="calibre18">
```
<span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setUp</span><span class="hljs-number">()</span></span>{
<span class="hljs-keyword">parent</span>::setUp();
}
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testSome</span><span class="hljs-number">()</span></span>{
<span class="hljs-regexp">$this</span>->assertTrue(<span class="hljs-keyword">true</span>);
}
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tearDown</span><span class="hljs-number">()</span></span>{
}
}</span>
```
```
> 每個單元測試類必須繼承`TestCase`類或其子類,每個單元測試方法必須以`test`開頭。
除了單元測試方法之外,還可以增加一些特殊的單元測試定義方法(但不是必須的)。
`setUp`方法會在每一個單元測試方法之前自動調用,所以該方法可以用于一些測試的初始化,也就是所說的基境(`fixture`),對應還有一個`tearDown`方法會在每個測試方法完成后自動調用,通常用于重置一些測試的數據。
每個單元測試方法必須以`test`開頭,后面的測試方法名稱的命名可以隨意,例如上面的`testSome`方法改成`testSomethingToTrue`并不會影響測試結果。
如果你需要同時對控制器類和模型類做單元測試的話,也可以在模塊目錄下面創建`controller`和`model`子目錄,然后分別創建單元測試文件。
控制器單元測試文件`tests/index/controller/IndexTest.php`
```
<pre class="calibre18">
```
<span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>\<span class="hljs-title">controller</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testIndex</span><span class="hljs-number">()</span></span>{
<span class="hljs-regexp">$this</span>->call(<span class="hljs-string">'GET'</span>, <span class="hljs-string">'/'</span>);
<span class="hljs-regexp">$this</span>->assertResponseOk();
}
}</span>
```
```
測試用例對網站首頁進行了一次GET請求,并判斷是否返回正確的響應。
模型單元測試文件`tests/index/model/IndexTest.php`
```
<pre class="calibre18">
```
<span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">tests</span>\<span class="hljs-title">index</span>\<span class="hljs-title">model</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">tests</span>\<span class="hljs-title">TestCase</span>;
<span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexTest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">TestCase</span></span>{
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">testSearch</span><span class="hljs-number">()</span></span>{
<span class="hljs-regexp">$this</span>->seeInDatabase(<span class="hljs-string">'user'</span>, [<span class="hljs-string">'id'</span> => <span class="hljs-number">2</span>]);
}
}</span>
```
```
> 在測試模型用例之前,請首先確保正確配置了數據庫配置文件。
該測試用例測試數據表`think_user`(假設你的數據表前綴設置為`think_`)是否存在`id`為2的數據。
其實模型的測試用例和控制器的測試用例沒有明確的界限,大多數情況,我們只需要針對控制器類做單元測試即可,同樣,你仍然可以對不同的應用層類庫做單元測試。
除了支持`PHPUnit`自帶的測試方法外,`ThinkPHP`單元測試擴展還封裝了一些額外的方法,我們以后會給你詳細描述。
## 定義單元測試
單元測試最大的工作是使用斷言(`assertion`),所以你會發現PHPUnit自身帶了很多的斷言方法,常用的方法包括:
斷言方法 描述 assertTrue($var) 斷言變量為true assertFalse($var) 斷言變量為false assertEquals($value,$var) 斷言變量為$value assertNull($var) 斷言變量為null assertContains($needle,$var) 斷言變量中包含$needle
- 脕茫隆壟脨貌脩脭
- 脕茫隆壟脨貌脩脭
- 脪祿隆壟祿霉麓隆
- 脪祿隆壟祿霉麓隆
- 露鎂隆壟URL潞脥脗路脫脡
- 露鎂隆壟URL潞脥脗路脫脡
- 脠媒隆壟脟毛脟貿潞脥脧矛脫婁
- 脠媒隆壟脟毛脟貿潞脥脧矛脫婁
- 脣脛隆壟脢媒戮脻驢芒
- 脣脛隆壟脢媒戮脻驢芒
- 脦氓隆壟虜茅脩爐脫茂脩脭
- 脦氓隆壟虜茅脩爐脫茂脩脭
- 脕霉隆壟脛攏脨脥潞脥鹿脴脕陋
- 攏簍1攏漏脛攏脨脥露簍脪氓
- 攏簍2攏漏祿霉麓隆虜脵脳梅
- 攏簍3攏漏露脕脠隆脝梅潞脥脨脼賂脛脝梅
- 攏簍4攏漏脌脿脨脥脳陋祿祿潞脥脳脭露爐脥錨魯脡
- 攏簍5攏漏虜茅脩爐路露脦摟
- 攏簍6攏漏脢盲脠毛潞脥脩茅脰隴
- 攏簍7攏漏鹿脴脕陋
- 攏簍8攏漏脛攏脨脥脢盲魯枚
- 脝脽隆壟脢脫脥錄潞脥脛攏擄氓
- 脝脽隆壟脢脫脥錄潞脥脛攏擄氓
- 擄脣隆壟碌梅脢脭潞脥脠脮脰戮
- 擄脣隆壟碌梅脢脭潞脥脠脮脰戮
- 戮脜隆壟API驢陋路壟
- 戮脜隆壟API驢陋路壟
- 脢廬隆壟脙眉脕卯脨脨鹿隴戮脽
- 脢廬隆壟脙眉脕卯脨脨鹿隴戮脽
- 脢廬脪祿隆壟脌漏脮鹿
- 脢廬脪祿隆壟脌漏脮鹿
- 脢廬露鎂隆壟脭脫脧卯
- Cookie
- Session
- 碌樓脭陋虜芒脢脭
- 脥錄脧帽麓婁脌鉚
- 脦脛錄鎂脡脧麓蘆
- 脩茅脰隴脗毛
- 賂陸脗錄
- A隆壟魯攏錄沒脦脢脤芒錄爐
- B隆壟3.2潞脥5.0脟酶鹵冒
- C隆壟脰煤脢脰潞爐脢媒
- 路盧脥芒脝陋攏潞脩摟脧擄ThinkPHP5碌脛脮媒脠路脳脣脢脝
- 路盧脥芒脝陋攏潞脩摟脧擄ThinkPHP5碌脛脮媒脠路脳脣脢脝