LaravelComment effectuer des tests automatisés ? L'article suivant utilise un exemple simple utilisant PHPUnit et PEST pour apprendre comment démarrer les tests dans Laravel. J'espère que cela sera utile à tout le monde.
Quand on parle de tests automatisés ou de tests unitaires pour n'importe quel langage de programmation, il existe généralement deux catégories de personnes :
Alors, à travers cet article, je vais essayer de convaincre les premiers de voir ses avantages et de voir à quel point il est facile de démarrer des tests automatisés dans Laravel. [Recommandation associée : tutoriel vidéo laravel]
Tout d'abord, parlons du « pourquoi », puis je montrerai quelques exemples très basiques de « comment tester ».
Les tests automatisés ne sont pas compliqués : ils exécutent simplement des parties de votre code pour vous et signalent toute erreur. C'est la façon la plus simple de les décrire. Imaginez que vous lancez une nouvelle fonctionnalité dans votre application, puis qu'un assistant robot teste manuellement la nouvelle fonctionnalité pour vous tout en vérifiant que le nouveau code ne casse rien dans l'ancienne fonctionnalité.
L'avantage est : un nouveau test automatique de toutes les fonctions. Cela peut sembler être un travail supplémentaire, mais si vous ne dites pas au « robot » de le faire, alors vous devriez le faire manuellement, n'est-ce pas ? Ou déployez-vous de nouvelles fonctionnalités sans tests détaillés et en espérant que les utilisateurs signalent des bogues ? J'appelle sarcastiquement cette approche « développement multi-doigts ».
Avec chaque nouvelle fonctionnalité de votre application, les récompenses des tests automatisés deviennent de plus en plus élevées.
Démarrage de notre premier test automatisé
Pour exécuter votre premier test automatisé dans Laravel, vous n'avez pas besoin d'écrire de code. Oui, vous avez bien lu. Tout est déjà configuré et prêt dans l'installation par défaut de Laravel, y compris le premier exemple vraiment basique.laravel new project cd project php artisan testComme prévu, le terminal affichera les résultats suivants :
PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\ExampleTest ✓ the application returns a successful response Tests: 2 passed Time: 0.10s
Si nous regardons le /tests
de Laravel par défaut. dossier contenant deux fichiers.
:
class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/'); $response->assertStatus(200); } }
Vous n'avez pas besoin de connaître de syntaxe pour comprendre ce que signifie ce code : chargez la page d'accueil et vérifiez si le code d'état HTTP est « 200 OK ». /tests
文件夹,其中有两个文件。
tests/Feature/ExampleTest.php:
class ExampleTest extends TestCase{ public function test_that_true_is_true() { $this->assertTrue(true); } }
你无需了解任何语法即可读懂这段代码的含义:加载主页并检查 HTTP 状态代码是否「200 OK」。
你需要注意:在查看测试结果时,方法名称 test_the_application_returns_a_successful_response()
如何变为可读文本,只需将下划线符号替换为空格即可。
tests/Unit/ExampleTest.php:
php artisan make:test HomepageTest
这样的代码看上去让人感觉毫无意义,检查结果为 true 很必要吗?在后面片段中,我们将具体讨论单元测试。现在,你只需要了解每次测试中通常发生的情况。
tests/
文件夹中的每个测试文件都是一个 PHP 类,扩展了 PHPUnit 的 TestCase从结构上讲,这就是你需要知道的全部内容,其他一切都取决于你要测试的确切内容。
要生成一个空的测试类,只需运行以下命令:
class HomepageTest extends TestCase{ // Replace this method with your own ones public function test_example() { $response = $this->get('/'); $response->assertStatus(200); } }
它会生成文件 tests/Feature/HomepageTest.php
:
class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/non-existing-url'); $response->assertStatus(200); } } class ExampleTest extends TestCase { public function test_that_true_is_false() { $this->assertTrue(false); } }
让我向你展示如果测试断言没有返回预期结果会发生什么。
让我们将示例测试编辑为:
FAIL Tests\Unit\ExampleTest ⨯ that true is true FAIL Tests\Feature\ExampleTest ⨯ the application returns a successful response --- • Tests\Unit\ExampleTest > that true is true Failed asserting that false is true. at tests/Unit/ExampleTest.php:16 12▕ * @return void 13▕ */ 14▕ public function test_that_true_is_true() 15▕ { ➜ 16▕ $this->assertTrue(false); 17▕ } 18▕ } 19▕ • Tests\Feature\ExampleTest > the application returns a successful response Expected response status code [200] but received 404. Failed asserting that 200 is identical to 404. at tests/Feature/ExampleTest.php:19 15▕ public function test_the_application_returns_a_successful_response() 16▕ { 17▕ $response = $this->get('/non-existing-url'); 18▕ ➜ 19▕ $response->assertStatus(200); 20▕ } 21▕ } 22▕ Tests: 2 failed Time: 0.11s
现在,如果我们再次运行 php artisan test
test_the_application_returns_a_successful_response()
devient un texte lisible, remplacez simplement le symbole de soulignement par un espace. 🎜tests/Unit/ExampleTest.php🎜:🎜use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class RegistrationTest extends TestCase { use RefreshDatabase; public function test_registration_screen_can_be_rendered() { $response = $this->get('/register'); $response->assertStatus(200); } public function test_new_users_can_register() { $response = $this->post('/register', [ 'name' => 'Test User', 'email' => 'test@example.com', 'password' => 'password', 'password_confirmation' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } }🎜Un tel code semble dénué de sens. Est-il nécessaire de vérifier que le résultat est vrai ? Dans un segment ultérieur, nous discuterons en détail des tests unitaires. Pour l’instant, il vous suffit de comprendre ce qui se passe généralement dans chaque test. 🎜🎜🎜Chaque fichier de test dans le dossier
tests/
est une classe PHP étendant TestCase de PHPUnit🎜🎜🎜Dans chaque classe, vous pouvez créer plusieurs méthodes, généralement une méthode est utilisée pour une situation à tester🎜🎜Il y a trois actions à l'intérieur de chaque méthode : préparer la situation, puis agir, puis vérifier (affirmer) que le résultat est comme prévu 🎜🎜🎜 Structurellement, c'est tout ce que vous devez savoir, tout le reste dépend de ce que vous voulez tester exactement. 🎜🎜Pour générer une classe de test vide, exécutez simplement la commande suivante : 🎜<php> <env name="APP_ENV" value="testing"/> <env name="BCRYPT_ROUNDS" value="4"/> <env name="CACHE_DRIVER" value="array"/> <!-- <env name="DB_CONNECTION" value="sqlite"/> --> <!-- <env name="DB_DATABASE" value=":memory:"/> --> <env name="MAIL_MAILER" value="array"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> <env name="TELESCOPE_ENABLED" value="false"/> </php>🎜Elle générera le fichier
tests/Feature/HomepageTest.php
: 🎜$this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME);🎜🎜🎜Et si le test échoue ? 🎜🎜🎜Laissez-moi vous montrer ce qui se passe si une assertion de test ne renvoie pas le résultat attendu.
$this->assertDatabaseCount('users', 1); // 或者... $this->assertDatabaseHas('users', [ 'email' => 'test@example.com', ]);🎜Maintenant, si nous exécutons à nouveau
php artisan test
: 🎜class AuthenticationTest extends TestCase { use RefreshDatabase; public function test_login_screen_can_be_rendered() { $response = $this->get('/login'); $response->assertStatus(200); } public function test_users_can_authenticate_using_the_login_screen() { $user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } public function test_users_can_not_authenticate_with_invalid_password() { $user = User::factory()->create(); $this->post('/login', [ 'email' => $user->email, 'password' => 'wrong-password', ]); $this->assertGuest(); } }🎜Comme vous pouvez le voir, il y a deux déclarations marquées comme FAIL, sous l'explication, la flèche pointe vers la ligne de test exacte où l'assertion a échoué. C'est ainsi que l'erreur apparaît. C'est très pratique, n'est-ce pas ? 🎜
让我们来看看一个现实生活中常见的例子。假设你有一个表单,你需要测试各种情况:检查是否填充无效数据是否失败,检查是否输入正确输入成功等。
你不一定知道,其实官方的 Laravel Breeze 入门套件附带了 内部功能测试?现在,让我们从那里看几个例子:
tests/Feature/RegistrationTest.php
use App\Providers\RouteServiceProvider; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class RegistrationTest extends TestCase { use RefreshDatabase; public function test_registration_screen_can_be_rendered() { $response = $this->get('/register'); $response->assertStatus(200); } public function test_new_users_can_register() { $response = $this->post('/register', [ 'name' => 'Test User', 'email' => 'test@example.com', 'password' => 'password', 'password_confirmation' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } }
在这里,我们在一个类中有两个测试,因为它们都与注册表相关:一个是检查表单是否正确加载了,另一个是检查提交是否正常。
我们来熟悉另外两个检查结果的方法,另外两个断言: $this->assertAuthenticated()
和 $response->assertRedirect()
。 你可以查看 PHPUnit and Laravel Response 官方文档中所有可用的断言。请记住,一些一般的断言发生在 $this
对象上,而另一些检查则来自于路由调用的特定 $response
语句。
另一件重要的事情是 use RefreshDatabase;
语句,使用这个 trait,包含在这个类的上方。当你的测试操作可能会影响数据库时,需要使用它,例如在本例中,注册会在 users
数据库表中添加一个新条目。为此,你需要创建一个单独的测试数据库,该数据库将会在每次测试中使用 php artisan migrate:fresh
命令时被刷新。
你有两个选择:物理上创建一个单独的数据库,或者使用内存中的 SQLite 数据库。它都在 Laravel 默认提供的文件 phpunit.xml
中配置。具体来说, 你需要这部分:
<php> <env name="APP_ENV" value="testing"/> <env name="BCRYPT_ROUNDS" value="4"/> <env name="CACHE_DRIVER" value="array"/> <!-- <env name="DB_CONNECTION" value="sqlite"/> --> <!-- <env name="DB_DATABASE" value=":memory:"/> --> <env name="MAIL_MAILER" value="array"/> <env name="QUEUE_CONNECTION" value="sync"/> <env name="SESSION_DRIVER" value="array"/> <env name="TELESCOPE_ENABLED" value="false"/> </php>
看到被注释掉的 DB_CONNECTION
和 DB_DATABASE
了吗?如果你的服务器上有 SQLite,最简单的操作就是取消注释这些行,你的测试将在该内存数据库上运行。
在本次测试中,我们断言用户通过了身份验证,并被重定向到正确的首页,但我们也可以测试数据库中真实的数据。
除此代码之外:
$this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME);
我们也可以使用 Database Testing assertions 并执行以下操作:
$this->assertDatabaseCount('users', 1); // 或者... $this->assertDatabaseHas('users', [ 'email' => 'test@example.com', ]);
让我们看看另外一个来自 Laravel Breeze 的测试。
tests/Feature/AuthenticationTest.php:
class AuthenticationTest extends TestCase { use RefreshDatabase; public function test_login_screen_can_be_rendered() { $response = $this->get('/login'); $response->assertStatus(200); } public function test_users_can_authenticate_using_the_login_screen() { $user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(RouteServiceProvider::HOME); } public function test_users_can_not_authenticate_with_invalid_password() { $user = User::factory()->create(); $this->post('/login', [ 'email' => $user->email, 'password' => 'wrong-password', ]); $this->assertGuest(); } }
这是关于登录表单的例子。他的逻辑和注册差不多吧?但不一样的是使用了三个方法而不是两个,所以这是一个测试好的和坏的场景的例子。所以,他们共同的逻辑是你应该测试的两种情况:什么时候顺利,什么时候失败。
此外,你在这个测试中看到的是 Database 工厂类 的使用:Laravel 创建了一个假用户(再次, 在你的测试数据库刷新) 上,然后尝试使用正确或不正确的凭据登录。
同样,Laravel 为 User
模型生成带有假数据的默认工厂,开箱即用。
database/factories/UserFactory.php:
class UserFactory extends Factory { public function definition() { return [ 'name' => $this->faker->name(), 'email' => $this->faker->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } }
看,有多少东西是 Laravel 本身提供的,所以我们很容易开始测试。
因此,如果我们在安装 Laravel Breeze 后运行 php artisan test
, 我们应该会看到如下内容:
PASS Tests\Unit\ExampleTest ✓ that true is true PASS Tests\Feature\Auth\AuthenticationTest ✓ login screen can be rendered ✓ users can authenticate using the login screen ✓ users can not authenticate with invalid password PASS Tests\Feature\Auth\EmailVerificationTest ✓ email verification screen can be rendered ✓ email can be verified ✓ email is not verified with invalid hash PASS Tests\Feature\Auth\PasswordConfirmationTest ✓ confirm password screen can be rendered ✓ password can be confirmed ✓ password is not confirmed with invalid password PASS Tests\Feature\Auth\PasswordResetTest ✓ reset password link screen can be rendered ✓ reset password link can be requested ✓ reset password screen can be rendered ✓ password can be reset with valid token PASS Tests\Feature\Auth\RegistrationTest ✓ registration screen can be rendered ✓ new users can register PASS Tests\Feature\ExampleTest ✓ the application returns a successful response Tests: 17 passed Time: 0.61s
你已经看到了 tests/Feature
和 tests/Unit
子文件夹。两者之间有什么区别?答案有点“哲学”。
从测试的全局视角来看,在 Laravel/PHP 生态系统之外,有不同类型的自动化测试。你可以找到以下术语:
这听起来很复杂,而且这些测试类型之间的实际差异有时是模糊的。这就是为什么 Laravel 简化了所有这些令人困惑的术语并将它们分为两类:单元测试/功能测试。
简而言之,功能测试尝试运行应用程序的实际功能:获取 URL、调用 API、模拟填写表单等确切行为。功能测试通常执行与任何项目用户在现实生活中手动执行的相同或相似的事情。
单元测试有两个含义。通常,你可能会发现任何自动化测试都称为「单元测试」,而整个过程可能称为「单元测试」。但是在功能与单元的上下文中,这个过程是关于单独测试代码的特定非公共单元。例如,你有一些 Laravel 类,它有一个计算某些东西的方法,比如带有参数的订单的总价格。因此,你的单元测试将断言该方法(代码单元)是否返回了具有不同参数的正确结果。
要生成单元测试,你需要添加一个标志:
php artisan make:test OrderPriceTest --unit
生成的代码与 Laravel 的默认单元测试相同:
class OrderPriceTest extends TestCase { public function test_example() { $this->assertTrue(true); } }
如你所见,没有 RefreshDatabase
行为的定义,这是单元测试最常见的定义之一:它不涉及数据库,它像一个「黑匣子」一样工作,与正在运行的应用程序隔离。
你可以尝试模仿我之前提到的示例,假设我们有一个服务类 OrderPrice
。
app/Services/OrderPriceService.php:
class OrderPriceService{ public function calculatePrice($productId, $quantity, $tax = 0.0) { // 某种计算逻辑 } }
然后,单元测试可能看起来像这样:
class OrderPriceTest extends TestCase{ public function test_single_product_no_taxes() { $product = Product::factory()->create(); // 生成假的产品数据 $price = (new OrderPriceService())->calculatePrice($product->id, 1); $this->assertEquals(1, $price); } public function test_single_product_with_taxes() { $price = (new OrderPriceService())->calculatePrice($product->id, 1, 20); $this->assertEquals(1.2, $price); } // 更多的参数和案例 }
从我个人对 Laravel 项目的经验而言,绝大多数测试是功能测试,而不是单元测试。首先,你需要测试你的应用程序是否正常工作,以及真实用户使用它的方式。
接下来,如果你有可以定义为单元的特殊计算或逻辑,或带有一些参数,你可以专门为此创建单元测试。
有时候,编写测试需要更改代码本身,并将其重构为更「可测试的」:将单元分离为特殊的类或方法。
php artisan test
命令的实际用途是什么,我们应该在什么时候运行它?
什么时候运行测试,在开发过程中并没有固定的时间节点或说法,具体取决于你公司的工作流程。通常情况下,在我们将最新的代码更改推送到代码仓库之前,你需要确保所有测试都是「绿色的」(意味着没有错误)。
因此,当你在本地编写代码,在你觉得自己已经完成了你的任务时,你需要运行测试,用来确保你没有破坏任何东西。请记住,你的代码可能不仅会在你自己编写的代码逻辑中导致错误,而且还会无意中破坏其他人很久以前编写的代码中的其他行为。
如果我们更进一步,可以自动化的完成很多事情。如使用各种 CI/CD 工具,你可以指定在有人将更改推送到特定 Git 分支时或在将代码合并到生产分支之前执行的测试。最简单的工作流程是使用 Github Actions,在这里,我提供了 一个单独的视频 演示它。
关于所谓的「测试覆盖率」应该覆盖到多大的范围的争议,一直以来,有多种意见:你应该测试每个页面上的每个操作和每个可能的案例,还是只将你的工作限制在最重要的部分。
事实上,这就是我同意人们指责自动化测试花费更多时间而不是带来实际收益观点的地方。如果你为每个细节编写测试,这种情况就可能出现。也就是说,你的项目可能需要思考这个问题:「代码中潜在的错误会给你带来多大的成本或代价」。
换句话说,你需要通过“如果此代码失败会发生什么?”这个问题来确定你的测试工作的优先级。如果你的支付系统存在错误,这将直接影响业务。如果你的角色/权限功能被破坏,那这将是一个巨大的安全问题。
我喜欢 Matt Stauffer 在一次会议上的措辞:「你需要先测试这些东西,如果它们失败了,你就会被解雇」。当然,这有点夸张,但你明白了:首先测试重要的事情。然后是其他功能,如果你有时间的话。
以上所有示例均基于默认的 Laravel 测试工具:PHPUnit。但多年来,生态系统中出现了其他工具,最新流行的工具之一是 PEST。由 Laravel 官方员工 Nuno Maduro 创建,它的目标是简化语法,从而更快地编写测试代码。
在底层实现上,它基于 PHPUnit 运行;作为一个附属扩展,它只是试图最小化 PHPUnit 代码的一些默认重复部分。
让我们来看一个例子。还记得 Laravel 中默认的功能测试类吗?就如下面这段代码:
namespace Tests\Feature; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase; class ExampleTest extends TestCase { public function test_the_application_returns_a_successful_response() { $response = $this->get('/'); $response->assertStatus(200); } }
让我们使用 PEST 来实现同样的测试,实现后的代码如下:
test('the application returns a successful response')->get('/')->assertStatus(200);
是的,一行代码,就是这样。因此,PEST 的目标是解决以下问题:
要在 Laravel 中生成 PEST 测试,你需要指定一个附加标志:
php artisan make:test HomepageTest --pest
在撰写本文时,PEST 在 Laravel 开发人员中相当流行,但是除了众所周知的 PHPUnit 之外,是否使用这个额外的工具并学习它的语法是你个人的喜好。
因此,这就是你需要了解的有关自动化测试基础知识的全部内容。从这里开始,你可以选择创建哪些测试以及如何在你的项目中运行它们。
原文地址:https://laravel-news.com/how-to-start-testing
译文地址:https://learnku.com/laravel/t/67381
更多编程相关知识,请访问:编程教学!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!