摘要:寫一個,的是,的內容參照寫測試代碼三,然后寫上很明顯,這里測試的是,即和,是一個自定義的,主要功能就是實現了全部,并保存在文件里作為。
本文主要探討寫laravel integration/functional test cases時候,如何assert。前面幾篇文章主要聊了如何reseed測試數據,mock數據,本篇主要聊下assert的可行實踐,盡管laravel官方文檔聊了Testing JSON APIs,并提供了一些輔助的assert方法,如assertStatus(), assertJson()等等,但可行不實用,不建議這么做。
最佳需要是對api產生的response做更精細的assert。那如何是更精細的assertion?簡單一句就是把response code/headers/content 完整內容進行比對(assert)。 方法就是把response的內容存入json文件里作為baseline。OK,接下來聊下如何做。
寫一個AccountControllerTest,call的是/api/v1/accounts,AccountController的內容參照寫Laravel測試代碼(三),然后寫上integration/functional test cases:
assertApiIndex(); } public function testShow() { $this->assertApiShow(1); } }
很明顯,這里測試的是index/show api,即/api/v1/accounts和/api/v1/accounts/{account_id},AssertApiBaseline是一個自定義的trait,主要功能就是實現了assert 全部response,并保存在json文件里作為baseline。所以,重點就是AssertApiBaseline該如何寫,這里就直接貼代碼:
[ "D" => "DiJeb7IQHo8FOFkXulieyA", ], "api" => [ ], ]; private static $servers = [ "web" => [ "HTTP_ACCEPT" => "application/json", "HTTP_ORIGIN" => "https://test.company.com", "HTTP_REFERER" => "https://test.company.com", ], "api" => [ "HTTP_ACCEPT" => "application/json", ], ]; public static function assertJsonResponse(TestResponse $response, string $message = "", array $ignores = []): TestResponse { static::assertJsonResponseCode($response, $message); static::assertJsonResponseContent($response, $message); static::assertJsonResponseHeaders($response, $message); return $response; } public static function assertJsonResponseCode(TestResponse $response, string $message = ""): void { static::assert($response->getStatusCode(), $message); } public static function assertJsonResponseContent(TestResponse $response, string $message = "", array $ignores = []): void { static::assert($response->json(), $message); } public static function assertJsonResponseHeaders(TestResponse $response, string $message = ""): void { $headers = $response->headers->all(); $headers = array_except($headers, [ "date", "set-cookie", ]); // except useless headers static::assert($headers, $message); } public static function assert($actual, string $message = "", float $delta = 0.0, int $maxDepth = 10, bool $canonicalize = false, bool $ignoreCase = false): void { // assert $actual with $expected which is from baseline json file // if there is no baseline json file, put $actual data into baseline file (or -d rebase) // baseline file path // support multiple assertion in a test case static $assert_counters = []; static $baselines = []; $class = get_called_class(); $function = static::getFunctionName(); // "testIndex" $signature = "$class::$function"; if (!isset($assert_counters[$signature])) { $assert_counters[$signature] = 0; } else { $assert_counters[$signature]++; } $test_id = $assert_counters[$signature]; $baseline_path = static::getBaselinesPath($class, $function); if (!array_key_exists($signature, $baselines)) { if (file_exists($baseline_path) && array_search("rebase", $_SERVER["argv"], true) === false) { // "-d rebase" $baselines[$signature] = GuzzleHttpjson_decode(file_get_contents($baseline_path), true); } else { $baselines[$signature] = []; } } $actual = static::prepareActual($actual); if (array_key_exists($test_id, $baselines[$signature])) { static::assertEquals($baselines[$signature][$test_id], $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); } else { $baselines[$signature][$test_id] = $actual; file_put_contents($baseline_path, GuzzleHttpjson_encode($baselines[$signature], JSON_PRETTY_PRINT)); static::assertTrue(true); echo "R"; } } /** * @param string|string[]|null $route_parameters * @param array $parameters * * @return mixed */ protected function assertApiIndex($route_parameters = null, array $parameters = []) { return static::assertApiCall("index", $route_parameters ? (array) $route_parameters : null, $parameters); } protected function assertApiShow($route_parameters, array $parameters = []) { assert($route_parameters !== null, "$route_parameters cannot be null"); return static::assertApiCall("show", (array) $route_parameters, $parameters); } protected static function getFunctionName(): string { $stacks = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); do { $stack = array_pop($stacks); } while ($stack && substr($stack["function"], 0, 4) !== "test"); return $stack["function"]; // "testList" } protected static function getBaselinesPath(string $class, string $function): string { $class = explode("", $class); $dir = implode("/", array_merge( [strtolower($class[0])], array_slice($class, 1, -1), ["_baseline", array_pop($class)] )); if (!file_exists($dir)) { mkdir($dir, 0755, true); } return base_path() . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . $function . ".json"; } protected static function prepareActual($actual) { if ($actual instanceof Arrayable) { $actual = $actual->toArray(); } if (is_array($actual)) { array_walk_recursive($actual, function (&$value, $key): void { if ($value instanceof Arrayable) { $value = $value->toArray(); } elseif ($value instanceof Carbon) { $value = "Carbon:" . $value->toIso8601String(); } elseif (in_array($key, ["created_at", "updated_at", "deleted_at"], true)) { $value = Carbon::now()->format(DATE_RFC3339); } }); } return $actual; } private function assertApiCall(string $route_action, array $route_parameters = null, array $parameters = []) { [$uri, $method] = static::resolveRouteUrlAndMethod(static::resolveRouteName($route_action), $route_parameters); /** @var IlluminateFoundationTestingTestResponse $response */ $response = $this->call($method, $uri, $parameters, $this->getCookies(), [], $this->getServers(), null); return static::assertJsonResponse($response, ""); } private static function resolveRouteName(string $route_action): string { return static::ROUTE_NAME . "." . $route_action; } private static function resolveRouteUrlAndMethod(string $route_name, array $route_parameters = null) { $route = Route::getRoutes()->getByName($route_name); assert($route, "Route [$route_name] must be existed."); return [route($route_name, $route_parameters), $route->methods()[0]]; } private function getCookies(array $overrides = []): array { $cookies = $overrides + self::$cookies[static::$middlewareGroup]; return $cookies; } private function getServers(array $overrides = []): array { return $overrides + self::$servers[static::$middlewareGroup]; } }
雖然AssertApiBaseline有點長,但重點只有assert()方法,該方法實現了:
如果初始沒有baseline文件,就把response內容存入json文件
如果有json文件,就拿baseline作為expected data,來和本次api產生的response內容即actual data做assertion
如果有"rebase"指令表示本次api產生的response作為新的baseline存入json文件中
支持一個test case里執行多次assert()方法
所以,當執行phpunit指令后會生成對應的baseline文件:
OK,首次執行的時候重新生成baseline文件,查看是不是想要的結果,以后每次改動該api后,如果手滑寫錯了api,如response content是空,這時候執行測試時會把baseline作為expected data和錯誤actual data 進行assert就報錯,很容易知道代碼寫錯了;如果git diff知道最新的response 就是想要的(如也無需求需要把"name"換另一個),就phpunit -d rebase 把新的response作為新的baseline就行。。
這比laravel文檔中說明的寫json api test cases的優點在哪?就是對response做了精細控制。對response 的status code,headers,尤其是response content做了精細控制(content的每一個字段都行了assert對比)。
這是我們這邊寫api test cases的實踐,有疑問可留言交流。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30695.html
摘要:用也有三四個月了,雖然是兼職開發,但是使用的頻率非常之高,畢竟是產品化的一個項目。第二階段數據庫和開發了比較多的功能之后,會發現需要大量的測試數據,這時候和就該大顯身手了。 用Laravel也有三四個月了,雖然是兼職開發,但是使用的頻率非常之高,畢竟是產品化的一個項目。在這期間,也踩了無數的坑,走了很多彎路,所以準備把最近的感悟記錄下來,方便后來者。 第一階段:簡單的增刪改查 這是最...
摘要:最適合入門的初級教程五路由咱會創建了控制器也有了接下來要搞的就是把兩者關聯起來了最適合入門的初級教程三我們講過的或者方法第一個參數就是我們要定義的路由就是我們在地址欄請求的那段第二個參數可以是一個閉包函數里面寫請求定義的路由時執行的內容上篇 最適合入門的Laravel初級教程(五) 路由咱會創建了; 控制器也有了;接下來要搞的就是把兩者關聯起來了;最適合入門的laravel初級教程(三...
摘要:值得一提的是擴展包不免費用于商業用途,作者用一種人類友好的方式說你使用這個擴展包就是應該去掙錢的,而不是免費的去工作這個擴展包收費美元。除了這些,還有五個沒有全面的審查的擴展包。最后,還有三個優質的包選擇于。 showImg(https://segmentfault.com/img/remote/1460000012312105?w=2200&h=1125); 開發者們都是懶惰的,不,...
摘要:值得一提的是擴展包不免費用于商業用途,作者用一種人類友好的方式說你使用這個擴展包就是應該去掙錢的,而不是免費的去工作這個擴展包收費美元。除了這些,還有五個沒有全面的審查的擴展包。最后,還有三個優質的包選擇于。 showImg(https://segmentfault.com/img/remote/1460000012312105?w=2200&h=1125); 開發者們都是懶惰的,不,...
摘要:原文來自免費視頻教程地址期間受到很多私事影響,終于還是要好好寫寫的教程了。我們來實現這個功能顯示文章詳情通過文章展示來快速體驗上面的流程注冊路由來到中,我們增加一個路由上面的路由指定我們需要加載中的方法。 原文來自: https://jellybool.com/post/programming-with-laravel-5-model-controller-view-basic-wor...
閱讀 1176·2021-10-11 10:59
閱讀 1963·2021-09-29 09:44
閱讀 853·2021-09-01 10:32
閱讀 1424·2019-08-30 14:21
閱讀 1870·2019-08-29 15:39
閱讀 2973·2019-08-29 13:45
閱讀 3532·2019-08-29 13:27
閱讀 2006·2019-08-29 12:27