多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 工作记录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 > 接下面我们需要对数据进行测试用例,看看数据是否能写进入 ![图片alt](/media/editor/1616577293856_20210324171454439691.png ''图片title'') > #### 上图我们建立了一个 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() 函数来进行做差比较用户上传的标签和本身有的标签,如果需要删除我们就循环去一个一个删除 > #### 如果需要添加我们就一个一个去添加这样就完成了基本的流程而且逻辑比较清晰. ------------ >#### 完成后我们看看效果 ![标签更新](/media/editor/blog1_20210324180917446488.gif "标签更新") ------------ > ### 总结: 我们使用 laravel 完成了基本的关系表操作,当然还有更复杂的操作,不过基本的流程大概就是这样。