成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

寫 Laravel 測試代碼(五)

xbynet / 3071人閱讀

摘要:寫一個,的是,的內(nèi)容參照寫測試代碼三,然后寫上很明顯,這里測試的是,即和,是一個自定義的,主要功能就是實(shí)現(xiàn)了全部,并保存在文件里作為。

本文主要探討寫laravel integration/functional test cases時(shí)候,如何assert。前面幾篇文章主要聊了如何reseed測試數(shù)據(jù),mock數(shù)據(jù),本篇主要聊下assert的可行實(shí)踐,盡管laravel官方文檔聊了Testing JSON APIs,并提供了一些輔助的assert方法,如assertStatus(), assertJson()等等,但可行不實(shí)用,不建議這么做。

最佳需要是對api產(chǎn)生的response做更精細(xì)的assert。那如何是更精細(xì)的assertion?簡單一句就是把response code/headers/content 完整內(nèi)容進(jìn)行比對(assert)。 方法就是把response的內(nèi)容存入json文件里作為baseline。OK,接下來聊下如何做。

寫一個AccountControllerTest,call的是/api/v1/accounts,AccountController的內(nèi)容參照寫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,主要功能就是實(shí)現(xiàn)了assert 全部response,并保存在json文件里作為baseline。所以,重點(diǎn)就是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有點(diǎn)長,但重點(diǎn)只有assert()方法,該方法實(shí)現(xiàn)了:

如果初始沒有baseline文件,就把response內(nèi)容存入json文件

如果有json文件,就拿baseline作為expected data,來和本次api產(chǎn)生的response內(nèi)容即actual data做assertion

如果有"rebase"指令表示本次api產(chǎn)生的response作為新的baseline存入json文件中

支持一個test case里執(zhí)行多次assert()方法

所以,當(dāng)執(zhí)行phpunit指令后會生成對應(yīng)的baseline文件:

OK,首次執(zhí)行的時(shí)候重新生成baseline文件,查看是不是想要的結(jié)果,以后每次改動該api后,如果手滑寫錯了api,如response content是空,這時(shí)候執(zhí)行測試時(shí)會把baseline作為expected data和錯誤actual data 進(jìn)行assert就報(bào)錯,很容易知道代碼寫錯了;如果git diff知道最新的response 就是想要的(如也無需求需要把"name"換另一個),就phpunit -d rebase 把新的response作為新的baseline就行。。

這比laravel文檔中說明的寫json api test cases的優(yōu)點(diǎn)在哪?就是對response做了精細(xì)控制。對response 的status code,headers,尤其是response content做了精細(xì)控制(content的每一個字段都行了assert對比)。
這是我們這邊寫api test cases的實(shí)踐,有疑問可留言交流。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/30695.html

相關(guān)文章

  • Laravel 菜鳥晉級之路

    摘要:用也有三四個月了,雖然是兼職開發(fā),但是使用的頻率非常之高,畢竟是產(chǎn)品化的一個項(xiàng)目。第二階段數(shù)據(jù)庫和開發(fā)了比較多的功能之后,會發(fā)現(xiàn)需要大量的測試數(shù)據(jù),這時(shí)候和就該大顯身手了。 用Laravel也有三四個月了,雖然是兼職開發(fā),但是使用的頻率非常之高,畢竟是產(chǎn)品化的一個項(xiàng)目。在這期間,也踩了無數(shù)的坑,走了很多彎路,所以準(zhǔn)備把最近的感悟記錄下來,方便后來者。 第一階段:簡單的增刪改查 這是最...

    YacaToy 評論0 收藏0
  • 最適合入門的Laravel初級教程()

    摘要:最適合入門的初級教程五路由咱會創(chuàng)建了控制器也有了接下來要搞的就是把兩者關(guān)聯(lián)起來了最適合入門的初級教程三我們講過的或者方法第一個參數(shù)就是我們要定義的路由就是我們在地址欄請求的那段第二個參數(shù)可以是一個閉包函數(shù)里面寫請求定義的路由時(shí)執(zhí)行的內(nèi)容上篇 最適合入門的Laravel初級教程(五) 路由咱會創(chuàng)建了; 控制器也有了;接下來要搞的就是把兩者關(guān)聯(lián)起來了;最適合入門的laravel初級教程(三...

    kamushin233 評論0 收藏0
  • 13 個快速構(gòu)建 Laravel 后臺的擴(kuò)展包

    摘要:值得一提的是擴(kuò)展包不免費(fèi)用于商業(yè)用途,作者用一種人類友好的方式說你使用這個擴(kuò)展包就是應(yīng)該去掙錢的,而不是免費(fèi)的去工作這個擴(kuò)展包收費(fèi)美元。除了這些,還有五個沒有全面的審查的擴(kuò)展包。最后,還有三個優(yōu)質(zhì)的包選擇于。 showImg(https://segmentfault.com/img/remote/1460000012312105?w=2200&h=1125); 開發(fā)者們都是懶惰的,不,...

    MiracleWong 評論0 收藏0
  • 13 個快速構(gòu)建 Laravel 后臺的擴(kuò)展包

    摘要:值得一提的是擴(kuò)展包不免費(fèi)用于商業(yè)用途,作者用一種人類友好的方式說你使用這個擴(kuò)展包就是應(yīng)該去掙錢的,而不是免費(fèi)的去工作這個擴(kuò)展包收費(fèi)美元。除了這些,還有五個沒有全面的審查的擴(kuò)展包。最后,還有三個優(yōu)質(zhì)的包選擇于。 showImg(https://segmentfault.com/img/remote/1460000012312105?w=2200&h=1125); 開發(fā)者們都是懶惰的,不,...

    ityouknow 評論0 收藏0
  • Laravel 5系列教程:MVC的基本流程

    摘要:原文來自免費(fèi)視頻教程地址期間受到很多私事影響,終于還是要好好寫寫的教程了。我們來實(shí)現(xiàn)這個功能顯示文章詳情通過文章展示來快速體驗(yàn)上面的流程注冊路由來到中,我們增加一個路由上面的路由指定我們需要加載中的方法。 原文來自: https://jellybool.com/post/programming-with-laravel-5-model-controller-view-basic-wor...

    mrcode 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<