摘要:以及敏捷開發的先驅者之一的有句名言如果你沒有進行測試驅動開發,那么你應該正在做開發后堵漏的事今天我們將進行一場基于的測試驅動開發之旅。使用生成測試類。現在使用命令來生成模型并將其添加到我們的模型中。
TDD 以及敏捷開發的先驅者之一的?James Grenning有句名言:
如果你沒有進行測試驅動開發,那么你應該正在做開發后堵漏的事 - James?Grenning
今天我們將進行一場基于 Laravel 的測試驅動開發之旅。 我們將創建一個完整的 Laravel REST API,其中包含身份驗證和 CRUD 功能,而無需打開 Postman 或瀏覽器。?
注意:本旅程假定你已經理解了?Laravel?和?PHPUnit 的基本概念。你是否已經明晰了這個問題?那就開始吧。
首先創建一個新的 Laravel 項目?composer create-project --prefer-dist laravel/laravel tdd-journey。
然后,我們需要創建 用戶認證 腳手架,執行 ?php artisan make:auth?,設置好 .env 文件中的數據庫配置后,執行數據庫遷移?php artisan migrate。
本測試項目并不會使用到剛生成的用戶認證相關的路由和視圖。我們將使用?jwt-auth。所以需要繼續?安裝?jwt 到項目。
注意:如果您在執行?jwt:generate?指令時遇到錯誤, 您可以參考?這里解決這個問題,直到 jwt 被正確安裝到項目中。
最后,您需要在 tests/Unit 和 tests/Feature 目錄中刪除 ExampleTest.php 文件,使我們的測試結果不被影響。
首先將?JWT 驅動配置為 auth?配置項的默認值:
[ "guard" => "api", "passwords" => "users", ], "guards" => [ ... "api" => [ "driver" => "jwt", "provider" => "users", ], ],
然后將如下內容放到你的 routes/api.php?文件里:
"api", "prefix" => "auth"], function () { Route::post("authenticate", "AuthController@authenticate")->name("api.authenticate"); Route::post("register", "AuthController@register")->name("api.register"); });
現在我們已經將驅動設置完成了,如法炮制,去設置你的用戶模型:
getKey(); } // 返回一個鍵值對數組,包含要添加到 JWT 的任何自定義 claim public function getJWTCustomClaims() { return []; } }
我們所需要做的就是實現?JWTSubject 接口然后添加相應的方法即可。
接下來,我們需要增加權限認證方法到控制器中.
運行 php artisan make:controller AuthController 并且添加以下方法:
validate($request,["email" => "required|email","password"=> "required"]); // 測試驗證 $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){ // 表達驗證 $this->validate($request,[ "email" => "required|email|max:255|unique:users", "name" => "required|max:255", "password" => "required|min:8|confirmed", ]); // 創建用戶并生成 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")); } }
這一步非常直接,我們要做的就是添加 authenticate 和 register 方法到我們的控制器中。在 authenticate 方法,我們驗證了輸入,嘗試去登錄,如果成功就返回令牌。在 register 方法,我們驗證輸入,然后基于此創建一個用戶并且生成令牌。
4. 接下來,我們進入相對簡單的部分。 測試我們剛寫入的內容。 使用 php artisan make:test AuthTest 生成測試類。 在新的 tests / Feature / AuthTest 中添加以下方法:
"test@gmail.com", "name" => "Test", "password" => "secret1234", "password_confirmation" => "secret1234", ]; //發送 post 請求 $response = $this->json("POST",route("api.register"),$data); //判斷是否發送成功 $response->assertStatus(200); //接收我們得到的 token $this->assertArrayHasKey("token",$response->json()); //刪除數據 User::where("email","test@gmail.com")->delete(); } /** * @test * Test login */ public function testLogin() { //創建用戶 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(); }
上面代碼中的幾行注釋概括了代碼的大概作用。 您應該注意的一件事是我們如何在每個測試中創建和刪除用戶。 測試的全部要點是它們應該彼此獨立并且應該在你的理想情況下存在數據庫中的狀態。
如果你想全局安裝它,可以運行 $ vendor / bin / phpunit 或 $ phpunit 命令。 運行后它應該會給你返回是否安裝成功的數據。 如果不是這種情況,您可以瀏覽日志,修復并重新測試。 這就是 TDD 的美麗之處。
5. 對于本教程,我們將使用『菜譜 Recipes』作為我們的 CRUD 數據模型。
首先創建我們的遷移數據表 php artisan make:migration create_recipes_table 并添加以下內容:
increments("id"); $table->string("title"); $table->text("procedure")->nullable(); $table->tinyInteger("publisher_id")->nullable(); $table->timestamps(); }); } public function down() { Schema::dropIfExists("recipes"); }
然后運行數據遷移。 現在使用命令 php artisan make:model Recipe 來生成模型并將其添加到我們的模型中。
belongsTo(User::class); }
然后將此方法添加到 user 模型。
hasMany(Recipe::class); }
6. 現在我們需要最后一部分設置來完成我們的食譜管理。 首先,我們將創建控制器 php artisan make:controller RecipeController 。 接下來,編輯 routes / api.php 文件并添加 create 路由端點。
["api","auth"],"prefix" => "recipe"],function (){ Route::post("create","RecipeController@create")->name("recipe.create"); });
在控制器中,還要添加 create 方法
validate($request,["title" => "required","procedure" => "required|min:8"]); //創建配方并附加到用戶 $user = Auth::user(); $recipe = Recipe::create($request->only(["title","procedure"])); $user->recipes()->save($recipe); //返回 json 格式的食譜數據 return $recipe->toJson(); }
使用 php artisan make:test RecipeTest 生成特征測試并編輯內容,如下所示:
"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); } }
上面的代碼你可能還是不太理解。我們所做的就是創建一個用于處理用戶注冊和 token 生成的方法,然后在 testCreate() 方法中使用該 token 。注意使用 RefreshDatabase trait ,這個 trait 是 Laravel 在每次測試后重置數據庫的便捷方式,非常適合我們漂亮的小項目。
好的,所以現在,我們只要判斷當前請求是否是響應狀態,然后繼續運行 $ vendor/bin/phpunit 。
如果一切運行順利,您應該收到錯誤。 ?
There was 1 failure:1) TestsFeatureRecipeTest::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:49FAILURES!
Tests: 3, Assertions: 5, Failures: 1.
查看日志文件,我們可以看到罪魁禍首是 Recipe 和 User 類中的 publisher 和 recipes 的關系。 Laravel 嘗試在表中找到一個字段為 user_id 的列并將其用作于外鍵,但在我們的遷移中,我們將publisher_id 設置為外鍵。 現在,將行調整為:
//食譜文件 public function publisher(){ return $this->belongsTo(User::class,"publisher_id"); } //用戶文件 public function recipes(){ return $this->hasMany(Recipe::class,"publisher_id"); }
然后重新運行測試。 如果一切順利,我們將獲得所有綠色測試!?
... 3 / 3 (100%) ... OK (3 tests, 5 assertions)
現在我們仍然需要測試創建配方的方法。為此,我們可以判斷用戶的『菜譜 Recipes』計數。更新你的 testCreate 方法,如下所示:
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); //得到計數做出判斷 $count = User::where("email","test@gmail.com")->first()->recipes()->count(); $this->assertEquals(1,$count);
我們現在可以繼續編寫其余的方法。首先,編寫我們的 routes/api.php
["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"); });
接下來,我們將方法添加到控制器。 以下面這種方式更新 RecipeController 類。
validate($request,["title" => "required","procedure" => "required|min:8"]); //創建配方并附加到用戶 $user = Auth::user(); $recipe = Recipe::create($request->only(["title","procedure"])); $user->recipes()->save($recipe); //返回配方的 json 格式數據 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(); } //顯示單個食譜的詳細信息 public function show(Recipe $recipe){ if($recipe->publisher_id != Auth::id()){ abort(404); return; } return $recipe->toJson(); } //刪除一個配方 public function delete(Recipe $recipe){ if($recipe->publisher_id != Auth::id()){ abort(404); return; } $recipe->delete(); }
代碼和注釋已經很好地解釋了這個邏輯。
最后我們的?test/Feature/RecipeTest:
"test", "email" => "test@gmail.com", "password" => Hash::make("secret1234"), ]); $this->user = $user; $token = JWTAuth::fromUser($user); return $token; } // 測試創建路由 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); // 獲取計數并斷言 $count = $this->user->recipes()->count(); $this->assertEquals(1,$count); } // 測試顯示所有路由 public function testAll(){ // 驗證并將配方附加到用戶 $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.all")); $response->assertStatus(200); // 斷言計數為1,第一項的標題相關 $this->assertEquals(1,count($response->json())); $this->assertEquals("Jollof Rice",$response->json()[0]["title"]); } // 測試更新路由 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); // 調用路由并斷言響應 $response = $this->withHeaders([ "Authorization" => "Bearer ". $token, ])->json("POST",route("recipe.update",["recipe" => $recipe->id]),[ "title" => "Rice", ]); $response->assertStatus(200); // 斷言標題是新標題 $this->assertEquals("Rice",$this->user->recipes()->first()->title); } // 測試單一的展示路由 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); // 斷言標題是正確的 $this->assertEquals("Jollof Rice",$response->json()["title"]); } // 測試刪除路由 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); // 斷言沒有食譜 $this->assertEquals(0,$this->user->recipes()->count()); }
除了附加測試之外,我們還添加了一個類范圍的 $user 屬性。 這樣,我們不止可以利用 $user 來使用?authenticate?方法不僅生成令牌,而且還為后續其他對 $user 的操作做好了準備。
現在運行?$ vendor/bin/phpunit?如果操作正確,你應該進行所有綠色測試。
希望這能讓你深度了解在 TDD 在 Laravel 項目中的運行方式。 他絕對是一個比這更寬泛的概念,一個不受特地方法約束的概念。
雖然這種開發方法看起來比常見的調試后期程序要耗時, 但他很適合在代碼中盡早捕獲錯誤。雖然有些情況下非 TDD 方式會更有用,但習慣于 TDD 模式開發是一種可靠的技能和習慣。
本演練的全部代碼可參見 Github?here 倉庫。請隨意使用。
干杯!
文章轉自:https://learnku.com/laravel/t...
更多文章:https://learnku.com/laravel/c...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/8920.html
摘要:以及敏捷開發的先驅者之一的有句名言如果你沒有進行測試驅動開發,那么你應該正在做開發后堵漏的事今天我們將進行一場基于的測試驅動開發之旅。使用生成測試類。現在使用命令來生成模型并將其添加到我們的模型中。 showImg(https://segmentfault.com/img/remote/1460000018404936?w=1440&h=900); TDD 以及敏捷開發的先驅者之一的?...
摘要:關注的目標就是在代碼提交之后,順利且迅速的把新的功能部署到產品環境上。由于是,那么單元測試,回歸測試,集成測試,都是實現的手段。高質量的產品需求書和高質量的自動化集成測試用例毫無疑問,是高質量軟件的保證之一。 showImg(https://segmentfault.com/img/remote/1460000006877091?w=800&h=600); 什么是Test-Driven...
摘要:目前就職于,他在各種演講研討會和開發者大會上積極推廣測試驅動開發。問很多敏捷教練都表示訓練新人做測試驅動開發是一件辛苦而進度緩慢的事,并且收益也不是很大。首先是開發的對話式風格。第一個問題就是測試套件的速度。 Harry J.W. Percival目前就職于PythonAnywhere,他在各種演講、研討會和開發者大會上積極推廣測試驅動開發(TDD)。他在利物浦大學獲得計算機科學碩士學...
摘要:目前就職于,他在各種演講研討會和開發者大會上積極推廣測試驅動開發。問很多敏捷教練都表示訓練新人做測試驅動開發是一件辛苦而進度緩慢的事,并且收益也不是很大。首先是開發的對話式風格。第一個問題就是測試套件的速度。 Harry J.W. Percival目前就職于PythonAnywhere,他在各種演講、研討會和開發者大會上積極推廣測試驅動開發(TDD)。他在利物浦大學獲得計算機科學碩士學...
showImg(https://segmentfault.com/img/bV6aHV?w=1280&h=800); 社區優秀文章 Laravel 5.5+passport 放棄 dingo 開發 API 實戰,讓 API 開發更省心 - 自造車輪。 API 文檔神器 Swagger 介紹及在 PHP 項目中使用 - API 文檔撰寫方案 推薦 Laravel API 項目必須使用的 8 個...
閱讀 1166·2021-10-20 13:48
閱讀 2173·2021-09-30 09:47
閱讀 3103·2021-09-28 09:36
閱讀 2342·2019-08-30 15:56
閱讀 1195·2019-08-30 15:52
閱讀 2019·2019-08-30 10:48
閱讀 606·2019-08-29 15:04
閱讀 564·2019-08-29 12:54