ใช้ ExMachina ช่วยเตรียมข้อมูลสำหรับ test ของ Elixir
Tags: elixir, programming
Published: 2021-01-21 00:47:58

[ExMachina](https://hexdocs.pm/ex_machina/readme.html) เป็น library ที่ช่วยให้เราจัดเตรียมข้อมูลสำหรับทดสอบให้แยกออกมาอยู่ที่เดียวกัน ทำให้โค้ดที่เขาเขียนเทสจริงๆไม่รกและเราเอาสิ่งที่แยกมาไปใช้ได้ในหลายๆเทสเคสได้

การจะใช้งาน ExMachina ขั้นแรกเราเพิ่ม dependency ให้กับ `mix.exs` ของเราก่อนแบบนี้

```elixir
{:ex_machina, "~> 2.5.0"}
```

เราสามารถเขียนโค้ดของ module ที่เป็น Factory module ในการจัดเตรียม test data ไว้ที่ไหนก็ได้ แต่จาก document ของ ExMachina เนี่ยแนะนำว่าให้เอาไว้ที่ `test/support` directory

แต่ว่าพอเอาไว้ในนี้ต้องไม่ลืมเพิ่ม elixir_paths ให้กับ application config ของ `mix.exs` ด้วยไม่งั้น compiler มันจะไม่ compile โค้ดในนี้ เรา config แบบนี้

```elixir
  def project do
    [
      app: :ecto_sample,
      version: "0.1.0",
      elixir: "~> 1.11",
      start_permanent: Mix.env() == :prod,
      deps: deps(),
      # ตรงนี้ให้ไปเรียก `elixir_paths` เพื่อ config ขึ้นอยู่กับแต่ละ Mix.env
      elixirc_paths: elixirc_paths(Mix.env()), ()
      aliases: aliases()
    ]
  end

  # ถ้าเป็น :test env ก็ให้ compile ของใน `test/support` ด้วย
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]
```

ส่วนวิธีการเขียนโค้ด factory module นั้นก็ง่ายๆคือให้สร้าง module แล้ว use `ExMachina` หรือ `ExMachina.Ecto` ถ้าเราใช้เตรียมข้อมูลให้กับ Ecto Schema โดยต้อง config repo module ให้ด้วย

จากนั้นก็สร้าง function ที่ลงท้ายด้วย `_factory` ซึ่งจะเป็นฟังก์ชันที่เราไปใช้กับ `build`, `insert` method ของ ExMachina ได้ ตัวอย่างเช่น

```elixir
defmodule EctoSample.Factory do
  use ExMachina.Ecto, repo: EctoSample.Repo

  def post_factory do
    %EctoSample.Post{
      title: "Sample use ExMachina",
      body: "Example to prepare fixture data for testing"
    }
  end
end
```

ผมก็เขียนฟังก์ชัน `post_factory` ซึ่งก็แค่เตรียมข้อมูลของ `EctoSample.Post` schema เอาไว้ ทีนี้ตอนใช้งานใน test case สิ่งที่เราทำก็คือใช้ ExMachina ฟังก์ชันอย่าง `build` เพื่อสร้างค่าตาม factory function หรือใช้ `insert` เพื่อ insert ค่าลง DB ตาม factory function ตัวอย่างเช่น

```elixir
defmodule EctoSampleTest do
  use ExUnit.Case

  # ต้อง import factory module ที่ต้องการใช้เข้ามาก่อน
  import EctoSample.Factory

  test "build post by ExMachina Factory" do
    post = build(:post)
    {:ok, post} = EctoSample.Repo.insert(post)
    assert post.title == "Sample use ExMachina"
    assert post.body == "Example to prepare fixture data for testing"
  end

  test "insert post by ExMachina Factory" do
    post = insert(:post)
    assert post.title == "Sample use ExMachina"
    assert post.body == "Example to prepare fixture data for testing"
  end
end
```

ซึ่งค่าที่ส่งให้ `build` กับ `insert` ก็คือชื่อของ factory ชื่อของฟังก์ชันที่อยู่ก่อนคำว่า `_factory` นั่นเอง

ทีนี้ถ้าเทสเคสของเรามีข้อมูลที่ต้องการปรับจากค่าที่เตรียมไว้ใน factory ทั้ง `insert` และ `build` สามารถรับค่า key: value เพิ่มไปได้เพื่อเปลี่ยนแค่บาง field เช่นอ

```elixir
  test "insert post by ExMachina Factory with title Hello ExMachina" do
    post = insert(:post, title: "Hello ExMachina")
    assert post.title == "Hello ExMachina"
    assert post.body == "Example to prepare fixture data for testing"
  end
```

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


<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>