在Day19時搭配RESTful API風格的CRUD做出了一個留言板的功能,但也發現到了每次要確認功能是否正常的時候都需要打開PostMan一個一個功能測試,那如果之後功能做到好幾十個的時候也要這樣手動測試嗎?為了解決這個問題就有了Test這個功能
Laravel本身就內建了兩個Test,直接先下指令來測試一下吧
php artisan test
以/tests/Unit/exampleTest這個範例來說,assertTrue代表的是系統會去測定測試的東西是否為true,那當然這個測試裡面給的值是true所以這個測試就會pass,那可以試著把true改成false來試試看,就會看到測試結果為fail。
接著來介紹一下在/laravel資料夾下的phpunit.xml這個檔案,會記錄著測試的設定
testsuites這個tag設定了要跑哪些test,已預設來說會有Feature及Unit兩個資料夾的test,先試著把Unit所屬的tag註解起來再跑一次,就會看到系統不會去處理Unit這個資料夾裡的test了。
coverage這個tag則是設定要測試覆蓋率的範圍在哪邊,但這次先不介紹這麼深入,稍微記得有測試覆蓋率這個詞就好。
接下來先將無用的測試移除掉吧
rm tests/Feature/ExampleTest.php
rm tests/Unit/ExampleTest.php
接著進入今天的正題,來建立第一個測試,先以Day19的Message的Index列表功能來做示範,下指令建立測試,重點是最後檔名要以Test當結尾,然後因為是Message系列的功能,將他歸納在Message資料夾下面,資料夾不需要另外建立,系統會一併創建
php artisan make:test Message/indexTest
接著就會在Feature/Message/下看到一個indexTest.php的檔案,先將檔案修改成這個樣子
<?php
namespace Tests\Feature\Message;
use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class indexTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$message = Message::create([
'member' => 'verycow',
'message' => 'first test',
]);
$response = $this->withHeaders([
])->json('GET', '/api/messages', [
]);
$response->dump()->dumpHeaders();
$response->assertStatus(200);
}
}
修改的部分有
use DatabaseTransactions;
那就下指令跑看看測試吧,測試結果如下
這樣就有一個最基本的服務是否能正常運作測試啦,當然還有其他的assert能使用,例如回傳資訊是否正確
$response->assertStatus(200)
->assertJson([
[
'member' => $message->member,
'message' => $message->message,
]
]);
這個判定代表著回傳的Json內容是否為當初塞入的測試資料,也可以故意判定錯誤的資料,系統也會反饋錯誤的資料有哪些,在後續除錯上會很方便,更多的案例或是assert可以在官網上找到。
那接著先把後續的create、detail、update、delete完成
create - POST method - 預期回傳success字串
<?php
namespace Tests\Feature\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class createTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$response = $this->withHeaders([
])->json('POST', '/api/messages', [
'member' => 'verycow',
'message' => 'update test'
]);
$response->dump()->dumpHeaders();
$response->assertStatus(200)
->assertSee('success');
}
}
detail - GET method - 預期回傳特定Message內容,注意細節url後面要提供id
<?php
namespace Tests\Feature\Message;
use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class detailTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$message = Message::create([
'member' => 'verycow',
'message' => 'first test',
]);
$response = $this->withHeaders([
])->json('GET', '/api/messages/' . $message->id, [
]);
$response->dump()->dumpHeaders();
$response->assertStatus(200)
->assertJson([
'member' => $message->member,
'message' => $message->message,
]);
}
}
update - PUT method - 預期回傳success字串並且DB內容已修改
<?php
namespace Tests\Feature\Message;
use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class updateTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$message = Message::create([
'member' => 'verycow',
'message' => 'test',
]);
$response = $this->withHeaders([
])->json('PUT', '/api/messages/' . $message->id, [
'message' => 'update test'
]);
// $response->dump()->dumpHeaders();
$response->assertStatus(200)
->assertSee('success');
$this->assertDatabaseHas('messages', [
'message' => 'update test'
]);
}
}
delete - DELETE method - 預期回傳success字串並且DB內資料已被刪除
<?php
namespace Tests\Feature\Message;
use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class deleteTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$message = Message::create([
'member' => 'verycow',
'message' => 'test',
]);
$response = $this->withHeaders([
])->json('DELETE', '/api/messages/' . $message->id, [
]);
// $response->dump()->dumpHeaders();
$response->assertStatus(200)
->assertSee('success');
$this->assertDeleted($message);
}
}
以上就是最基本的Test,要特別說明一下前面有用到的RefreshDatabase與DatabaseTransactions,如果沒有使用這兩個Trait,每一次的測試完畢後測試資料都會留存在DB裡面,但因為這些測試資料實際上不應該保存下來,所以需要利用這兩個Trait來清除資料,那RefreshDatabase與DatabaseTransactions的差別在於,
RefreshDatabase是將整個table移除後再重新建立table,而DatabaseTransactions只會將此次測試的資料移除掉,所以可以自行去測試發現DatabaseTransactions的自增加id會越來越往上加但是使用RefreshDatabase就不會發生這樣的問題,但相對的是使用RefreshDatabase會有比較高的效能成本,到後期測試越來越多時會越來越有感受。
其實測試要做的事情就是
還有點時間來介紹一點進階的Test,首先在列表的時候只建立了一筆測試資料,那如果我想要有多筆測試資料難道要create model三次嗎?此時就要搭配Factory來操作,首先建立一個Message的Factory
php artisan make:factory MessageFactory
就可以在database/factories/之下找到MessageFactory檔案,Message的資訊有id、member、message、created_at、updated_at,其中id、created_at、updated_at已經會有預設值,所以此時只要設定member與message即可,此時搭配faker不是電競選手那個faker,faker的意思是使用大神已經寫好的套件來自動產出假資料,就不必費神在想要塞哪些假資料了,faker能提供的假資料有非常多,姓名、地址、國家、文章、Email等等都有提供,可至該大神的Github找到可使用的假資料格式,接著將MessageFactory改造如下
<?php
namespace Database\Factories;
use App\Models\Message;
use Illuminate\Database\Eloquent\Factories\Factory;
class MessageFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Message::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'member' => $this->faker->name(),
'message' => $this->faker->sentence(),
];
}
}
並將indexTest改造如下
<?php
namespace Tests\Feature\Message;
use App\Models\Message;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class indexTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use DatabaseTransactions;
public function test_200()
{
$messages = Message::factory(3)
->create();
$response = $this->withHeaders([
])->json('GET', '/api/messages', [
]);
// $response->dump()->dumpHeaders();
$response->assertStatus(200)
->assertJson([
[
'member' => $messages[0]->member,
'message' => $messages[0]->message,
],
[
'member' => $messages[1]->member,
'message' => $messages[1]->message,
],
[
'member' => $messages[2]->member,
'message' => $messages[2]->message,
]
]);
}
}
就能非常快速的製造多筆假資料來測試了呢,其實在於寫測試這部分還有非常多的流派,譬如BDD、TDD等等流派,但在深究這些流派到底哪些有用之前我覺得最基本的測試寫好及一定的覆蓋率才是最重要的。
另外一定也會有一個疑問,為什麼不用PostMan測就好還有花這麼多時間另外寫測試?就我個人的理解是因為基本的測試是為了後續的CI/CD所鋪路,但目前這個階段離CI/CD還有點距離,有機會再談。
入職後前三個月都在做的事情就在今天介紹完畢了,謝謝觀看的各位,請記得按讚分享開啟小鈴鐺,你的支持會讓按讚數+1。