Reputation: 211
Normally, unit tests like ExUnit should be self-contained with input, function call and desired output, so that the test can run on any system and always tests correctly regardless of environment.
On the other side, if your application does syscalls, for example with Elixir's System.cmd/3
or Erlang's :os.cmd/1
and works with the results, your tests may get different results because of reasons like different/updated binaries, changed circumstances, different operating systems and so on.
Of course, it is good that tests fail in these cases, so that your coverage of real life situations increases. When developing, however, you would want to first get your functions to do the right thing, and only then to do the thing right. If the outside world changes, it is difficult or even impossible to always run the tests predictably.
Additionally, you may want to test for conditions that rarely or almost never happen, but your system calls do not give you that information, because it is very rare to happen indeed. You would need to somehow mock the output of the syscall and separate it from the inner logic of your program.
To keep it simple (the same principle applies in more complicated situations), consider reading the boot time of the system and responding depending on the cleaned result:
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
This function can only be tested correctly if you reboot your system at the specific time, which of course is unreasonable. But as the format of the output is roughly known, you could create a list of test values like ['16:04', '23:59', '12:00', "12:00", 2, "xyz", '1.0"]
and test the parsing part without the syscall, then compare it to your expected results as usual.
But how is this done? The syscall is the first thing in the function, so if you take it out into a separate function, you could test the syscall, but that does not help you much, because the syscall itself is the problem:
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
If you add another helper method that just parses the string/charlist, you can achieve what you want, while making the syscall itself private:
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
Now you can call the helper test function in the ExUnit case and the normal program can call the normal function.
While this last idea works in practice, it strikes me as not very elegant. I can see the following downsides:
So, my questions would be:
Upvotes: 3
Views: 1847
Reputation: 2345
Regarding the style and how you split up the functionality into separate functions, or leave it in one is up to your appetite, and how you would like to deal with code going forward. There are pros and cons for each solution (all-in-one function or separated out).
Regarding the testing aspect, the best bet is to treat the OS calls as external API calls. Doing so, you can easily use mocks and stubs within your tests, so you can control what and how you test for.
Jose Valim has a very comprehensive blog post about mocks and how you should go about testing external calls. I'd recommend to read that through first.
If you google around, there are few libraries that can stub/mock things out for you:
Upvotes: 3