DEV Community

Cover image for ใช้ Dredd เทส API Docs ที่สร้างจาก Agilo ซึ่ง include .apib หลายๆไฟล์ กับ Laravel
Atthaphon Urairat
Atthaphon Urairat

Posted on

ใช้ Dredd เทส API Docs ที่สร้างจาก Agilo ซึ่ง include .apib หลายๆไฟล์ กับ Laravel

ในบทความนี้จะพูดถึงเรื่อง การเขียน artisan command สำหรับรันคำสั่งของ package ของ
Aglio เพื่อ complie ไฟล์แยกย่อยต่างๆที่เราเขียนสำหรับสร้าง Api Docs
ของเราให้เป็นไฟล์ไฟล์เดียว เพื่อที่จะทำให้เราสามารถใช้ dredd
ในการตรวจสอบความถูกต้องของ endpoints ที่เราได้สร้างขึ้นและเอาไปเทียบกับตัว endpoints ที่ได้ออกแบบไว้ใน Api
Docs ของเรา

ในบทความนี้เราใช้อะไรกันบ้าง

จากปัญหาที่พบในการสร้าง API ที่มีความยาวพอสมควร

ปัญหานี้เป็นประสบการณ์ตรงที่ผมเคยได้เจอเมื่อต้องทำการสร้าง Api Docs
ที่มีความยาวมากพอสมควร ตอนเริ่มสร้าง Api เราก็จะมี Api ไม่กี่ endpoins ใช่ไหมครับ
แต่เมื่อเราพัฒนาโปรเจ็คต่อไปเรื่อยๆ การเพิ่ม Api ก็เลยทำให้ไฟล์ .apib ของเรา
เริ่มบวมขึ้นบวมขึ้นเรื่อยๆ หรือในบางกรณีที่มีการออกแบบ Api ทั้งหมดก่อนการพัฒนา ก็เช่นเดียวกัน

ในหลายๆครั้งเมื่อเราต้องเลื่อนหน้าจะลงไปเรื่อยๆ หรือกำลังพิมพ์อยู่
แต่บังเอิญมือเราบังเอิญไปแตะเข้ากับ track pad จนทำให้ cursor
ในหน้าจอกระเด็นไปอยู่ส่วนไหนก็ไม่ทราบ
ทำให้เราต้องใช้สายตามองหาบรรทัดที่กำลังทำงานกันอยู่กันใหม่
บางครั้งรีบๆ ก็เขียนมั่วบรรทัดกันไป เอ้าสนุกล่ะทีนี้ ship high! เลยครับ

ดังนั้นผมเลยหันมาใช้ Including Files <!-- include(filename{.apib|.md|.json}) -->
เพื่อแยกการเขียน endpoints แต่ละ endpoints ออกจากกันไปไว้ในไฟล์ใหม่
เรื่องราวกำลังจะดีอยู่แล้วเชียวครับท่านผู้ชม

แต่เมื่อเราต้องทำการ testing สำหรับตรวจสอบว่า endpoints ที่เราออกแบบไว้ใน
Api Docs และ endpoints ที่เราสร้างมามันคายค่าออกมาเหมือนที่เราได้ออกไว้หรือเปล่า
ผมใช้ dredd ในการตรวจสอบสำหรับกระบวนการนี้ แต่ ณ เวลานี้ dredd
ไม่ซัพพอร์ตการตรวจสอบ API ที่มีการแยกไฟล์ต่างหากนะสิครับ แย่เลยเรา

ดังนั้นผมจึงต้องหาวิธีดัดแปลงอะไรอะไรนิดหน่อยเพื่อไม่ให้ เจ้า
เอกสารที่สร้างแบบแยกส่วนไว้หมดประโยชน์

ผมจึงคิดว่ามันน่าจะมีประโยชน์กับคนอื่นบ้าง เลยเอามาแปะไว้ ณ ที่นี้ครับ

เริ่มงานกันเลย

ติดตั้ง composer

ถ้าใครติดตั้งแล้วก็ให้ข้ามไปได้เลยครับ

$ curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/bin --filename=composer

ติดตั้ง npm

ถ้าใครติดตั้งแล้วก็ให้ข้ามไปได้เลยครับ

ถ้าใครยังไม่ได้ติดตั้งก็ให้ ดาวน์โหลด Node.js
มาติดตั้งก่อนนะครับ จากนั้นก็

$ npm install npm@latest -g

ติดตั้ง laravel

ถ้าใครติดตั้งแล้วก็ให้ข้ามไปได้เลยครับ

ผมจะรันคำสั่งสำหรับสร้างโปรเจ็คเลยนะครับ ถ้าเพื่อนๆยังไม่ install laravel
ก็ให้ไปดูวิธีการติดตั้งได้จาก Laravel

เรามาสร้าง โปรเจ็คใหม่ชื่อ dejo กันครับ ผมใช้งาน version 5.4.xx นะครับ

$ composer create-project --prefer-dist laravel/laravel dejo

ติดตั้ง aglio

ถ้าใครติดตั้งแล้วก็ให้ข้ามไปได้เลยครับ

$ npm install -g aglio

ติดตั้ง dredd

ถ้าใครติดตั้งแล้วก็ให้ข้ามไปได้เลยครับ

$ npm install -g dredd

ลงมือเขียน Api Docs

เราใช้ Apiblueprint
สำหรับรูปแบบการเขียนกันนะครับ
สามารถเข้าไปดูตัวอย่างการเขียนรูปแบบต่างได้ตามลิงค์ที่แปะไว้เลยครับ

จากนั้นก็ให้เราสร้าง โฟลเดอร์ docs สำหรับวาง Api Docs ของเรากันก่อน
โดยเริ่มจากการเข้าไปยัง โฟลเดอร์ dejo ที่เราได้สร้างไปแล้วก่อนหน้านี้

$ cd path/to/dejo

จากนั้นก็สร้างโฟลเดอร์ docs และเข้าไปยัง public โฟลเดอร์

$ mkdir public/docs && cd public/docs

แล้วก็สร้างไฟล์ขึ้นมา 3 ไฟล์ ไฟล์แรกคือ document.apib
ใช้สำหรับเป็นไฟล์หลักที่จะเอาเรียกไฟล์ include ต่าง ไฟล์ที่สองและไฟล์ที่สาม one.apib
two.apib คือไฟล์ include ที่เราทำการแยกไว้สำหรับเขียน Api endpoints แยกกัน

$ touch document.apib one.apib two.apib

จากนั้นก็เราก็ลองเขียน Api Docs กันเลยครับ

document.apib

FORMAT: 1A
HOST: http://dejo.localhost

# Dejo API

Dejo API ใช้สำหรับ [ใช้ Dredd เทส API Docs ที่สร้างจาก Agilo หลายๆไฟล์ ใน Laravel](https://atthaphon.urairat.me/2018/01/25/ใช้-dredd-เทส-api-docs-ที่สร้างจาก-agilo-หลายๆไฟล์-ใน-laravel/)

# Group One
ใช้รับค่าจากฟีเจอร์ One

## One List [/one]
Retrieving a list of available feature one of Dejo application.

<!-- include(one.apib) -->

# Group Two
ใช้รับค่าจากฟีเจอร์ Two

## Two List [/two]
Retrieving a list of available feature Two of Dejo application.

<!-- include(two.apib) -->

one.apib

### Get a list one feature [GET]

ตัวอย่างการคืนค่า สำหรับทบความจาก [ใช้ Dredd เทส API Docs ที่สร้างจาก Agilo หลายๆไฟล์ ใน Laravel](https://atthaphon.urairat.me/2018/01/25/ใช้-dredd-เทส-api-docs-ที่สร้างจาก-agilo-หลายๆไฟล์-ใน-laravel/)

+ Response 200 (application/json)

    + Body

            {
                    "data": [
                        {
                                "id": 1,
                                "name": "Sexy Name"
                        }
                    ]
            }

two.apib

### Get a list two feature [GET]

ตัวอย่างการคืนค่า สำหรับทบความจาก [ใช้ Dredd เทส API Docs ที่สร้างจาก Agilo หลายๆไฟล์ ใน Laravel](https://atthaphon.urairat.me/2018/01/25/ใช้-dredd-เทส-api-docs-ที่สร้างจาก-agilo-หลายๆไฟล์-ใน-laravel/)

+ Response 200 (application/json)

    + Body

            {
                    "data": [
                        {
                                "id": 1,
                                "name": "Naruto Uzumaki",
                                "title": "Hokage"
                        }
                    ]
            }


เสร็จแล้วครับ Api Docs ดิบๆของเราแต่ยังไม่เสร็จนะครับ เราต้องสร้าง .html ไฟลให้กับ
docs ของเราซะก่อน และอย่าลืมสร้าง route ให้กับ Api Docs ของเราด้วย

Aglio สำหรับแปลง .apib เป็น .html

เราจะทำการแปลงเอกสาร ที่เราสร้างจากไฟล์ .apib ที่มีการเขียน markdown และมีการ
include ซึ่งนั่นเป็น syntax สำหรับ Aglio เพื่อใช้ในการ map files ในระหว่าง
generate เพื่อให้ออกมาเป็น ไฟล์ .html สำหรับให้เราใช้เพื่อโชว์หน้า Api Docs
ของเราครับ

เมื่อทุกอย่างเรียบร้อยแล้วก็รันคำสั่ง สำหรับสร้างเอกสาร .html ไฟล์กันเลยครับ

  • i คือ input ไฟล์
  • o คือ output ไฟล์
$ aglio -i document.apib -o document.html

จากนั้นเราก็จะไปสร้าง route ใน laravel สำหรับใช้ดู Api docs ของเรา

สร้าง Route สำหรับเปิดดู Api Docs ของเรา

สร้าง route สำหรับ เปิดไฟล์ document.html ที่ Aglio สร้างมาให้กับเราเมื่อซักครู่
ซึ่งไฟล์นั้นถูกจัดเก็บไว้ใน ที่เดียวกับไฟล์ document.apib

โดยการเพิ่ม route ใน ไฟล์ routes/web.php

<?php
...

Route::get('document/', function () {
    return File::get(public_path() . '/docs/document.html');
});

จากนั้นเราก็ลองเปิด url เราดูก็จะได้หน้าตา Api Docs แบบนี้ครับ

Run Dredd With Aglio Multiple Blueprint Files

สร้าง Endpoints สำหรับ /one และ /two

ถ้าเราต้องการเทส Api Docs เราจึงเป็นที่จะต้องมี endpoints ที่คายค่าออกมาได้ตรงกับ Api
Docs ที่เราได้เขียนไปใช่ไหมครับ ดังนั้นเรามาเขียน routes อย่างง่าย
เพื่อจำลองการคายค่า สำหรับเอาไว้เทสกันดีกว่า

<?php
...

Route::get('one/', function () {
    $data = ['data' => [['id'=>1, "name"=> "Sexy Name"]]];

    return response()->json($data);
});

Route::get('two/', function () {
    $data = ['data' =>[["id" => 1, "name" => "Naruto Uzumaki", "title" => "Hokage"]]];

    return response()->json($data);
});

เทส API Docs ด้วย Dredd

ปัญหามันอยู่ตรงนี้ครับ เมื่อเราทำการ include .apib มันจะทำให้ dredd
ไม่สามารถตรวจสอบเอกสารที่ถูก include เข้ามาได้

ก่อนอื่นเราก็มาทำการ ตั้งค่าเบื้องต้นสำหรับ dredd ของเรากันก่อน
เราต้อง cd path/to/dejo เพื่อไปยัง root โฟลเดอร์ของโปรเจ็คเราก่อน จากนั้น

$ dredd init
? Location of the API description document ./public/docs/document.apib
? Command to start API backend server e.g. (bundle exec rails server)
? URL of tested API endpoint: http://dejo.localhost
? Programming language of hooks:
❯ php
  perl
  go
  ...
? Do you want to use Apiary test inspector? No
? Dredd is best served with Continuous Integration. Create CircleCI config for Dredd? No

เราก็จะได้ไฟล์ dredd.yml มา
ต่อไปเราก็ทำการ test ด้วย dredd โดยพิมพ์คำสั่ง

$ dredd
info: Configuration './dredd.yml' found, ignoring other arguments.
info: Beginning Dredd testing...
complete: Tests took 5ms

เราจะเห็นได้ว่า ไม่มีการคืนค่า test ออกมาเลย ก็เพราะ dredd หา endpoint ที่เราสร้างไว้ไม่เจอ
นั่นเป็นเพราะ dredd ไม่รองรับไฟล์แยกซึ่งเป็น feature ที่ช่วยอำนวยความสะดวกในการสร้าง Api Docs ที่มีอยู่ใน Aglio ของเรา
ดังนั้น เราจึงต้องทำการ compile ไฟล์ทั้งหมดให้เป็นไฟล์เดียวซะก่อน ก่อนที่จะทำการรันเทสด้วย dredd อีกครั้ง

สร้าง Laravel aritisan command สำหรับ คอมไฟล์และรันเทส

ไอเดียคือ เราจะสร้าง artisan command line เพื่อช่วยให้ชีวิตเราดีขึ้น
ด้วยการแพ็ครวมคำสั่งที่เราจำเป็นต้องใช้ให้อยู่ในการรันคำสั่งแค่ครั้งเดียวเท่านั้น

  1. สร้าง API docs ในไฟล์ .html
  2. สร้างไฟล์ .apib อีกตัวที่รวมเอา ไฟล์ include ทุกๆไฟล์ที่เรียกใช้จากไฟล์หลักมารวมกันอยู่ในไฟล์เดียว
  3. run dredd เพื่อเทส Api Docs กับ endpoint ที่เราสร้างขึ้น

เราจะได้ไม่ต้องมานั่งพิมพ์คำสั่งหลายๆครั้ง

สร้าง artisan command กันเลย

เปิด iterm อีกเช่นเคยแล้วจากนั้นก็

$ php artisan make:command ApiDocument

เราจะได้ไฟล์ ชื่อ ApiDocument.php มา ซึ่งไฟล์ใหม่นี้จะอยู่ในโฟลเดอร์
app/console/commnads/ApiDocument.php ให้เราเปิดมันขึ้นมา
แล้วเขียนโค้ดใหม่ลงไปแทน

ApiDocument.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

class ApiDocument extends Command
{
    protected $signature = 'dejo:document';

    protected $description = 'Dejo\'s command to generate API documentation, also create compile .apib for test purpose  and run  dredd command to test our Api documentation.';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $this->rendererDoc();
        $this->compileDoc();
        $this->runDreddTesting();
    }

    private function rendererDoc()
    {
        $apibFile = public_path()."/docs/document.apib";

        $docsFile = public_path()."/docs/document.html";

        $process = new Process("aglio -i $apibFile -o $docsFile");

        $process->run();

        try {
            $process->mustRun();

            $this->info("Dejo renderer documentation completed...");
        } catch (ProcessFailedException $e) {
            $this->info($e->getMessage());
        }
    }

    private function compileDoc()
    {
        $apibFile = public_path()."/docs/document.apib";

        $compileFile = public_path()."/docs/compile.apib";

        $process = new Process("aglio -i $apibFile --compile -o $compileFile");

        $process->run();

        try {
            $process->mustRun();

            $this->info("Dejo compile blue print file completed...");
        } catch (ProcessFailedException $e) {
            $this->info($e->getMessage());
        }
    }

    private function runDreddTesting()
    {
        $process = new Process("dredd");

        $process->run();

        try {
            $process->mustRun();

            $this->info("Dredd is running please wait...");

            $this->info($process->getOutput());

        } catch (ProcessFailedException $e) {
            $this->info($e->getMessage());
        }
    }

}

Register Command ของเราด้วย

หลังจากสร้าง command แล้วเรายังต้อง ทำการแนะนำให้ laravel
รู้จักกับคำสั่งใหม่นี้ด้วยซะก่อน เราจึงจะสามารถใช้งานมันได้

ให้เราเปิดไฟล์ app/Console/Commands/Kernel.php และเพิ่มบรรทัดนี้เข้าไป

<?php
...

class Kernel extends ConsoleKernel
{

    protected $commands = [
        Commands\ApiDocument::class, // แนะนำตัวกับ kernel ให้เจ้าบ้าน Laravel เรียกใช้งานเราได้
    ];

    ...
}

ตั้งค่า dredd.yml ใหม่อีกครั้งสำหรับเทส

จากตัวอย่างโค้ดใหม่ใน command ที่เราได้สร้างขี้น จะเห็นได้ว่า ในฟังชั่น
compileDoc() เราได้ทำการสร้าง compile ไฟล์ขึ้นมาใหม่อีกหนึ่งตัว ชื่อว่า compile.apib
ไฟล์ตัวนี้เองที่เราได้ทำการรวมเอา include ทุกตัวเข้ามาแพ็ครวมกันไว้ทั้งหมดเพื่อที่ว่า Dredd
จะได้มองเห็น Endpoints ของเราได้ซักที

ดังนั้นเราจะเปลี่ยนค่าตัวแปรใน dredd.yml เพื่อให้ dredd อ่าน ไฟล์ compile.apib
แทน document.apib

...
# เปลี่ยนจาก ./public/docs/document.apib เป็น ./public/docs/compile.apib
blueprint: ./public/docs/compile.apib

ทดสอบ command กันดีกว่า

ใกล้จะเสร็จแล้วครับ ในที่สุดก็ได้ลองของใหม่กันจริงๆเสียที
เริ่มทดสอบโค้ดที่เราเขียนมากันดีกว่า

$ php artisan dejo:document
Dejo renderer documentation completed...
Dejo compile blue print file completed...
Dredd is running please wait...
info: Configuration './dredd.yml' found, ignoring other arguments.
info: Beginning Dredd testing...
pass: GET (200) /one duration: 391ms
pass: GET (200) /two duration: 286ms
complete: 2 passing, 0 failing, 0 errors, 0 skipped, 2 total
complete: Tests took 695ms

เรียบร้อยแล้วครับ สำเร็จลุล่วงไปด้วยดี ตอนนี้ก็ทำการพัฒนาโปรเจ็คต่อไปได้เลย

สรุปแบบรวดรัด

เราต้องการแยกการเขียน Api Docs ออกเป็นส่วนๆเพื่อให้ง่ายต่อการต่อยอดเพิ่มเติม
แต่เครื่องมือที่เลือกมาใช้นั้นมีข้อจำกัดบางอย่าง ดังนั้นเราจำเป็นต้องหาวิธีแก้ไขตามอัตภาพ

Top comments (0)