3

Situation

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.


Example

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.

Naive approach

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

Slightly better...

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.

... but not good?

While this last idea works in practice, it strikes me as not very elegant. I can see the following downsides:

  1. Each funtion needs to be split into private syscall, public helper and public normal method, increasing the amount of functions threefold. The resulting code is longer and more difficult to read because of the needless partitioning.
  2. The helper method needs to be public to be tested, but it should not be exposed to the public. As a result, additional documentation has to be written, the API reference gets longer and the method must do more checks to assure safe operation (whereas before, only values that were produced by the syscall itself could happen).
  3. Although the small main function only calls the other one with a predefined set, it cannot be included in the test coverage. This complaint is a bit of a nitpick, but I imagine it gets problematic if one uses automatic testing tools that display test coverage in lines of code or number of functions.

Questions

So, my questions would be:

  • How to correctly handle such cases in testing, e. g. with ExUnit?
  • How to separate syscalls from inner logic and reduce the amount of boilerplate functions?
  • Are there any tools or general methods how this is normally done in functional programming?
user493184
  • 201
  • 2
  • 7
  • 1
    You could mock `:os.cmd/1` with https://github.com/jjh42/mock. – Dogbert Mar 20 '17 at 09:25
  • 1
    Not sure if this is applicable to Elixir, but in at least one C++ application, we made all system calls through a [wrapper](https://github.com/PlatformLab/RAMCloud/blob/master/src/Syscall.h) so that we could easily [mock](https://github.com/PlatformLab/RAMCloud/blob/master/src/MockSyscall.h) them out. – merlin2011 Mar 20 '17 at 09:26
  • @merlin2011 Yes, that was the way I have done it in object-oriented languages, because you can easily supply a real and a testing implementation that both conform to the same interface. Without any objects, I am currently struggling a bit to find the right way. – user493184 Mar 20 '17 at 09:45
  • Bear in mind, it might be difficult to mock `:os` module functions, at least it is for `:meck`: https://github.com/eproxus/meck#caveats . It's probably a good idea to wrap them in an utility module and mock that. – nietaki Mar 20 '17 at 15:03

1 Answers1

3

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:

Máté
  • 2,294
  • 3
  • 18
  • 25
  • Thank you for the blog post and suggestions, I will read it and try to formulate code that fits my example. – user493184 Mar 20 '17 at 09:50
  • 1
    Do note that you should only use Elixir mocks as a last fallback and if you're aware of what they exactly do (mess around with global module names, mostly). There are cleaner, but mildly more cumbersome methods of doing this. – cdegroot Mar 20 '17 at 12:29
  • @cdegroot Do you mean mocks like José presented them in the linked post (basically as classes that are substituted for normal classes when testing)? If yes, what would you suggest instead? – user493184 Mar 20 '17 at 14:50
  • I use `:meck` in Elixir in multiple projects, and it's very simple and effective to use. – nietaki Mar 20 '17 at 15:05
  • @nietaki thanks for the confirmation, I haven't used it in Elixir per se. It works perfectly in EUnit / CT in the Erlang world. – Máté Mar 20 '17 at 15:33
  • @user493184 I don't like José's proposal because even though it tackles some of the worst issues, it still stops at proper mocking as it is akin to mocking the whole class instead of just an instance. To do that, you need to be able to inject single mock processes that capture their messages (and have some reasonable behavior around what to return). I'm still not sure what the correct solution is, http://evrl.com/elixir/tdd/mocking/2017/03/05/elixir-mocking.html has some of my thinking here (but more to come and _then_ I can properly post an answer here ;-)) – cdegroot Mar 20 '17 at 18:55