我們執(zhí)行 mix phx.gen.html
命令時,它會生成如下文件:
- a schema in web/models
- a view in web/views
- a controller in web/controllers
- a migration file for the repository
- default CRUD templates in web/templates
- test files for generated model and controller
其中有兩個測試文件,但是沒有視圖的測試文件 - 為什么?難道視圖不重要?不不不,只要是代碼,都會有測試的必要 - 有時沒寫,只是一個優(yōu)先級或是投入產(chǎn)出比上的考慮。那如何入手?
查看 test/views
目錄,現(xiàn)在已經(jīng)有三個文件。我們可以借鑒其中的 error_view_test.exs
文件。
首先在 test/views
目錄下新建一個 recipe_view_test.exs
文件,然后準備好如下內(nèi)容:
defmodule TvRecipeWeb.RecipeViewTest do
use TvRecipeWeb.ConnCase, async: true
# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
end
前面章節(jié)里我們曾經(jīng)提到過,templates 文件會被編譯成 view 模塊下的函數(shù)。比如我們的 web/templates/recipe/index.html.eex
文件,最終會變成 TvRecipe.RecipeView
模塊中的一個函數(shù):
def render("index.html", assigns) do
# 返回編譯后的 eex 模板
end
換句話說,我們其實可以在 TvRecipe.RecipeView
中定義 render("index.html", assigns)
,而不必再寫一個 index.html.eex
模板。只是模板對我們的開發(fā)更為友好,所以才從 View 中分離出來。
因此,測試模板即測試 View 中的函數(shù)。
那么,測試什么?測試我們的期望與事實是否相符。
舉 recipe/index.html.eex
模板說,我們想在模板頁面上顯示哪些數(shù)據(jù)?Phoenix 最后的輸出是否確保顯示了?
來加一個測試:
diff --git a/test/views/recipe_view_test.exs b/test/views/recipe_view_test.exs
index be4148a..8174c14 100644
--- a/test/views/recipe_view_test.exs
+++ b/test/views/recipe_view_test.exs
@@ -4,4 +4,28 @@ defmodule TvRecipe.RecipeViewTest do
# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
+ alias TvRecipe.Recipes.Recipe
+ @recipe1 %{id: "1", name: "淘米", title: "俠飯", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999"}
+ @recipe2 %{id: "2", name: "煮飯", title: "俠飯", season: "1", episode: "1", content: "浸泡", user_id: "888"}
+
+ test "render index.html", %{conn: conn} do
+ recipes = [struct(Recipe, @recipe1), struct(Recipe, @recipe2)]
+ content = render_to_string(TvRecipeWeb.RecipeView, "index.html", conn: conn, recipes: recipes)
+ # 頁面上包含標題 Listing recipes
+ assert String.contains?(content, "Listing Recipes")
+ for recipe <- recipes do
+ # 頁面上包含菜譜名
+ assert String.contains?(content, recipe.name)
+ # 頁面上包含節(jié)目名
+ assert String.contains?(content, recipe.title)
+ # 包含 season
+ assert String.contains?(content, recipe.season)
+ # 包含 episode
+ assert String.contains?(content, recipe.episode)
+ # 不包含所有者 id
+ refute String.contains?(content, recipe.user_id)
+ # 因為 content 很長,我們不在 index.html 里顯示
+ refute String.contains?(content, recipe.content)
+ end
+ end
+
end
你可能要問,測試里的 season
為什么是 "1" 而不是 1,要知道我們給它定義的類型是 integer
。
是的,我們本應該把它寫為 1,但那樣的話,我們的測試代碼里就要做類型轉換:
assert String.contains?(content, Integer.to_string(recipe.season))
為了省事,我們就直接把 1 寫成了 "1" - 這并不會有問題,因為 Phoenix 在編譯模板時,也會做類型轉換:
iex> EEx.eval_string "foo <%= bar %>", [bar: 123]
"foo 123"
運行測試:
$ mix test
Compiling 1 file (.ex)
...
1) test render index.html (TvRecipe.RecipeViewTest)
test/views/recipe_view_test.exs:9
Expected false or nil, got true
code: String.contains?(content, recipe.user_id())
stacktrace:
test/views/recipe_view_test.exs:25: anonymous fn/3 in TvRecipe.RecipeViewTest.test render index.html/1
(elixir) lib/enum.ex:1755: Enum."-reduce/3-lists^foldl/2-0-"/3
test/views/recipe_view_test.exs:15: (test)
....................................................
Finished in 0.8 seconds
58 tests, 1 failure
一個錯誤發(fā)生,因為目前的 index.html.eex
中包含了 content
與 user_id
的內(nèi)容。
我們調整下 index.html.eex
文件:
diff --git a/web/templates/recipe/index.html.eex b/web/templates/recipe/index.html.eex
index b6ff40b..1dc8a3a 100644
--- a/web/templates/recipe/index.html.eex
+++ b/web/templates/recipe/index.html.eex
@@ -7,8 +7,6 @@
<th>Title</th>
<th>Season</th>
<th>Episode</th>
- <th>Content</th>
- <th>User</th>
<th></th>
</tr>
@@ -20,8 +18,6 @@
<td><%= recipe.title %></td>
<td><%= recipe.season %></td>
<td><%= recipe.episode %></td>
- <td><%= recipe.content %></td>
- <td><%= recipe.user_id %></td>
<td class="text-right">
<%= link "Show", to: recipe_path(@conn, :show, recipe), class: "btn btn-default btn-xs" %>
再運行測試:
mix test
........................................................
Finished in 0.8 seconds
58 tests, 0 failures
悉數(shù)通過。
我們知道,index.html.eex
頁面上有一個 New recipe
的按鈕,那我們的測試里是否需要體現(xiàn)?Show
、Edit
、Delete
這些按鈕呢?要不要給它們寫測試?
測試測什么,測到怎么的粒度,我覺得沒有標準答案,更多時候要根據(jù)項目情況去權衡。但如果一定要有一個什么做為參考,我會選擇設計稿。設計稿上定下來的元素,我們就盡量在測試里體現(xiàn) - 否則設計人員很容易找上門來,說怎么少了這少了那。
在結束本節(jié)之前,別忘了在菜單欄上加上“菜譜”,不然我們就只能通過修改 url 訪問菜譜相關頁面了:
diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_controller_test.exs
index a1b75c6..7bd839c 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
@@ -29,6 +29,7 @@ defmodule TvRecipe.UserControllerTest do
# 注冊后自動登錄,檢查首頁是否包含用戶名
conn = get conn, Routes.page_path(conn, :index)
assert html_response(conn, 200) =~ Map.get(@valid_attrs, :username)
+ assert html_response(conn, 200) =~ "菜譜"
end
test "does not create resource and renders errors when data is invalid", %{conn: conn} do
diff --git a/web/templates/layout/app.html.eex b/web/templates/layout/app.html.eex
index b13f370..49240c9 100644
--- a/web/templates/layout/app.html.eex
+++ b/web/templates/layout/app.html.eex
@@ -19,6 +19,7 @@
<li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
<%= if @current_user do %>
<li><%= link @current_user.username, to: Routes.user_path(@conn, :show, @current_user) %></li>
+ <li><%= link "菜譜", to: Routes.recipe_path(@conn, :index) %></li>
<li><%= link "退出", to: Routes.session_path(@conn, :delete, @current_user), method: "delete" %></li>
<% else %>
<li><%= link "登錄", to: Routes.session_path(@conn, :new) %></li>
運行測試:
$ mix test
..........................................................
Finished in 0.8 seconds
58 tests, 0 failures
更多建議: