上一章里,我们用 mix phx.gen.html
命令创建出完整用户界面,并且具备增加、删除、更改、查询用户的功能。
这一章,我们将实现 username
的第一个规则:username
必填,如果未填写,提示用户请填写
。
先来看看,在 http://localhost:4000/users/new
页面上,提交空白用户名的话,我们会看到什么?
页面会提示我们,can't be blank
。
很好,虽然不知道怎么回事,但必填的限制已经有了,那如何将它改成请填写
呢?
打开 web/models/user.ex
文件,其中有一行:
|> validate_required([:username, :email, :password])
正是 validate_required
明确了 username
为必填。从文档里我们看到,validate_required
还接收一个可选的 message
参数,用于自定义错误消息。
让我们加上试试:
diff --git a/tv_recipe/users/user.ex b/tv_recipe/users/user.ex
index b7713a0..87ce321 100644
--- a/web/models/user.ex
+++ b/web/models/user.ex
@@ -15,7 +15,7 @@ defmodule TvRecipe.User do
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:username, :email, :password])
- |> validate_required([:username, :email, :password])
+ |> validate_required([:username, :email, :password], message: "请填写")
|> unique_constraint(:username)
|> unique_constraint(:email)
end
打开网址,提交空白用户名,页面上已经显示“请填写”了:
很好,但请注意,我们这是人肉测试。
又或者,我们可以用 Phoenix 生成的测试文件来验证。
打开 test/tv_recipe/users_test.exs
文件,默认内容如下:
defmodule TvRecipe.UserTest do
use TvRecipe.ModelCase
alias TvRecipe.User
@valid_attrs %{email: "some content", password: "some content", username: "some content"}
@invalid_attrs %{}
test "changeset with valid attributes" do
changeset = User.changeset(%User{}, @valid_attrs)
assert changeset.valid?
end
test "changeset with invalid attributes" do
changeset = User.changeset(%User{}, @invalid_attrs)
refute changeset.valid?
end
end
文件中有两个变量,@valid_attrs
表示有效的 User
属性,@invalid_attrs
表示无效的 User
属性,我们按本章开头拟定的规则修改 @valid_attrs
:
diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 1d5494f..7c73207 100644
--- a/test/tv_recipe/users_test.exs
+++ b/test/tv_recipe/users_test.exs
@@ -3,7 +3,7 @@ defmodule TvRecipe.UserTest do
alias TvRecipe.User
- @valid_attrs %{email: "some content", password: "some content", username: "some content"}
+ @valid_attrs %{email: "chenxsan@gmail.com", password: "some content", username: "chenxsan"}
@invalid_attrs %{}
test "changeset with valid attributes" do
接着,在 users_test.exs
文件中添加一个新测试:
diff --git a/test/models/user_test.exs b/test/models/user_test.exs
index 7c73207..4c174ab 100644
--- a/test/tv_recipe/users_test.exs
+++ b/test/tv_recipe/users_test.exs
@@ -15,4 +15,9 @@ defmodule TvRecipe.UserTest do
changeset = User.changeset(%User{}, @invalid_attrs)
refute changeset.valid?
end
+
+ test "username should not be blank" do
+ attrs = %{@valid_attrs | username: ""}
+ assert %{username: ["请填写"] } = errors_on(%User{}, attrs)
+ end
end
这里,%{@valid_attrs | username: ""}
是 Elixir 更新映射(Map)的一个方法。
至于 errors_on/2
函数,它需要新增在 test/support/data_case.ex
文件中:
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
+
+ def errors_on(struct, attrs) do
+ changeset = struct.__struct__.changeset(struct, attrs)
+ errors_on(changeset)
+ end
end
是否很吃惊?要知道,如果是在 JavaScript 里写两个同名函数,后一个函数会覆盖前一个的定义,而 Elixir 下,我们可以定义多个同名函数,它们能处理不同的状况,而又互不干扰。
它检查给定数据中的错误消息,并返回给我们。
现在在命令行下运行:
$ mix test test/tv_recipe/users_test.exs
结果如下:
...
Finished in 0.07 seconds
3 tests, 0 failures
测试通过。现在我们可以放心地认为,用户提交空白 username
时,Phoenix 一定会返回“请填写”的错误消息。
为什么要写测试?
可能很多人都抱有这个疑问。测试增加了我们的工作量,而它的作用又不那么明显。更何况,这还只是一个入门教程。
我想谈几点个人感受:
- 我不喜欢拿自己当人肉测试机。
- 代码的修改是必然发生的,而我们在修改时无法保证周全,此时测试即任务清单,它帮我们指出,哪些地方的代码需统一修改,这样我们才能保证代码的质量。
- 在团队协作中,你很难保证别人的代码不会破坏到自己的那部分。比如一个开源项目,有人在 github 上提了 pull request,你如果有测试代码,马上就能知道,这个 pull request 是否会破坏其它功能,如果你没有测试代码,好了,你得一行一行验证了 - 这种成本无论是对维护者还是贡献者来说,都是极大的浪费。
而况在 Phoenix 框架下,测试非常容易写。
更多建议: