Laravel测试驱动开发—正向单元测试

发表于:2019-3-05 11:08

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:KevinYan译    来源:思否

  文章中简述了如何在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方法。
  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字段是可空的,title和image字段是必须项。
  执行迁移文件创建完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!

      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号