# 工作記錄4之 laravel 的測試用例
> #### 如果在laravel中完成一個多對多關系的測試用例 ?
### 為了完成一個多對多關系的表我們需要先建立這么一個關系表,這三個表的結構如下
> #### article表
| 字段 | 類型 | 描述 |
| ------------ | ------------ |
| id | int | 主鍵自增 |
| title | varchar | 文章標題 |
| body | varchar | 文章內容 |
> #### tag 表
| 字段 | 類型 | 描述 |
| ------------ | ------------ |
| id | int | 主鍵自增 |
| tag_name | varchar | 標簽名稱 |
> #### tag_mapping 表(中間表)
| 字段 | 類型 | 描述 |
| ------------ | ------------ |
| id | int | 主鍵自增 |
| article_id | int | 文章外鍵 |
| tag_id | int | 標簽外鍵 |
> #### 添加軟刪除操作 , 在 moudel 下面的 中間表添加以下內容:
use SoftDeletes
protected $datas = ['deleted_at']; //目的是向中間表添加字段
> #### 接著我們要想數據表中添加字段 deleted_at, 如果一開始已經在設計表添加就不用
php artisan make:migration create_add_delete_at_to_tag_mapping_table
> #### 創建好文件后在 migrations 下面會多出一個.php文件, 點開后我們寫入下面的內容
//這個文件有兩個函數, 而且我們使用了 after函數讓 delete_at 字段在 tag_id 的后面
```php
public function up()
{
Schema::table('tag_mapping', function (Blueprint $table) {
//
$table->dateTime('deleted_at') //添加的字段
->default(null) //設置默認值為空
->nullable() //可為空字段
->after('tag_id'); //在什么字段的后面添加到
});
}
//如果數據進行誤操作可以進行回滾 rollback
public function down()
{
Schema::table('tag_mapping', function (Blueprint $table) {
//
$table->dropColumn('deleted_at'); //只回滾下面的這一列
});
}
```
> #### 如果我們想要更改表名稱
```php
public function up()
{
Schema::table('student_salesman_manage', function (Blueprint $table) {
Schema::rename('student_salesman_manage', 'student_salesman_change_log');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('student_salesman_change_log', function (Blueprint $table) {
Schema::rename('student_salesman_change_log', 'student_salesman_manage');
});
}
```
> #### 上面我們定義了 down 方法進行 rollback , 我們看看如果進行回滾操作 (它會執行上一條 migrate 的操作)
php artisan migrate:rollback
> 接下面我們需要對數據進行測試用例,看看數據是否能寫進入

> #### 上圖我們建立了一個 Factory 和一個 Feature 的文件夾,并且我們還在類下面創建了 幾個工廠類。最重要的屬 BaseFaactory
> ##### 我們進去看看類如何實現, 我會把每一行的注釋寫在下面
```php
namespace Tests\Factory; //名字空間根據實際情況下
class BaseFactory
{
protected $model = []; //存放模型類去調用
public function clear() //特色一執行就去刪除數據庫中的文件
{
foreach ($this->model as $m) {
$m::query()->forceDelete(); // 用的 laravel 中的真實刪除數據庫的數據
}
return $this;
}
}
```
>#### 接著我們定義其他的類
//文章的工廠測試類
class ArticleFactory extends BaseFactory //繼承我們的父類
{
protected $model = [Article::class];
public function __construct()
{
}
public function create($title , $body){
return \App\Article::query()->create([
'title' => $title,
'body' => $body,
'user_id' => 1
]);
}
}
------------
//標簽的測試類
class TagFactory extends BaseFactory
{
protected $model = [Tags::class];
public function __construct()
{
}
public function create($tagName){
return \App\Tags::query()->create([
'tag_name' => $tagName
]);
}
}
------------
中間表的類
class TagMappingFactory extends BaseFactory
{
protected $model = [Tag_mapping::class];
public function __construct()
{
}
public function create($article_id , $tag_id){
return \App\Tag_mapping::query()->create([
'article_id' => $article_id,
'tag_id' => $tag_id,
]);
}
}
> #### 萬事俱備只欠東風, 我們在剛剛創建的 Feature 下面創建一個測試類, 但是我們這里是在 Unit 文件下面做單元測試
class ExampleTest extends TestCase //TestCase 類在下面
{
//分別聲明三個表的工廠類
protected $tagFactory;
protected $articleFactory;
protected $tagMappingFactory;
protected function setUp(): void
{
parent::setUp(); // TODO: Change the autogenerated stub
}
}
------------
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
}
------------
#### 下面我們需要添加一個多對多的關系, 目前有幾種但是對應的關系, 這個方法是定義在 article 模型中
public function hasTags(){
// dd(1);
// return $this->belongsToMany('App\Tags','tag_mapping','article_id', 'tag_id');
// $this->morphToMany() //多態
return $this->hasManyThrough(Tags::class, Tag_mapping::class, 'article_id', 'id', 'id', 'tag_id');
// return $this->belongsToMany('App\Tags','tag_mapping','article_id', 'tag_id');
}
> #### 注釋: 不難看出 hasManyThrough 方法是通過中間表去查找, 兩個id 分別對應第二主鍵 和本地主鍵
[========]
> ### 現在我們回到器測試代碼上面, 編寫下面的類方法
namespace Tests\Unit;
class ExampleTest extends TestCase
{
protected $tagFactory;
protected $articleFactory;
protected $tagMappingFactory;
protected function setUp(): void
{
//這里調用了父類的setUp方法在執行測試的時候會自動執行下面
parent::setUp();
// $this->tagFactory = resolve(TagFactory::class)->clear(); //全局輔助函數 resolve 來解析
// $this->articleFactory = resolve(ArticleFactory::class)->clear();
// $this->tagMappingFactory = resolve(TagMappingFactory::class)->clear();
//另外一種方式和上面是一樣的
// $this->tagFactory = new TagFactory();
// $this->tagFactory->clear();
}
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest() //主要的測試代碼
{
$tagName = 'testTag';
$tagArr = array(); //提交上面的標簽
//模擬一些標簽
for ($i = 0; $i < 10; $i++){
array_push($tagArr,$this->tagFactory->create($tagName.$i)->id);
}
$article = $this->articleFactory->create('hello', '123456'); //隨便測試一個用戶的ID
//我們向中間表隨便存兩條紀律
$this->tagMappingFactory->create($article->id, $tagArr[0]);
$this->tagMappingFactory->create($article->id, $tagArr[3]);
//這里用query方法可以找到后面的find方法用于關系表查詢, 主要方法還是 hasTags() 上面我們已經在模型中寫出這里只是調用
$tags = Article::query()->find($article->id)->hasTags->toArray();
$requestTags = [$tagArr[4], $tagArr[5]]; //模擬用戶存的數據
$tagNeedAdd = array_diff($requestTags, $arr); // 需要添加tag
$tagNeedDelete = array_diff($arr, $requestTags); // 需要刪掉tag
if(!empty($tagNeedAdd)){
foreach ($tagNeedAdd as $tag){
Tag_mapping::query()->create(['article_id'=>$article->id, 'tag_id' => $tag]);
}
}
if(!empty($tagNeedDelete)){
foreach ($tagNeedDelete as $tag){
Tag_mapping::query()->where('article_id', $article->id)
->where('tag_id', $tag)
->delete();
}
}
dump($arr, $requestTags); //arr = "表里的標簽, $requestTags = 提交上來的標簽"
dd($tagNeedAdd, $tagNeedDelete); // $diffArrTag = 添加的標簽 , $diffArrTag2 = 刪除的標簽
// $this->assertEquals($tagName, $tags[0]['tag_name']);
}
}
------------
> #### 回到代碼上我們主要用了一個 array_diff() 函數來進行做差比較用戶上傳的標簽和本身有的標簽,如果需要刪除我們就循環去一個一個刪除
> #### 如果需要添加我們就一個一個去添加這樣就完成了基本的流程而且邏輯比較清晰.
------------
>#### 完成后我們看看效果

------------
> ### 總結: 我們使用 laravel 完成了基本的關系表操作,當然還有更復雜的操作,不過基本的流程大概就是這樣。