使用 TDD 开发 Laravel 应用的简单 11 步

大多数web开发人员在听说TDD(测试驱动开发)时都会退缩。嗯,当我被要求先用TDD编程时,我做到了。

刚开始的时候,你会觉得压力很大。如果你抗拒它,它会让你更难学会它。那么你该怎么做呢?拥抱它。它存在是有原因的。总是会有关于技术的争论,无论是编程语言还是开发软件的过程。

有些人可能同意XP或Xtreme编程优于TDD,反之亦然。这实际上取决于你作为一个程序员想走哪条路,特别是如果你是一个团队的领导者。所以我们要明智地选择。

注意:这是API响应的TDD。如果您希望使用Laravel blade进行特性测试,请阅读本文。
介绍太多了;我们开始工作吧!

STEP 1: 准备LARAVEL测试套件

在根目录中,更新phpunit.xml文件:

<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="API_DEBUG" value="false"/>
<ini name="memory_limit" value="512M" />

它看起来是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>

        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="SESSION_DRIVER" value="array"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="API_DEBUG" value="false"/>
        <env name="MAIL_DRIVER" value="log"/>
        <ini name="memory_limit" value="512M" />
    </php>
</phpunit>

我们只需要在内存中进行测试,这样会更快。我们将为数据库使用sqlite数据库和:memory:。我必须将调试设置为false,因为我们只需要断言实际的错误。将来当实际调用变得昂贵时,可能需要增加内存限制。

如果准备好了,请确保您的基本测试用例。

<?php
namespace Tests;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Faker\Factory as Faker;
/**
 * Class TestCase
 * @package Tests
 * @runTestsInSeparateProcesses
 * @preserveGlobalState disabled
 */
abstract class TestCase extends BaseTestCase
{
    use CreatesApplication, DatabaseMigrations, DatabaseTransactions;
    protected $faker;
    /**
     * Set up the test
     */
    public function setUp()
    {
        parent::setUp();
        $this->faker = Faker::create();
    }
    /**
     * Reset the migrations
     */
    public function tearDown()
    {
        $this->artisan('migrate:reset');
        parent::tearDown();
    }
}

我们需要添加databasmigrations特性,因此在测试的每次运行中,迁移文件也都在运行。您可能还注意到,我们有setUp()和tearDown()方法,它们是在测试期间完成应用程序的周期所必需的。

STEP 2:编写实际测试

就像Bob叔叔说的,除非您先编写测试,否则您无权编写代码(实现)。

我们来写一下测试。

要让PHPUNIT了解您的测试,您可以将/* @test /注释放在docblock上,或者将test_作为前缀。

<?php
namespace Tests\Unit;
use Tests\TestCase;
class ArticleApiUnitTest extends TestCase
{
  public function it_can_create_an_article()
  {
      $data = [
        'title' => $this->faker->sentence,
        'content' => $this->faker->paragraph
      ];

      $this->post(route('articles.store'), $data)
        ->assertStatus(201)
        ->assertJson($data);
  }
}

在这个测试中,我们检查是否可以创建一篇文章。

我们断言应用程序是否会给我们状态201,是否会用正确的JSON数据进行响应。

对于Laravel来说,由于它使用的是活动记录ORM模式,所以在创建数据时最好将其保存在数据库中。

在创建第一个测试之后,运行 phpunit 或者 vendor/bin/phpunit

所以当我们运行 phpunit 时,它失败了!这是好吗?没错,是很好玩!因为我们运行的是TDD的第二条规则,即在创建测试之后它应该失败。

让我们先来看看它为什么失败。

我们在测试中断言它应该返回201,但它返回404。为什么?

大多数人可能知道为什么,但对于其他人,这是因为URL还没有构建。[POST] /api/v1/article还不存在,因此会抛出404。

我们需要做什么?

STEP 3:在路由文件中创建URL

让我们创建URL,看看会发生什么。

去你的/routes/api.php文件并创建URL。在api.php中创建url路由时,它会自动用 /api作为前缀。

<?php
use App\Http\Controllers\Api\ArticlesApiController;
use Illuminate\Support\Facades\Route;
Route::group(['prefix' => 'v1'], function () {
  Route::resource('articles', ArticlesApiController::class);
});

你可以跑:

php artisan make:controller ArticlesApiController —-resource

或者您可以手动创建它。我手动创建的。POST请求转到 articlesapicontroller 的 store() 方法。

STEP 4: 调试你的控制器

<?php
namespace App\Http\Controllers\Api;
class ArticlesApiController extends Controller 
{
    public function store() {
        dd('success!');
    }
}

因此,让我们调试一下,看看调用能否到达应用程序的这一部分,并再次运行phpunitagain。

它正在提示我们终端中的成功字符串!这意味着它正在赶上我们的测试。

STEP 5: 验证你的输入

不要忘记验证存进数据库的数据. 所以我们创建一个叫 CreateArticleRequest 的类来进行验证。

<?php
namespace App\Http\Controllers\Api;
class ArticlesApiController extends Controller 
{
    public function store(CreateArticleRequest $request) {
        dd('success!');
    }
}

它包括了什么? 当然是验证规则!

<?php
namespace App\Articles\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreateArticleRequest extends FormRequest
{
    /**
     * Transform the error messages into JSON
     *
     * @param array $errors
     * @return \Illuminate\Http\JsonResponse
     */
    public function response(array $errors)
    {
        return response()->json($errors, 422);
    }
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => ['required'],
            'content' => ['required']
        ];
    }
}

这很好,因为我们可以在稍后创建另一个测试,以查看它是否捕获了验证错误,但是为了使本教程更简单,这将在另一篇文章中进行介绍。

STEP 6: 返回创建的文章

请记住,应该返回特定的JSON结构,这样我们就知道数据是在数据库中创建的。因此,我们需要返回Article对象来满足我们的测试。

<?php
namespace App\Http\Controllers\Api;
class ArticlesApiController extends Controller 
{
    /**
    * @param CreateArticleRequest $request
    */
    public function store(CreateArticleRequest $request) {
      return Article::create($request->all());
    }
}

您可能会注意到Article类被高亮显示。这是因为IDE (PhpStorm)无法定位该类。所以,让我们创建它!

STEP 7: 创建类 ARTICLE

<?php
namespace App\Articles;
use Illuminate\Database\Eloquent\Model;
class Article extends Model {
  protected $fillable = [
    'title',
    'content'
  ];

}

在本文类中,您必须定义可填充字段和隐藏字段。完成之后,再次检查控制器并导入Article类。

<?php
namespace App\Http\Controllers\Api;
use App\Articles\Article;
class ArticlesApiController extends Controller 
{
    /**
    * @param CreateArticleRequest $request
    */
    public function store(CreateArticleRequest $request) {
      return Article::create($request->all());
    }
}

如果您注意到,类不再突出显示,因为IDE已经可以定位文件。

我们快到了!测试已正确设置,URL已构建并可访问,捕获它的控制器也已就绪,为数据库表建模的类也准备好了。现在,让我们再次运行phpunit。

STEP 8: 再次运行PHPUNIT,看看发生了什么

又失败了. :( 这是好还是坏? 好吧,有好有坏。很好,因为我们对状态201的断言已经从404更改为500(如果您注意到了这一点)。

坏的,它失败了,我们需要它通过,对吧?当您希望调试并查看应用程序真正抛出的是什么时,您可以在测试中这样做。只需在post请求之后添加 ->dump() 方法。

<?php
namespace Tests\Unit;
use Tests\TestCase;
class ArticleApiUnitTest extends TestCase
{
  public function it_can_create_an_article()
  {
      $data = [
        'title' => $this->faker->sentence,
        'content' => $this->faker->paragraph
      ];

      $this->post(route('articles.store'), $data)
        ->dump()
        ->assertStatus(201)
        ->assertJson($data);
  }
}

您可以进一步调试POST请求的输出。它可能有你需要的所有信息。如果它仍然不能提示您正在发生什么,那么您可以依赖 /storage/logs/laravel 。日志文件。

我们来看看为什么错误变成了500。

我们试图将数据插入到一个不存在的表中,因此应用程序会抱怨没有表可以将数据插入。

STEP 9: 创建数据库表

我们只需要运行一下 laravel 命令:

php artisan make:migration create_articles_table –create=articles

它会自动在下面创建一个迁移文件

/database/migrations

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

默认情况下,它将只有ID和时间戳字段。由您用必需的字段填充它。

STEP 10:再次运行 phpunit

我们快到了!我们承诺这将是11步,我们现在是第10步!你走了这么远,真该拍拍你的背!

糟糕,又失败了?Whyyyyyyy ? ? ?但是如果你仔细检查,你就会发现你是在正确的轨道上!状态码再次从500更改为200。状态200是一个好迹象,因为这意味着它在POST请求之后成功地返回了一些东西!这和我们需要的不匹配。我们需要201代码来知道在数据库中插入了一个实际的post。所以我们只需要将控制器修改为:

<?php
namespace App\Http\Controllers\Api;
use App\Articles\Article;
class ArticlesApiController extends Controller 
{
    /**
    * @param CreateArticleRequest $request
    */
    public function store(CreateArticleRequest $request) {
      return Article::create($request->all(), 201);
    }
}

STEP 11: 运行PHPUNIT,希望一切顺利

恭喜你!你成功了,这是鲍勃叔叔的第三条规则!

这只是Laravel上TDD的简单实现。关于这一点,我将来可能会介绍其他方法,比如存储库模式。存储库模式最好使用DDD或域驱动开发来实现。

参见

https://medium.com/@jsdecena/simple-tdd-in...

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!