iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 25
1
Software Development

函數式編程: 從 Elixir & Phoenix 入門。系列 第 25

測試與文件,and one more thing…

測試跟文件是許多 developer 最不想面對,卻也是非常重要的部份。 Elixir 在相關工具的整合上非常用心,讓大家能輕鬆愉快養成寫文件與測試的好習慣。由於這些工具大多都是 Elixir 標準函式庫,或是官方維護的函式庫,所以不限於 Phoenix,任何 Elixir 專案都可以使用,本篇要來介紹這些工具的用法。

ExUnit

一般 Elixir 專案

先從最基本的開始:Elixir 內建的測試框架叫做 ExUnit,只要建立一個 .exs* 檔案,並在模組宣告的下一行加入 use ExUnit.Case,就可以使用此測試框架提供的功能。依慣例大部份的測試檔案都會放在 專案名稱/test 下, 這裡是一個 Elixir 專案的測試檔範例:

defmodule ExampleTest do
  use ExUnit.Case

  test "the truth" do
    assert 1 + 1 == 2
  end
end

test 標註測試名稱,在 do...end 區塊裡放測試的內容。在測試的本體部份,則用 assert/1 來驗證。另外你也可以用 describe/2 來將測試分組。

而在 ExUnit.Assertions 文件下,可以找到驗證各種情況的函式,例如確保這個執行會有 exception (assert_raise/2)、確認兩個值之間的差小於特定範圍 (assert_in_delta/3) 等等。

Note *: 其實也可以用 .ex 檔案,但一般來說不會想要編譯並保存測試檔的 beam binary,所以慣例上是用 .exs

執行測試

在專案下,輸入 mix test 就可以執行專案下的所有測試案例:

$ mix test
....................

Finished in 0.2 seconds
20 tests, 0 failures

Randomized with seed 693717

Phoenix 中的微調

打開我們新建專案中的 test 資料夾,會發現測試檔也依專案結構放在不同的資料夾內。在 test/hello_phx/blog 裡的 blog_test.exs 前幾行長這樣:

# test/hello_phx/blog/blog_test.exs

defmodule HelloPhx.BlogTest do
  use HelloPhx.DataCase
  # ...

注意第二行並非我們之前所說的 use ExUnit.Case,而是掛了我們專案命名空間的 HelloPhx.DataCase。要了解這是什麼,得要看看 test/support/data_case.ex 這張檔案:

# test/support/data_case.ex

defmodule HelloPhx.DataCase do
  use ExUnit.CaseTemplate

  using do
    quote do
      alias HelloPhx.Repo

      import Ecto
      import Ecto.Changeset
      import Ecto.Query
      import HelloPhx.DataCase
    end
  end

  setup tags do
    # ...
  end

  def errors_on(changeset) do
    # ...
  end
end

從檔案的最上方可以查到這是使用了 ExUnit 的 CaseTemplate。這個功能可以讓你 (或框架) 定義不同的測試環境及共用測試函式。利用了 macro 的機制,上方 quote do...end 區塊裡的程式碼,會被嵌入到每一張 use 了這個模組的檔案裡。

而 Phoenix 幫你預設了三種情況:

  • DataCase 給需要與資料庫互動的測試用
  • ConnCase 給 web 類型,需要處理 connection 的測試用
  • ChannelCase 給 channel,也就是 websocket 相關的測試用

Property-base test

上面的那些都蠻無聊的,我知道。現在要來些有趣的了。許多人一想到測試程式,直覺浮現的大概就是這樣的程式碼,也就是 Elixir 自動幫你生成的那種:

test "length/1 calculates the length of a list" do
  assert length([]) == 0
  assert length([:one]) == 1
  assert length([1, 2, 3]) == 3
end

這種測試方式有個名字,叫做 example-based approach test。意思是我們測試的資料,是靠寫測試的人自己想出一些正常範例、及一些邊界範例,一個個把這些範例的測試結果寫下來。但這樣的缺點是有時你會漏掉邊界範例,有時實作改變時,當初想的範例還是正常的,但邊界條件已經改變了。

*啊要不然還能怎樣?*你可能會這樣想。

記得我們之前講過的惰性求值,Lazy evaluation 嗎?那個章節說到你可以擁有無限大的起始值。那麼放在測試裡,我們可以這樣想:你可以擁有無限大的測試資料,例如說:

對於任兩個正整數+/2 的結果必然比兩個數字都大。

這種測試做法稱之為 property-based testing,基於特性的測試。你給定測試資料的特性,測試時會自動生出符合該特性的多筆資料來執行測試。這種測試在 Haskell 及 Erlang 已行之有年,而 Elixir 基於原本 Dave Thomas 所寫的 pollution 實作了一個包含資料生成及測試語法的 StreamData,並預計在1.6 或 1.7 版正式併入標準函式庫。

而 property-based testing 的語法將會長這樣:

defmodule MyPropertyTest do
  use ExUnit.Case, async: true
  use ExUnitProperties

  property "sum of positive integer is greater than both integers" do
    check all a <- integer(),
              b <- integer(),
              a > 0 and b > 0,
              sum = a + b do
      assert sum > a
      assert sum > b            
    end
  end
end

文件

Elixir 的預設文件格式是 Markdown,只要在程式裡用 @moduledoc 或是 @doc就可以了,像這樣:

defmodule MyModule do
  @moduledoc """
      模組文件
  """

  @doc """
    函式文件
  """
  def my_function do
  end

而要生成文件,只要在 mix.exs 裡加入 ex_doc 這個 dependency:

def deps do
  [{:ex_doc, "~> 0.16", only: :dev, runtime: false}]
end

接著用 mix deps.get 安裝。再輸入 mix docs 就會自動生成文件了。

https://ithelp.ithome.com.tw/upload/images/20180115/20103390OFgbvh3Whx.png

所以你看到的 Elixir 函式庫文件都長同一個樣子,原因就是大家都用內建的工具來生成。好處就是查起來很順手,功能也非常齊全,不需要每次都重新適應。

One more thing… Doctest

在前面的說明藏了一個線索,說:「依慣例大部份的測試檔案都會放在 專案名稱/test 下。」,也就是說,並非所有的測試都會寫在那裡面。這個好東西叫做 doctest。因為 Elixir 的文件格式是 Markdown,而大家都知道 Markdown 可以內嵌程式碼,所以你可以在文件裡這樣寫:

defmodule Num do
  @doc """
  Demonstrate doctest feature
  ## Example
    iex> Num.is_even?(1)
    true
  """
  def is_even?(num) do
    rem(num, 2) == 0
  end
end

就可以生成這樣的文件 (注意程式碼的部份):

https://ithelp.ithome.com.tw/upload/images/20180115/20103390ZRKrD0Cy57.png

我知道,Num.is_even?(1) 應該是錯的,我是故意的

mix test 跑測試的時候,你會看到這樣的結果:

https://ithelp.ithome.com.tw/upload/images/20180115/201033902VADbWFvdo.png

沒錯,你寫在文件裡的 iex> 的程式碼會被執行,並用下一行的值做為測試比對的結果。之所以可以做到這件事,你可以打開 test/專案名稱.exs 測試檔來看,會發現這麼一行:

doctest 專案名稱

代表在執行測試時,會去翻實作的程式碼文件,找到裡面的程式範例來執行。

程式、文件跟測試都乾淨的寫在同一張檔案裡,是所謂三位一體

https://ithelp.ithome.com.tw/upload/images/20180115/20103390yetqntEZ6y.jpg

重點回顧

  • Elixir 的測試框架叫 ExUnit
  • Property-based testing StreamData 函式庫即將被併入
  • 文件用 Markdown 寫
  • Doctest 超好用

接下來會花一些篇幅來解釋 Phoenix 中的重要功能: Channel。
Happy hacking! 下次見。


上一篇
View 與 Template
下一篇
Channel.part_1
系列文
函數式編程: 從 Elixir & Phoenix 入門。31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
taiansu
iT邦新手 4 級 ‧ 2018-01-15 03:12:57

已更新

我要留言

立即登入留言