通常,像ExUnit这样的单元测试应该是自包含的输入,函数调用和所需的输出,这样测试就可以在任何系统上运行,并且无论环境如何都能正确测试。
另一方面,如果您的应用程序执行系统调用,例如使用Elixir的System.cmd/3
或Erlang的:os.cmd/1
并使用结果,则由于不同/更新的二进制文件等原因,您的测试可能会得到不同的结果,改变了环境,不同的操作系统等等。
当然,在这些情况下测试失败是好的,这样你对现实生活情况的报道就会增加。然而,在开发时,你会希望首先让你的函数做正确的事情,然后才能做正确的事情。如果外部世界发生变化,则很难甚至不可能始终以可预测的方式运行测试。
此外,您可能希望测试很少或几乎不会发生的情况,但系统调用不会向您提供该信息,因为它确实很少发生。您需要以某种方式模拟系统调用的输出并将其与程序的内部逻辑分开。
为了保持简单(同样的原则适用于更复杂的情况),请考虑读取系统的启动时间并根据清理后的结果进行响应:
def what_time do
time =
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
|> to_string
|> String.trim("\n")
|> String.split(":")
|> List.to_tuple
case time do
{"12", "00"} -> {:ok, "It's High Noon!"}
_ -> {:error, "meh"}
end
end
只有在特定时间重启系统才能正确测试此功能,这当然是不合理的。但是,由于输出的格式大致已知,您可以创建一个测试值列表,如['16:04', '23:59', '12:00', "12:00", 2, "xyz", '1.0"]
,并在没有系统调用的情况下测试解析部分,然后像往常一样将其与预期结果进行比较。
但这是怎么做到的?系统调用是函数中的第一件事,所以如果你把它带到一个单独的函数中,你可以测试系统调用,但这对你没什么帮助,因为系统调用本身就是问题所在:
def what_time do
time = get_time
|> to_string
[...]
end
def get_time do
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
end
如果你添加另一个只解析字符串/ charlist的帮助方法,你可以实现你想要的,同时使系统调用本身是私有的:
def what_time do
what_time_helper(get_time())
end
def what_time_helper(time) do
time =
time
|> to_string
[...]
end
end
defp get_time do
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
end
现在您可以在ExUnit情况下调用辅助测试函数,正常程序可以调用正常函数。
虽然这最后一个想法在实践中起作用,但它让我觉得不是很优雅。我可以看到以下缺点:
所以,我的问题是:
答案 0 :(得分:3)
关于样式以及如何将功能拆分为单独的功能,或者将其保留在一个功能中取决于您的胃口,以及您希望如何处理未来的代码。每种解决方案都有利弊(一体化功能或分离出来)。
关于测试方面,最好的办法是将OS调用视为外部API调用。这样,您可以在测试中轻松使用模拟和存根,这样您就可以控制测试的内容和方式。
Jose Valim有一个关于模拟的very comprehensive blog post以及你应该如何测试外部电话。我建议先阅读。
如果你在谷歌周围,很少有库可以为你存根/嘲笑: