---
title: "初始化後端專案 - Cucumber 的文件測試法"
date: 2024-03-22T00:00:00+08:00
publishDate: 2024-03-22T00:00:00+08:00
lastmod: 2023-12-06T15:50:15+08:00
tags: ["Cucumber","教學","測試","後端","Ruby","Grape"]
series: "test-with-cucumber"
toc: true
permalink: "https://blog.aotoki.me/posts/2024/03/22/test-with-cucumber-setup-backend/"
language: "zh-tw"
---


前端的實作目前告一段落，我們將關注放到後端的部分。這次會直接使用 [Grape](https://github.com/ruby-grape/grape) 這個 Gem 來直接搭建 API 伺服器而不使用 Rails，並且刻意使用 Cucumber 來撰寫測試，在正常的狀況下只需要使用跟前端同一份即可，這次主要是展示針對後端可以怎樣實現。

<!--more-->

## 初始化{#initialize}

我們可以用 `bundle init` 建立一個空的 Ruby 的專案，並且安裝所需的 Gem 來搭建環境。

修改 `Gemfile` 加入我們需要使用到的套件。

```ruby
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'grape'
gem 'puma'
gem 'rack'

group :development do
  gem 'rubocop'
end

group :test do
  gem 'cucumber'
  gem 'rack-test'
  gem 'rspec'
end
```

運行 `bundle install` 安裝完畢後，我們再運行 `bundle exec cucumber --init` 將測試環境搭出來，並且加入一些用於測試的環境設定到 `features/support/env.rb` 裡面。

```ruby
ENV['RACK_ENV'] = 'test'

require_relative '../../app'
require 'rack/test'

def app
  Shop::API
end

World(Rack::Test::Methods)
```

這邊我們用 `require_relative` 去載入我們預定的主程式 `app.rb` 並且針對 `rack-test` 這個測試工具，定義一個在運行會用到的 `app` 方法，將 `Shop::API` 這個透過 Grape 來實現的 Rack 介面傳入，這樣我們就能在測試正常呼叫我們搭建的 API 伺服器。

## 撰寫測試{#write-tests}

跟前端一樣，我們會先撰寫一個初始的測試來確認環境設定正確，加入 `features/root.feature` 來確認可以正常的取的一個 JSON API 回應。

```gherkin
#language:zh-TW
功能: Root
  場景: 服務正常
    當 我開啟 "/"
    那麼 我會看到 JSON 屬性 "ok" 為真
```

這兩個步驟是還沒有被定義的，那麼繼續在 `features/step_definitions/common.rb` 裡面描述對應的行為。

```ruby
When('我開啟 {string}') do |path|
  get path
end

Then('我會看到 JSON 屬性 {string} 為真') do |json_path|
  json = JSON.parse(last_response.body)
  path = json_path.split('.')

  expect(json.dig(*path)).to be_truthy
end
```

在這邊用了一個小技巧來檢查 JSON 的資料，因為我們經常會有巢狀的 JSON 內容，那麼可以利用 Ruby 的 `#dig` 方法來取得某個值，舉例來說。

```json
{
  "name": "Aotoki",
  "job": {
    "title": "Senior Software Engineer",
    "department": "Cloud Team"
  }
}
```

想要拿到 `job` 裡面的 `title` 可以用 `json.dig('job', 'title')` 這樣做，但是在 Cucumber 裡面都是純文字內容，因此可以用 `job.title` 的格式來描述，我們只需要在步驟定義上用 `#split('.')` 拆開即可。

## 實作功能{#implementation}

最後，我們要加上 `app.rb` 以及給 Rack 使用的 `config.ru` 讓我們可以實際的運行這個伺服器。

首先，在 `config.ru` 加入以下內容。

```ruby
# frozen_string_literal: true

require_relative 'app'

run Shop::API
```

這樣待會就可以用 `bundle exec rackup` 自動以 Puma 當伺服器啟動我們的後端 API 伺服器。

然後繼續加入 `app.rb` 定義一個全新的 API 並且回應我們預期的內容。

```ruby
# frozen_string_literal: true

require 'bundler/setup'
Bundler.require(:default, ENV['RACK_ENV'].to_sym)

module Shop
  # :nodoc:
  class API < Grape::API
    format :json

    get do
      { ok: true }
    end
  end
end
```

完成一個最基礎的 API 實作後，我們就可以運行 `bundle exec cucumber` 來驗證結果，如果一切順利，就可以看到通過測試的訊息，我們就可以繼續後續跟前端整合的實作。

