使用 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 协议》,转载必须注明作者和本文链接
推荐文章: