Membuat Pengujian HTML di dalam Controller Phoenix
21 Mar 2016- Dokumen asal: Testing HTML in Phoenix Controllers
- Penulis asal: Paul Smith
Phoenix mempunyai beberapa helper untuk kegunaan pengujian sambutan HTML. Penjana yang siap tersedia di dalam Phoenix memberikan cara yang baik untuk bermula dengan proses pengujian HTML, tetapi diharapkan panduan ini akan memberikan penjelasan yang agak lebih mendalam.
Di dalam artikel ini kita akan meneliti:
- penggunaan ‘Phoenix helpers’ untuk menguji ‘controller’ dan maklumbalas mereka.
- beberapa ralat yang biasa, apa maksud mereka dan bagaimana untuk mengatasi mereka
- bagaimana untuk menggunakan ExMachina untuk menjana data ujian
- bagaimana untuk menggunakan ‘regular function’ dan
|>untuk mengubahsuai data ujian yang hasilkan oleh ExMachina.
Penyediaan
Ikuti arahan pemasangan Phoenix dan mulakan projek baru dengan mix phoenix.new my_blog. Setelah semua ‘dependencies’ dipasang, dan setelah membuat pangkalan data dengan mix ecto.create, kita sedia untuk bermula.
Menguji senarai blog post
Buat satu fail baru, test/controllers/post_controller_test.exs, seperti berikut:
defmodule MyBlog.PostControllerTest do
use MyBlog.ConnCase
setup do
{:ok, conn: Phoenix.ConnTest.conn()}
end
test "lists all blog posts", %{conn: conn} do
posts = create_pair(:post)
conn = get conn, post_path(conn, :index)
for post <- posts do
assert html_response(conn, 200) =~ post.title
end
end
end
Di dalam blok setup kita menjana satu conn yang akan digunakan. Mulai dari Phoenix 1.1, ini akan dijana secara otomatik oleh MyBlog.ConnCase.
Bahagian kedua pada ‘tuple’ yang dihasilkan dari blok setup tersebut akan digunakan sebagai argumen kepada pada fungsi test. Kita padankan ia untuk memastikan conn dapat digunakan di dalam pengujian.
Kita memanggil fungsi ExMachina iaitu create_pair/3 yang akan menambah 2 ‘blog post’ ke dalam pangkalan data. Sekiranya kita tidak mahu menyimpan ke dalam pangkalan data, kita boleh gunakan fungsi build_pair/3.
Seterusnya kita membuat permohonan kepada ‘controller’ kita dan memeriksa jika sambutan tersebut mempunyai tajuk kepada setiap ‘blog post’ kita.
‘Helper’ html_response memastikan sambutan adalah berjaya(status code 200) dan mengembalikan kandungan ‘HTML body’. Penggunaan =~ memeriksa jika apa-apa yang berada di bahagian kanan (post.title) adalah dijumpai di dalam bahagian kiri (‘HTML body’).
Sekarang kita boleh cuba untuk menjalankan pengujian dengan menjalankan mix test. Kita sepatutnya akan mendapat ralat seperti berikut:
** (CompileError) test/controllers/post_controller_test.exs:5: function create_pair/1 undefined
Memasang ExMachina
Untuk mengatasi ralat tersebut, kita akan memasang ExMachina dan menambah satu ‘factory’.
Tambahkan {:ex_machina, "~> 0.5.0"} kepada bahagian ‘dependencies’ di dalam fail mix.exs, seperti berikut:
defp deps do
[{:ex_machina, "~> 0.5.0"},
{:phoenix, "~> 1.0.3"},
# lain-lain 'dependencies'...
]
end
Kemudian tambah :ex_machina kepada senarai ‘applications’, seperti berikut:
def application do
[mod: {MyBlog, []},
applications: [:ex_machina, :phoenix, :phoenix_html, :cowboy, :logger,
:phoenix_ecto, :postgrex]]
end
Kemudian, kita akan membuat ‘factory’ yang digunakan untuk menjana data ujian. Kita akan menggunakan fail lib/my_blog/factory.ex. Pastikan kita gunakan ‘extension’ .ex, bukannya .exs. Lihat fail ExMachina README untuk maklumat semasa.
defmodule MyBlog.Factory do
use ExMachina.Ecto, repo: MyBlog.Repo
def factory(:post) do
%MyBlog.Post{
title: sequence(:title, &"My Post #{&1}"),
body: "This is my post about something",
author: "Me!"
}
end
end
Ini akan membuat satu ‘factory’ bernama :post yang dibina menggunakan ‘struct’ MyBlog.Post. Kandungan ‘body’ dan ‘author’ adalah statik, tetapi kandungan ‘title’ menggunakan fungsi sequence/2 untuk memastikan kandungan ‘title’ sentiasa unik. Setiap kali satu ‘post’ baru ditambah, n akan ditambah 1. Sintaks &("My Post #{&1}") adalah jalanpintas kepada fn(f) -> "My Post #{n}" end. Kita boleh pilih kaedah mana yang kita mahu guna.
Seterusnya kita akan menambah import MyBlog.Factory di dalam blok using di dalam fail test/support/conn_case.ex, seperti berikut:
using do
quote do
# ...other code automatically generated by Phoenix when you start a project
import MyBlog.Factory
end
end
Menambah Model Post
Jika kita menjalankan mix test kita akan mendapat:
== Compilation error on file test/support/factory.ex ==
** (CompileError) test/support/factory.ex:5: MyBlog.Post.__struct__/0 is
undefined, cannot expand struct MyBlog.Post
Ini disebabkan kita masih belum menyediakan satu ‘struct’ MyBlog.Post. Kita akan menyediakan ‘struct’ tersebut dengan membina satu ‘model’ dan ‘migration’ seperti berikut:
$ mix phoenix.gen.model Post posts title:string body:text author:string
$ mix ecto.migrate
Menambah ‘routes’ dan ‘controller’
Apabila kita menjalankan $ mix test kita akan mendapat ralat berikut:
** (CompileError) test/controllers/post_controller_test.exs:11: function posts_path/2 undefined
Kita perlu menambah ‘route’ dan ‘controller action’.
Di dalam web/router.ex, edit sebagaimana berikut:
scope "/", MyBlog do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/posts", PostController, only: [:index]
end
Kemudian buat satu fail, web/controllers/post_controller.ex
defmodule MyBlog.PostController do
use MyBlog.Web, :controller
def index(conn, _params) do
end
end
‘Get’ dan ‘display’ post
Edit fail web/controllers/post_controller.ex tersebut kepada sebagaimana berikut:
defmodule MyBlog.PostController do
use MyBlog.Web, :controller
alias MyBlog.Post
def index(conn, _params) do
# MyBlog.Repo is aliased for you when you `use MyBlog.Web, :controller`
posts = Repo.all(Post)
render(conn, "index.html", posts: posts)
end
end
Apabila kita menjalankan $ mix test, kita akan mendapat:
undefined function: MyBlog.PostView.render/2 (module MyBlog.PostView is not available)
Ini bermaksud kita perlu menambah ‘view’ dan ‘template’. Tambahkan fail berikut web/views/post_view.ex.
defmodule MyBlog.PostView do
use MyBlog.Web, :view
end
dan fail ‘template’, web/templates/post/index.html.eex
<%= for post <- @posts do %>
<%= post.title %>
<% end %>
Sekarang $ mix test sepatutnya berjaya dijalankan.
Menggunakan kumpulan fungsi untuk menjalankan pengujian yang lebih berdayatahan
Katakanlah kita dibenarkan untuk meletakkan tag pada ‘post’ kita. Setiap ‘post’ akan mempunyai beberapa ‘tag’. Kita mulakan dengan membuat kemaskini kepada ‘factory’. Kita menganggap bahawa ‘model’ telah disediakan dan melompat terus membuat kemaskini kepada ujian.
def factory(:tag) do
%MyBlog.Tag{
name: sequence(:tag_name, fn(n) -> "Tag #{n}" end)
}
end
def factory(:post_tag) do
%MyBlog.PostTag{
post: build(:post)
tag: build(:tag)
}
end
Kita akan menggunakan urutan yang baru supaya apabila kita membina satu :tag, nama yang dijanakan sentiasa unik. Ketika membina satu :post_tag kita akan membina satu post dan tag secara lalai. Ini boleh dipintas sama seperti ciri-ciri lain. Kita akan melihat bagaimana untuk melakukannya sebentar lagi.
Tambahkan lagi satu ujian:
test "list of post shows the post's tags", %{conn: conn} do
tag_name = "elixir"
create(:post)
tag = create(:tag, name: tag_name)
create(:post_tag, tag: tag, post: post)
conn = get conn, post_path(conn, :index)
assert html_response(conn, 200) =~ tag_name
end
Ujian ini akan berjaya, tetapi mempunyai masalah di mana ujian-ujian tersebut agak rapuh dan tidak begitu ekspresif. Jika seseorang perlu untuk membuat satu post yang di-tag di dalam ujian lain mereka perlu mengingati bagaimana untuk membuatnya. Jika anda mengubah bagaimana anda membuat post, anda akan perlu mengubahnya di dalam setiap ujian yang menggunakannya.
Sebaliknya mari kita tambah satu fungsi kepada ‘factory’ kita di dalam fail lib/my_blog/factory.ex sebagai penjelasan.
def tagged_with(post, tag_name) do
tag = create(:tag, name: tag_name)
create(:post_tag, post: post, tag: tag)
post
end
Sekarang ujian kita adalah seperti berikut:
test "list of post shows the post's tags", %{conn: conn} do
tag_name = "elixir"
post = create(:post) |> tagged_with(tag_name)
conn = get conn, post_path(conn, :index)
assert html_response(conn, 200) =~ tag_name
end
Sekarang jika anda perlu untuk membuat satu post ber-tag di dalam ujian lain, ianya lebih mudah untuk dilakukan. Ini juga bagus sebab jika kita ubah bagaimana kita men-tag-kan post, kita cuma perlu kemaskini fungsi tagged_with/2 dan bukannya mengemaskini setiap ujian.
Menggunakan ‘regular function’ dengan ExMachina boleh menjadikan kod anda lebih mudah untuk dibaca, dan lebih mudah untuk diubah di masa hadapan.
Ini adalah sedikit rasa kepada apa yang boleh anda buat menggunakan ExMachina dan ujian-ujian Phoenix. Pastikan untuk meneliti dokumen-dokumen Phoenix.ConnTest dan dokumentasi ExMachina untuk contoh-contoh lain.
Mahu meningkatkan prestasi pengujian anda? Belajar mengenai menguji aplikasi Rails dan TDD di dalam buku baru kami Testing Rails. Buku ini meliputi setiap jenis pengujian secara mendalam, konsep-konsep pengujian tahap pertengahan dan ‘anti-pattern’ yang memberikan masalah walaupun kepada pengaturcara tahap pertengahan.