【发布时间】:2017-03-20 09:22:27
【问题描述】:
情况
通常,像 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
稍微好一点...
如果您添加另一个仅解析字符串/字符列表的辅助方法,您可以实现您想要的,同时将系统调用本身设为私有:
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案例中的辅助测试函数,普通程序也可以调用普通函数了。
...但不好?
虽然最后一个想法在实践中有效,但我觉得它不是很优雅。我可以看到以下缺点:
- 每个函数都需要拆分为私有系统调用、公共助手和公共普通方法,函数数量增加了三倍。由于不必要的分区,生成的代码更长且更难阅读。
- 辅助方法需要公开才能进行测试,但不应公开。因此,必须编写额外的文档,API 参考变得更长,并且该方法必须进行更多检查以确保安全操作(而以前,只有系统调用本身产生的值才会发生)。
- 虽然小main函数只调用了一个预定义集合的另一个,但它不能包含在测试覆盖范围内。这个抱怨有点吹毛求疵,但我想如果使用自动测试工具以代码行或函数数量显示测试覆盖率,就会有问题。
问题
所以,我的问题是:
- 如何在测试中正确处理此类情况,例如。 G。与 ExUnit?
- 如何将系统调用与内部逻辑分离并减少样板函数的数量?
- 是否有任何工具或通用方法通常在函数式编程中如何完成?
【问题讨论】:
-
你可以用github.com/jjh42/mock模拟
:os.cmd/1。 -
@merlin2011 是的,这就是我在面向对象语言中所做的方式,因为您可以轻松地提供符合相同接口的真实和测试实现。没有任何对象,我目前正在努力寻找正确的方法。
-
请记住,模拟
:os模块功能可能很困难,至少对于:meck: github.com/eproxus/meck#caveats 来说是困难的。将它们包装在实用程序模块中并模拟它可能是个好主意。
标签: unit-testing functional-programming elixir system-calls ex-unit