Imagine a program similar to this in Python:
import subprocess class Example(): _cmd_args = (['ls', '-a', '/usr/bin/'], ['ls', '-al', '/usr/local/bin/']) _default_args = 0 def init(self): pass def run_ls_command(self): p = subprocess.Popen(self.get_command(), stdout=subprocess.PIPE) out, err = p.communicate() return err def get_command(self): return self._cmd_args[self._default_args] def set_args(self, args): self._default_args = args def do_magical_stuff(self): #do some stuff self._default_args = 0 self.run_ls_command()
This is a simple example - but imagine a program where the primary "interactions" will be through executing commands through subprocess against the host operating system.
The specific commands and arguments will be constructed as the result of a variety of other configurations and the state of the program at the given time.
The above code is horribly not testable.
It seems like it might be best to design it in such a way to have objects like:
class MyArgs(): def __init__(self, _cmd, _cmd_args, _cmd_path): self.cmd = _cmd self.cmd_args = _cmd_args self.cmd_path = _cmd_path #more manipulation methods def get_command(self): return ([self.cmd, self.cmd_args, self.cmd_path])
class Example(): args = MyArgs() def __init__(self): pass def run_ls_command(self): print self.get_command() p = subprocess.Popen(self.get_command(), stdout=subprocess.PIPE) out, err = p.communicate() return err def set_args(self, c, a, p): #this would be more complicated logic in future and likely not just one method self.args.set_args(c,a,p) def get_command(self): return self.args.get_command()
I would then be able to test that
MyArgs.get_command() works correctly, directly on the class itself, for its given inputs (especially since I doubt I would be constructing a single "set all variables" actual command).
That would allow me to modify my tests against
Example to primarily test what
Example.get_command() returns whatever other manipulations would be happening to Example.
This is still not great to me, since I'm testing what should otherwise be private members - other than for testing,
get_command should be private.
I am primarily intending to test that the right commands are triggered - not whether they execute successfully. So for example, if a user passes in
['ls', '-a', '/a/dir/that/does/not/exist'] I do not care at this point that the
ls will fail - I want to validate the resulting command (actually executing the commands will take longer than I want to do for the unit test suite). Knowing in this case that the
ls -a /a/dir/that/does/not/exist is sufficient.
Is there a better way to design this architecture to facilitate testability?
ostreamas parameters, then have the real code pass in
stdoutand the test code pass in some
std::stringstreamsthat it can assert on afterward.
subprocess.Popenduring the test or not?
Popendoes and if it concatenates the list of string correctly. That's part of the standard library and should be considered to be working fine, no?
Popen-- whether that is a mocked Popen or an actual Popen with output redirected to some other stream (like Ixrec's suggestion) does not matter. Or some other option. However, I would like to avoid actually executing the command with
Popenas the commands are somewhat time consuming to run.