上一篇翻译的文章里通过简单的十一步讲述了如何在Laravel中进行测试驱动开发,这是作者关于测试的第二篇文章, 文章中简述了如何在Laravel中进行增删改查的单元测试,本文中的单元测试都是正向测试,之后还会有一篇来讲述如何做反向测试。

正向测试: Positive test 提供合法数据,测试预期被测程序能得到正常执行。

反向测试:Negative test 提供非法的输入数据,测试预期被测程序抛出指定的Exception。

以下是译文:

最近我开启了一个开源电子商务应用项目LARACOM, 使用的是Laravel框架并集成了Rob Gloudeman 的shoping cart 和与其相关的packages。

在开启这个项目时我必须为项目做长远规划和考虑,所以在我脑海中从来就没出现过"我不会用到TDD(test driven development)方法"这个想法,TDD是开发的必选项。为了进行TDD,我需要将项目中的测试用例划分到两个不同的组中,一组是单元测试另外一组是功能测试。

单元测试是用来测试项目中的Model、Repository等等这些类的,而功能测试是看测试代码是否能够正确访问到Controller并断言Controller的行为:是否redirect 到了目标URL、返回了指定的视图或者是跳转并携带着造成跳转的错误信息。

介绍的足够多了,如果你之前没有尝试过TDD,我之前有写进行TDD的基本方法。那篇文章里有介绍TDD的基本概念和开始TDD的基本方法,所以这里不再赘述。

今天我们要做的是为我的项目写Carousel业务的基本CRUD方法。

译者注:文章里作者开推荐了他写的实现repository设计模式的package

Edit: Hey! I created a base repository package you can use for your next project :)

Part I: Positive Unit Testing

从create测试开始

让我们从对create操作的测试开始。

创建/tests/Unit/Carousels/CarouselUnitTest.php 文件

注:通过命令 php artisan make:test Carousels/CarouselUnitTest --unit 创建

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{/** @test */public function it_can_create_a_carousel(){$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository(new Carousel);$carousel = $carouselRepo->createCarousel($data);$this->assertInstanceOf(Carousel::class, $carousel);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['image_src'], $carousel->src);}
}

在这个文件中我们要做的是:

  • 测试respository类是否能够用这些参数在数据库中新建carousel记录。
  • 测试在新建carousel后,返回的carousel对象各属性的值是否与创建时提供的参数值一致。

现在在你的终端中,执行vendor/bin/phpunit(当前工作目录必须是你项目的根目录)。

是否有错误?是的,因为我们还没有创建CarouselRepository.php这个文件,所以会有如下错误

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
E                                                                   1 / 1 (100%)
Time: 700 ms, Memory: 26.00MB
There was 1 error:
1) Tests\Unit\Carousels\CarouselUnitTest::it_can_create_a_carousel
Error: Class 'Tests\Unit\Carousels\CarouselRepository' not found

让我们创建app/Shop/Carousels/Repositories/CarouselRepository.php文件

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\QueryException;
class CarouselRepository
{/*** CarouselRepository constructor.* @param Carousel $carousel*/public function __construct(Carousel $carousel){$this->model = $carousel;}/*** @param array $data* @return Carousel* @throws CreateCarouselErrorException*/public function createCarousel(array $data) : Carousel{try {return $this->model->create($data);} catch (QueryException $e) {throw new CreateCarouselErrorException($e);}}
}

在这个repository中,我们用依赖注入将CarouselModel注入到了其中,因为还没有这个CarouselModel所以在注入这个Model时会抛出一个错误。

我们先来创建: app/Shop/Carousels/Carousel.php

<?php
namespace App\Shop\Carousels;
use Illuminate\Database\Eloquent\Model;
class Carousel extends Model
{protected $fillable = ['title','link','src'];
}

在repository创建完成后,我们将其引入到我们的测试文件中去,像这样:

<?php
namespace Tests\Unit\Carousels;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Repositories\CarouselRepository;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{/** @test */public function it_can_create_a_carousel(){$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository(new Carousel);$carousel = $carouselRepo->createCarousel($data);$this->assertInstanceOf(Carousel::class, $carousel);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['image_src'], $carousel->src);}
}

接下来在此运行vendor/bin/phpunit

Error Again? Yes? 你猜对了。

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
E                                                                   1 / 1 (100%)
Time: 898 ms, Memory: 26.00MB
There was 1 error:
1) Tests\Unit\Carousels\CarouselUnitTest::it_can_create_a_carousel
App\Shop\Carousels\Exceptions\CreateCarouselErrorException: PDOException: SQLSTATE[HY000]: General error: 1 no such table: carousels in /Users/jsd/Code/shop/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:77

测试文件中尝试往carousels表中写入数据,但是这个表现在还不存在。我们接下来通过Migration文件来创建这个表。

在命令行终端中运行:

php artisan make:migration create_carousels_table --create=carousels

编辑迁移文件如下:


<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCarouselTable extends Migration
{/*** Run the migrations.** @return void*/public function up(){Schema::create('carousels', function (Blueprint $table) {$table->increments('id');$table->string('title');$table->string('link')->nullable();$table->string('src');$table->timestamps();});}/*** Reverse the migrations.** @return void*/public function down(){Schema::dropIfExists('carousels');}
}

link字段是可空的,titleimage字段是必须项。

执行迁移文件创建完carousels表后,再次执行vendor/bin/phpunit显示如下:

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 696 ms, Memory: 26.00MB
OK (1 test, 6 assertions)

现在你已经让这个测试通过了,所以只要你在运行vendor/bin/phpunit时这个测试能正确通过,那么你就能认为会应用能成功创建carousel记录。

read测试

现在让我们来测试在创建carousel后,是否能从正确的读取到它。

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{/** @test */public function it_can_show_the_carousel(){$carousel = factory(Carousel::class)->create();$carouselRepo = new CarouselRepository(new Carousel);$found = $carouselRepo->findCarousel($carousel->id);$this->assertInstanceOf(Carousel::class, $found);$this->assertEquals($found->title, $carousel->title);$this->assertEquals($found->link, $carousel->link);$this->assertEquals($found->src, $carousel->src);}/** @test */public function it_can_create_a_carousel(){$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository(new Carousel);$carousel = $carouselRepo->createCarousel($data);$this->assertInstanceOf(Carousel::class, $carousel);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['src'], $carousel->src);}
}

运行测试,在每次我们新建测试后,我们总是预期测试会运行失败,为什么呢?因为我们还没有实现逻辑。如果我们在创建完测试后运行既能得到success的结果,那么证明我们在应用TDD时步骤上出现了错误(TDD 先写测试后编码实现)。

每一个我们新建的测试都要放在测试文件中的头部,因为我们想让新建的测试优先运行

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
E                                                                   1 / 1 (100%)
Time: 688 ms, Memory: 26.00MB
There was 1 error:
1) Tests\Unit\Carousels\CarouselUnitTest::it_can_show_the_carousel
InvalidArgumentException: Unable to locate factory with name [default] [App\Shop\Carousels\Carousel].

在这个错误中显示,测试程序尝试调用了还不存在的模型工厂。

创建文件: database/factories/CarouselModelFactory.php

<?php
/*
|--------------------------------------------------------------------------
| Model Factories
|--------------------------------------------------------------------------
|
| Here you may define all of your model factories. Model factories give
| you a convenient way to create models for testing and seeding your
| database. Just tell the factory how a default model should look.
|
*/
use App\Shop\Carousels\Carousel;
/** @var \Illuminate\Database\Eloquent\Factory $factory */
$factory->define(Carousel::class, function (Faker\Generator $faker) {return ['title' => $faker->word,'link' => $faker->url,'src' => $faker->url,];
});

在此运行phpunit

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
E                                                                   1 / 1 (100%)
Time: 708 ms, Memory: 26.00MB
There was 1 error:
1) Tests\Unit\Carousels\CarouselUnitTest::it_can_show_the_carousel
Error: Call to undefined method App\Shop\Carousels\Repositories\CarouselRepository::findCarousel()

现在找不到模型工厂的错误消失了,意味着现在可以持久化到数据库里了,有些人想让创建对象和持久化的过程能够分开,那么可以将测试代码中的:

$carousel = factory(Carousel::class)->create();

替换成:

$carousel = factory(Carousel::class)->make();

但是现在测试程序中仍然有错误,因为在repository中找不到findCarousel()方法。

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{protected $model;/*** CarouselRepository constructor.* @param Carousel $carousel*/public function __construct(Carousel $carousel){$this->model = $carousel;}/*** @param array $data* @return Carousel* @throws CreateCarouselErrorException*/public function createCarousel(array $data) : Carousel{try {return $this->model->create($data);} catch (QueryException $e) {throw new CreateCarouselErrorException($e);}}/*** @param int $id* @return Carousel* @throws CarouselNotFoundException*/public function findCarousel(int $id) : Carousel{try {return $this->model->findOrFail($id);} catch (ModelNotFoundException $e) {throw new CarouselNotFoundException($e);}}
}

现在运行phpunit看看输出是什么。

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 932 ms, Memory: 26.00MB
OK (1 test, 6 assertions)

update测试

现在让我们测试一下是否能够对carousel进行更新

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{/** @test */public function it_can_update_the_carousel(){$carousel = factory(Carousel::class)->create();$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository($carousel);$update = $carouselRepo->updateCarousel($data);$this->assertTrue($update);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['src'], $carousel->src);}/** @test */public function it_can_show_the_carousel(){$carousel = factory(Carousel::class)->create();$carouselRepo = new CarouselRepository(new Carousel);$found = $carouselRepo->findCarousel($carousel->id);$this->assertInstanceOf(Carousel::class, $found);$this->assertEquals($found->title, $carousel->title);$this->assertEquals($found->link, $carousel->link);$this->assertEquals($found->src, $carousel->src);}/** @test */public function it_can_create_a_carousel(){$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository(new Carousel);$carousel = $carouselRepo->createCarousel($data);$this->assertInstanceOf(Carousel::class, $carousel);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['src'], $carousel->src);}
}

这里我们在吃使用模型工厂创建了carousel记录,然后通过$data参数给updateCarousel来更新carousel并断言更新后的carousel对象的属性值与$data中的字段值一样。

现在这个测试会运行失败,因为你知道的在repository类中还没有定义updateCarousel方法,现在让我们来创建这个方法。

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{protected $model;/*** CarouselRepository constructor.* @param Carousel $carousel*/public function __construct(Carousel $carousel){$this->model = $carousel;}/*** @param array $data* @return Carousel* @throws CreateCarouselErrorException*/public function createCarousel(array $data) : Carousel{try {return $this->model->create($data);} catch (QueryException $e) {throw new CreateCarouselErrorException($e);}}/*** @param int $id* @return Carousel* @throws CarouselNotFoundException*/public function findCarousel(int $id) : Carousel{try {return $this->model->findOrFail($id);} catch (ModelNotFoundException $e) {throw new CarouselNotFoundException($e);}}/*** @param array $data* @return bool* @throws UpdateCarouselErrorException*/public function updateCarousel(array $data) : bool{try {return $this->model->update($data);} catch (QueryException $e) {throw new UpdateCarouselErrorException($e);}}
}

updateCarousel()方法创建完后再次运行phpunit

PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 932 ms, Memory: 26.00MB
OK (1 test, 6 assertions)

方法创建完后测试立马就能运行成功了。

delete测试

最后让我们看一下删除carousel测试

<?php
namespace Tests\Unit\Carousels;
use Tests\TestCase;
class CarouselUnitTest extends TestCase
{/** @test */public function it_can_delete_the_carousel(){$carousel = factory(Carousel::class)->create();$carouselRepo = new CarouselRepository($carousel);$delete = $carouselRepo->deleteCarousel();$this->assertTrue($delete);}/** @test */public function it_can_update_the_carousel(){$carousel = factory(Carousel::class)->create();$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository($carousel);$update = $carouselRepo->updateCarousel($data);$this->assertTrue($update);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['src'], $carousel->src);}/** @test */public function it_can_show_the_carousel(){$carousel = factory(Carousel::class)->create();$carouselRepo = new CarouselRepository(new Carousel);$found = $carouselRepo->findCarousel($carousel->id);$this->assertInstanceOf(Carousel::class, $found);$this->assertEquals($found->title, $carousel->title);$this->assertEquals($found->link, $carousel->link);$this->assertEquals($found->src, $carousel->src);}/** @test */public function it_can_create_a_carousel(){$data = ['title' => $this->faker->word,'link' => $this->faker->url,'src' => $this->faker->url,];$carouselRepo = new CarouselRepository(new Carousel);$carousel = $carouselRepo->createCarousel($data);$this->assertInstanceOf(Carousel::class, $carousel);$this->assertEquals($data['title'], $carousel->title);$this->assertEquals($data['link'], $carousel->link);$this->assertEquals($data['src'], $carousel->src);}
}

然后在repository中创建deleteCarousel()方法:

<?php
namespace App\Shop\Carousels\Repositories;
use App\Shop\Carousels\Carousel;
use App\Shop\Carousels\Exceptions\CarouselNotFoundException;
use App\Shop\Carousels\Exceptions\CreateCarouselErrorException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
class CarouselRepository
{protected $model;/*** CarouselRepository constructor.* @param Carousel $carousel*/public function __construct(Carousel $carousel){$this->model = $carousel;}/*** @param array $data* @return Carousel* @throws CreateCarouselErrorException*/public function createCarousel(array $data) : Carousel{try {return $this->model->create($data);} catch (QueryException $e) {throw new CreateCarouselErrorException($e);}}/*** @param int $id* @return Carousel* @throws CarouselNotFoundException*/public function findCarousel(int $id) : Carousel{try {return $this->model->findOrFail($id);} catch (ModelNotFoundException $e) {throw new CarouselNotFoundException($e);}}/*** @param array $data* @return bool* @throws UpdateCarouselErrorException*/public function updateCarousel(array $data) : bool{try {return $this->model->update($data);} catch (QueryException $e) {throw new UpdateCarouselErrorException($e);}}/*** @return bool*/public function deleteCarousel() : bool{return $this->model->delete();}
}

当这个方法被执行后,它应返回布尔值。如果删除成功返回True,否则返回null。然后再次运行phpunit

➜ git: phpunit --filter=CarouselUnitTest::it_can_delete_the_carousel
PHPUnit 6.5.7 by Sebastian Bergmann and contributors.
.                                                                   1 / 1 (100%)
Time: 916 ms, Memory: 26.00MB
OK (1 test, 1 assertion)

WOW. 现在你已经大功告成了CONGRATULATIONS!

如果想看更多的测试Example可以我的项目的单元测试目录。

下一部分将讲述在Laravel中进行Negative Unit Testing。

Laravel测试驱动开发 -- 正向单元测试相关推荐

  1. Laravel测试驱动开发--反向单元测试

    Negative CRUD Unit Testing in Laravel5 这是一篇译文,原文链接: https://medium.com/@jsdecena/... 作为CRUD Unit Tes ...

  2. Laravel测试驱动开发--反向单元测试 1

    Negative CRUD Unit Testing in Laravel5 这是一篇译文,原文链接: https://medium.com/@jsdecena/... 作为CRUD Unit Tes ...

  3. Laravel测试驱动开发--功能测试

    功能测试 测试驱动开发系列文章的最后一篇文章是关于功能测试的,现在的流行的架构是前后端分离所以很多时候我们并不会像文章里写的那样对响应页面进行功能测试,之前这个系列里的第一篇文章有讲如何对API进行功 ...

  4. Laravel测试驱动开发--功能测试 1

    功能测试 测试驱动开发系列文章的最后一篇文章是关于功能测试的,现在的流行的架构是前后端分离所以很多时候我们并不会像文章里写的那样对响应页面进行功能测试,之前这个系列里的第一篇文章有讲如何对API进行功 ...

  5. rest laravel_如何通过测试驱动开发来构建Laravel REST API

    rest laravel by Kofo Okesola 由Kofo Okesola 如何通过测试驱动开发来构建Laravel REST API (How to build a Laravel RES ...

  6. 用测试驱动开发状态机

    用测试驱动开发状态机 Developing state machines with test-driven development 由于状态机模型在嵌入式系统中的广泛应用,本文探讨了在测试驱动开发(T ...

  7. 测试驱动开发与行为驱动开发中的测试先行方法

    Gil Zilberfeld将在 Agile Practitioners会议上举办小型研讨会,讨论测试先行(test first)方法,测试驱动开发(TDD)和行为驱动开发(BDD)的基础. \\ \ ...

  8. 测试驱动开发 测试前移_测试驱动开发简介

    测试驱动开发 测试前移 I've been programming for five years and, honestly, I have avoided test-driven developme ...

  9. 测试驱动开发 测试前移_测试驱动的开发可能看起来是工作的两倍-但无论如何您都应该这样做...

    测试驱动开发 测试前移 by Navdeep Singh 通过Navdeep Singh 测试驱动的开发可能看起来是工作的两倍-但无论如何您都应该这样做 (Test-driven developmen ...

最新文章

  1. Stack Overflow被收购了,以后要付费“抄代码”?
  2. PyTorch模型量化工具学习
  3. mysql分区方案的研究
  4. JS设置每日定时任务
  5. JavaScript中函数的变量提升问题
  6. AngularJS中使用ng-repeat的index
  7. (计算机组成原理)第三章存储系统-第三节2:ROM芯片
  8. [BZOJ]3436: 小K的农场
  9. LINQ to XML 编程基础
  10. 三种强大的物体识别算法——SIFT/SURF、haar特征、广义hough变换的特性对比分析
  11. 蓝桥杯 ADV-157算法提高 现代诗如蚯蚓
  12. 30 个惊艳的 Bootstrap 扩展插件
  13. 五大开源MySQL管理工具!
  14. QQ群管机器人html官网源码
  15. 告别乐盲,AI 通过歌词生成旋律【智能快讯】
  16. 图像区块分割与采样YUV4:2:0
  17. 计算机基金经理排名,科班出身的基金经理业绩一定比非科班的好吗?
  18. SAS用proc means和proc univariate求数据的样本均值、中位数、四分位数、样本方差、极差、变异系数、二阶、三阶和四阶中心矩、偏度、峰度、标准差和对数据进行正态性检验
  19. pacman使用介绍
  20. 英寸和厘米的交互python_Python课 #04号作业

热门文章

  1. c++101rule
  2. jquery微博实例
  3. ceph升级到10.2.3 版本启动服务报错:Unknown lvalue 'TasksMax' in section 'Service'
  4. Jenkins持续集成环境, 如何自定义 maven repositories
  5. Java多线程时内存模型
  6. c#之多线程之为所欲为
  7. 利用linux curl爬取网站数据
  8. SQL Server 调优系列基础篇 - 子查询运算总结
  9. 【LeetCode从零单排】No118 Pascal#39;s Triangle
  10. 100c之29:求具有abcd= ( ab + cd )^2 性质的四位数