日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

使用 TDD 测试驱动开发来构建 Laravel REST API

發(fā)布時(shí)間:2024/1/17 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 TDD 测试驱动开发来构建 Laravel REST API 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

TDD 以及敏捷開(kāi)發(fā)的先驅(qū)者之一的?James Grenning有句名言:

如果你沒(méi)有進(jìn)行測(cè)試驅(qū)動(dòng)開(kāi)發(fā),那么你應(yīng)該正在做開(kāi)發(fā)后堵漏的事 - James?Grenning

今天我們將進(jìn)行一場(chǎng)基于 Laravel 的測(cè)試驅(qū)動(dòng)開(kāi)發(fā)之旅。 我們將創(chuàng)建一個(gè)完整的 Laravel REST API,其中包含身份驗(yàn)證和 CRUD 功能,而無(wú)需打開(kāi) Postman 或?yàn)g覽器。?

注意:本旅程假定你已經(jīng)理解了?Laravel?和?PHPUnit 的基本概念。你是否已經(jīng)明晰了這個(gè)問(wèn)題?那就開(kāi)始吧。

項(xiàng)目設(shè)置

首先創(chuàng)建一個(gè)新的 Laravel 項(xiàng)目?composer create-project --prefer-dist laravel/laravel tdd-journey。

然后,我們需要?jiǎng)?chuàng)建 用戶認(rèn)證 腳手架,執(zhí)行 ?php artisan make:auth?,設(shè)置好 .env 文件中的數(shù)據(jù)庫(kù)配置后,執(zhí)行數(shù)據(jù)庫(kù)遷移?php artisan migrate。

本測(cè)試項(xiàng)目并不會(huì)使用到剛生成的用戶認(rèn)證相關(guān)的路由和視圖。我們將使用?jwt-auth。所以需要繼續(xù)?安裝?jwt 到項(xiàng)目。

注意:如果您在執(zhí)行?jwt:generate?指令時(shí)遇到錯(cuò)誤, 您可以參考?這里解決這個(gè)問(wèn)題,直到 jwt 被正確安裝到項(xiàng)目中。

最后,您需要在 tests/Unit 和 tests/Feature 目錄中刪除 ExampleTest.php 文件,使我們的測(cè)試結(jié)果不被影響。

編碼

  • 首先將?JWT 驅(qū)動(dòng)配置為 auth?配置項(xiàng)的默認(rèn)值:
  • <?php // config/auth.php file 'defaults' => ['guard' => 'api','passwords' => 'users', ], 'guards' => [...'api' => ['driver' => 'jwt','provider' => 'users',], ], 復(fù)制代碼

    然后將如下內(nèi)容放到你的 routes/api.php?文件里:

    <?php Route::group(['middleware' => 'api', 'prefix' => 'auth'], function () {Route::post('authenticate', 'AuthController@authenticate')->name('api.authenticate');Route::post('register', 'AuthController@register')->name('api.register'); }); 復(fù)制代碼
  • 現(xiàn)在我們已經(jīng)將驅(qū)動(dòng)設(shè)置完成了,如法炮制,去設(shè)置你的用戶模型:
  • <?php ... class User extends Authenticatable implements JWTSubject {...//獲取將被存儲(chǔ)在 JWT 主體 claim 中的標(biāo)識(shí)public function getJWTIdentifier(){return $this->getKey();}// 返回一個(gè)鍵值對(duì)數(shù)組,包含要添加到 JWT 的任何自定義 claimpublic function getJWTCustomClaims(){return [];} } 復(fù)制代碼

    我們所需要做的就是實(shí)現(xiàn)?JWTSubject 接口然后添加相應(yīng)的方法即可。

  • 接下來(lái),我們需要增加權(quán)限認(rèn)證方法到控制器中.
  • 運(yùn)行 php artisan make:controller AuthController 并且添加以下方法:

    <?php ... class AuthController extends Controller {public function authenticate(Request $request){// 驗(yàn)證字段$this->validate($request,['email' => 'required|email','password'=> 'required']);// 測(cè)試驗(yàn)證$credentials = $request->only(['email','password']);if (! $token = auth()->attempt($credentials)) {return response()->json(['error' => 'Incorrect credentials'], 401);}return response()->json(compact('token'));}public function register(Request $request){// 表達(dá)驗(yàn)證$this->validate($request,['email' => 'required|email|max:255|unique:users','name' => 'required|max:255','password' => 'required|min:8|confirmed',]);// 創(chuàng)建用戶并生成 Token$user = User::create(['name' => $request->input('name'),'email' => $request->input('email'),'password' => Hash::make($request->input('password')),]);$token = JWTAuth::fromUser($user);return response()->json(compact('token'));} } 復(fù)制代碼

    這一步非常直接,我們要做的就是添加 authenticate 和 register 方法到我們的控制器中。在 authenticate 方法,我們驗(yàn)證了輸入,嘗試去登錄,如果成功就返回令牌。在 register 方法,我們驗(yàn)證輸入,然后基于此創(chuàng)建一個(gè)用戶并且生成令牌。

    4. 接下來(lái),我們進(jìn)入相對(duì)簡(jiǎn)單的部分。 測(cè)試我們剛寫(xiě)入的內(nèi)容。 使用 php artisan make:test AuthTest 生成測(cè)試類(lèi)。 在新的 tests / Feature / AuthTest 中添加以下方法:

    <?php /*** @test * Test registration*/ public function testRegister(){//創(chuàng)建測(cè)試用戶數(shù)據(jù)$data = ['email' => 'test@gmail.com','name' => 'Test','password' => 'secret1234','password_confirmation' => 'secret1234',];//發(fā)送 post 請(qǐng)求$response = $this->json('POST',route('api.register'),$data);//判斷是否發(fā)送成功$response->assertStatus(200);//接收我們得到的 token$this->assertArrayHasKey('token',$response->json());//刪除數(shù)據(jù)User::where('email','test@gmail.com')->delete(); } /*** @test* Test login*/ public function testLogin() {//創(chuàng)建用戶User::create(['name' => 'test','email'=>'test@gmail.com','password' => bcrypt('secret1234')]);//模擬登陸$response = $this->json('POST',route('api.authenticate'),['email' => 'test@gmail.com','password' => 'secret1234',]);//判斷是否登錄成功并且收到了 token $response->assertStatus(200);$this->assertArrayHasKey('token',$response->json());//刪除用戶User::where('email','test@gmail.com')->delete(); } 復(fù)制代碼

    上面代碼中的幾行注釋概括了代碼的大概作用。 您應(yīng)該注意的一件事是我們?nèi)绾卧诿總€(gè)測(cè)試中創(chuàng)建和刪除用戶。 測(cè)試的全部要點(diǎn)是它們應(yīng)該彼此獨(dú)立并且應(yīng)該在你的理想情況下存在數(shù)據(jù)庫(kù)中的狀態(tài)。

    如果你想全局安裝它,可以運(yùn)行 $ vendor / bin / phpunit 或 $ phpunit 命令。 運(yùn)行后它應(yīng)該會(huì)給你返回是否安裝成功的數(shù)據(jù)。 如果不是這種情況,您可以瀏覽日志,修復(fù)并重新測(cè)試。 這就是 TDD 的美麗之處。

    5. 對(duì)于本教程,我們將使用『菜譜 Recipes』作為我們的 CRUD 數(shù)據(jù)模型。

    首先創(chuàng)建我們的遷移數(shù)據(jù)表 php artisan make:migration create_recipes_table 并添加以下內(nèi)容:

    <?php ... public function up() {Schema::create('recipes', function (Blueprint $table) {$table->increments('id');$table->string('title');$table->text('procedure')->nullable();$table->tinyInteger('publisher_id')->nullable();$table->timestamps();}); } public function down() {Schema::dropIfExists('recipes'); } 復(fù)制代碼

    然后運(yùn)行數(shù)據(jù)遷移。 現(xiàn)在使用命令 php artisan make:model Recipe 來(lái)生成模型并將其添加到我們的模型中。

    <?php ... protected $fillable = ['title','procedure']; /*** 發(fā)布者* @return \Illuminate\Database\Eloquent\Relations\BelongsTo*/ public function publisher(){return $this->belongsTo(User::class); } 復(fù)制代碼

    然后將此方法添加到 user 模型。

    <?php .../*** 獲取所有菜譜* @return \Illuminate\Database\Eloquent\Relations\HasMany*/ public function recipes(){return $this->hasMany(Recipe::class); } 復(fù)制代碼

    6. 現(xiàn)在我們需要最后一部分設(shè)置來(lái)完成我們的食譜管理。 首先,我們將創(chuàng)建控制器 php artisan make:controller RecipeController 。 接下來(lái),編輯 routes / api.php 文件并添加 create 路由端點(diǎn)。

    <?php ...Route::group(['middleware' => ['api','auth'],'prefix' => 'recipe'],function (){Route::post('create','RecipeController@create')->name('recipe.create'); }); 復(fù)制代碼

    在控制器中,還要添加 create 方法

    <?php ...public function create(Request $request){//驗(yàn)證數(shù)據(jù)$this->validate($request,['title' => 'required','procedure' => 'required|min:8']);//創(chuàng)建配方并附加到用戶$user = Auth::user();$recipe = Recipe::create($request->only(['title','procedure']));$user->recipes()->save($recipe);//返回 json 格式的食譜數(shù)據(jù)return $recipe->toJson(); } 復(fù)制代碼

    使用 php artisan make:test RecipeTest 生成特征測(cè)試并編輯內(nèi)容,如下所示:

    <?php ... class RecipeTest extends TestCase {use RefreshDatabase;...//創(chuàng)建用戶并驗(yàn)證用戶身份protected function authenticate(){$user = User::create(['name' => 'test','email' => 'test@gmail.com','password' => Hash::make('secret1234'),]);$token = JWTAuth::fromUser($user);return $token;}public function testCreate(){//獲取 token$token = $this->authenticate();$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('POST',route('recipe.create'),['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$response->assertStatus(200);} } 復(fù)制代碼

    上面的代碼你可能還是不太理解。我們所做的就是創(chuàng)建一個(gè)用于處理用戶注冊(cè)和 token 生成的方法,然后在 testCreate() 方法中使用該 token 。注意使用 RefreshDatabase trait ,這個(gè) trait 是 Laravel 在每次測(cè)試后重置數(shù)據(jù)庫(kù)的便捷方式,非常適合我們漂亮的小項(xiàng)目。

    好的,所以現(xiàn)在,我們只要判斷當(dāng)前請(qǐng)求是否是響應(yīng)狀態(tài),然后繼續(xù)運(yùn)行 $ vendor/bin/phpunit 。

    如果一切運(yùn)行順利,您應(yīng)該收到錯(cuò)誤。 ?

    There was 1 failure:

  • Tests\Feature\RecipeTest::testCreate Expected status code 200 but received 500. Failed asserting that false is true.
  • /home/user/sites/tdd-journey/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:133 /home/user/sites/tdd-journey/tests/Feature/RecipeTest.php:49

    FAILURES! Tests: 3, Assertions: 5, Failures: 1.

    查看日志文件,我們可以看到罪魁禍?zhǔn)资?Recipe 和 User 類(lèi)中的 publisher 和 recipes 的關(guān)系。 Laravel 嘗試在表中找到一個(gè)字段為 user_id 的列并將其用作于外鍵,但在我們的遷移中,我們將publisher_id設(shè)置為外鍵。 現(xiàn)在,將行調(diào)整為:

    //食譜文件 public function publisher(){return $this->belongsTo(User::class,'publisher_id'); } //用戶文件 public function recipes(){return $this->hasMany(Recipe::class,'publisher_id'); } 復(fù)制代碼

    然后重新運(yùn)行測(cè)試。 如果一切順利,我們將獲得所有綠色測(cè)試!?

    ... 3 / 3 (100%) ... OK (3 tests, 5 assertions) 復(fù)制代碼

    現(xiàn)在我們?nèi)匀恍枰獪y(cè)試創(chuàng)建配方的方法。為此,我們可以判斷用戶的『菜譜 Recipes』計(jì)數(shù)。更新你的 testCreate 方法,如下所示:

    <?php ... //獲取 token $token = $this->authenticate(); $response = $this->withHeaders(['Authorization' => 'Bearer '. $token, ])->json('POST',route('recipe.create'),['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!' ]); $response->assertStatus(200); //得到計(jì)數(shù)做出判斷 $count = User::where('email','test@gmail.com')->first()->recipes()->count(); $this->assertEquals(1,$count); 復(fù)制代碼

    我們現(xiàn)在可以繼續(xù)編寫(xiě)其余的方法。首先,編寫(xiě)我們的 routes/api.php

    <?php ... Route::group(['middleware' => ['api','auth'],'prefix' => 'recipe'],function (){Route::post('create','RecipeController@create')->name('recipe.create');Route::get('all','RecipeController@all')->name('recipe.all');Route::post('update/{recipe}','RecipeController@update')->name('recipe.update');Route::get('show/{recipe}','RecipeController@show')->name('recipe.show');Route::post('delete/{recipe}','RecipeController@delete')->name('recipe.delete'); }); 復(fù)制代碼

    接下來(lái),我們將方法添加到控制器。 以下面這種方式更新 RecipeController 類(lèi)。

    <?php .... //創(chuàng)建配方 public function create(Request $request){//驗(yàn)證$this->validate($request,['title' => 'required','procedure' => 'required|min:8']);//創(chuàng)建配方并附加到用戶$user = Auth::user();$recipe = Recipe::create($request->only(['title','procedure']));$user->recipes()->save($recipe);//返回配方的 json 格式數(shù)據(jù)return $recipe->toJson(); } //獲取所有的配方 public function all(){return Auth::user()->recipes; } //更新配方 public function update(Request $request, Recipe $recipe){//檢查用戶是否是配方的所有者if($recipe->publisher_id != Auth::id()){abort(404);return;}//更新并返回$recipe->update($request->only('title','procedure'));return $recipe->toJson(); } //顯示單個(gè)食譜的詳細(xì)信息 public function show(Recipe $recipe){if($recipe->publisher_id != Auth::id()){abort(404);return;}return $recipe->toJson(); } //刪除一個(gè)配方 public function delete(Recipe $recipe){if($recipe->publisher_id != Auth::id()){abort(404);return;}$recipe->delete(); } 復(fù)制代碼

    代碼和注釋已經(jīng)很好地解釋了這個(gè)邏輯。

    最后我們的?test/Feature/RecipeTest:

    <?php ...use RefreshDatabase; protected $user; // 創(chuàng)建用戶并驗(yàn)證他 protected function authenticate(){$user = User::create(['name' => 'test','email' => 'test@gmail.com','password' => Hash::make('secret1234'),]);$this->user = $user;$token = JWTAuth::fromUser($user);return $token; } // 測(cè)試創(chuàng)建路由 public function testCreate() {// 獲取令牌$token = $this->authenticate();$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('POST',route('recipe.create'),['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$response->assertStatus(200);// 獲取計(jì)數(shù)并斷言$count = $this->user->recipes()->count();$this->assertEquals(1,$count); } // 測(cè)試顯示所有路由 public function testAll(){// 驗(yàn)證并將配方附加到用戶$token = $this->authenticate();$recipe = Recipe::create(['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$this->user->recipes()->save($recipe);// 調(diào)用路由并斷言響應(yīng)$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('GET',route('recipe.all'));$response->assertStatus(200);// 斷言計(jì)數(shù)為1,第一項(xiàng)的標(biāo)題相關(guān)$this->assertEquals(1,count($response->json()));$this->assertEquals('Jollof Rice',$response->json()[0]['title']); } // 測(cè)試更新路由 public function testUpdate(){$token = $this->authenticate();$recipe = Recipe::create(['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$this->user->recipes()->save($recipe);// 調(diào)用路由并斷言響應(yīng)$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('POST',route('recipe.update',['recipe' => $recipe->id]),['title' => 'Rice',]);$response->assertStatus(200);// 斷言標(biāo)題是新標(biāo)題$this->assertEquals('Rice',$this->user->recipes()->first()->title); } // 測(cè)試單一的展示路由 public function testShow(){$token = $this->authenticate();$recipe = Recipe::create(['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$this->user->recipes()->save($recipe);$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('GET',route('recipe.show',['recipe' => $recipe->id]));$response->assertStatus(200);// 斷言標(biāo)題是正確的$this->assertEquals('Jollof Rice',$response->json()['title']); } // 測(cè)試刪除路由 public function testDelete(){$token = $this->authenticate();$recipe = Recipe::create(['title' => 'Jollof Rice','procedure' => 'Parboil rice, get pepper and mix, and some spice and serve!']);$this->user->recipes()->save($recipe);$response = $this->withHeaders(['Authorization' => 'Bearer '. $token,])->json('POST',route('recipe.delete',['recipe' => $recipe->id]));$response->assertStatus(200);// 斷言沒(méi)有食譜$this->assertEquals(0,$this->user->recipes()->count()); } 復(fù)制代碼

    除了附加測(cè)試之外,我們還添加了一個(gè)類(lèi)范圍的 $user 屬性。 這樣,我們不止可以利用 $user 來(lái)使用?authenticate?方法不僅生成令牌,而且還為后續(xù)其他對(duì) $user 的操作做好了準(zhǔn)備。

    現(xiàn)在運(yùn)行?$ vendor/bin/phpunit?如果操作正確,你應(yīng)該進(jìn)行所有綠色測(cè)試。

    結(jié)論

    希望這能讓你深度了解在 TDD 在 Laravel 項(xiàng)目中的運(yùn)行方式。 他絕對(duì)是一個(gè)比這更寬泛的概念,一個(gè)不受特地方法約束的概念。

    雖然這種開(kāi)發(fā)方法看起來(lái)比常見(jiàn)的調(diào)試后期程序要耗時(shí), 但他很適合在代碼中盡早捕獲錯(cuò)誤。雖然有些情況下非 TDD 方式會(huì)更有用,但習(xí)慣于 TDD 模式開(kāi)發(fā)是一種可靠的技能和習(xí)慣。

    本演練的全部代碼可參見(jiàn) Github?here 倉(cāng)庫(kù)。請(qǐng)隨意使用。

    干杯!

    文章轉(zhuǎn)自:https://learnku.com/laravel/t/22690
    更多文章:https://learnku.com/laravel/c/translations

    轉(zhuǎn)載于:https://juejin.im/post/5c7f3eb9518825408d6fe8a2

    總結(jié)

    以上是生活随笔為你收集整理的使用 TDD 测试驱动开发来构建 Laravel REST API的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。