3

I am building an application in python that uses a wrap to a library that performs hardware communication

I would like to create some test units and I am pretty new to unit tests, so I would like to mock the communications but I really don't know how to do it

quick example:

this is the application code using the comm lib

def changeValue(id, val):
    current_value = comm.getval(id)
    if (current_value != val):
        comm.send(id, val)

I want to test this without performing communications, i.e. replacing the comm.getval return by some mocked value, and sending comm.send to a mocked comm class.

Can anyone give a hint on that?


The thing is that comm is a object inside a class

let's say the class is like this:

class myClass:
    comm = Comm()
    ....
    def __init__():
        comm = comm.start()

    def changeValue(id, val):
        ....

    ....
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Killercode
  • 894
  • 1
  • 20
  • 34
  • what is `comm`? some static library or an object? – Hilikus May 25 '15 at 14:10
  • comm is a python object from an external module and I don't want to cover that on tests. What I wanted to do is mock that object but I don't know how it can be done, I never used mockups before – Killercode May 25 '15 at 14:17

2 Answers2

2

You can use mock framework to this kind of jobs. First of all you use comm = Comm() in MyClass and that means you have something like from comm_module import Comm in MyClass's module. In these cases you need to patch Comm reference in MyClass's module to make your patch active.

So an example of how you can test your code without do any connection could be:

@patch("my_class.Comm", autospec=True)
def test_base(self, mock_comm_factory):
    mock_comm = mock_comm_factory.return_value
    MyClass()
    mock_comm.start.assert_called_with()

@patch("my_class.Comm", autospec=True)
def test_changeValue(self, mock_comm_factory):
    mock_comm = mock_comm_factory.return_value
    mock_comm.getval.return_value = 13
    MyClass().changeValue(33, 23)
    mock_comm.getval.assert_called_with(33)
    mock_comm.send.assert_called_with(33, 23)
    mock_comm.reset_mock()
    mock_comm.getval.return_value = 23
    MyClass().changeValue(33, 23)
    mock_comm.getval.assert_called_with(33)
    self.assertFalse(mock_comm.send.called)

Now I can start to explain all details of my answer like why use autospec=True or how to apply patch to all methods but that means to rewrite a lot of mock documentations and a SO answers. So I hope that is enough as starting point.

Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
1

The trick is not to use global objects like comm. If you can, make it so that comm gets injected to your class or method by the caller. Then what you do is pass a mocked comm when testing and then real one when in production.

So either you make a comm reference a field in your class (and inject it via a constructor or setter method) like so

class myClass:

  ....
  def __init__(myComm):
    comm = myComm;
    comm = comm.start()

  def changeValue(id, val):
    current_value = comm.getval(id)
    if (current_value != val):
      comm.send(id, val)

....

or you make it a parameter in the method where it is used, like so

def changeValue(id, val, myComm):
current_value = myComm.getval(id)
if (current_value != val):
    myComm.send(id, val)

Using global anything makes mocking a huge pain, try to use Dependency Injection whenever you need to mock something.

This is another good post about DI. It is in java, but it should be the same in python http://googletesting.blogspot.ca/2008/07/how-to-think-about-new-operator-with.html

Community
  • 1
  • 1
Hilikus
  • 9,954
  • 14
  • 65
  • 118
  • check my answer (I added an answer so the code snipped could be formatted)! – Killercode May 25 '15 at 16:07
  • i think it's better if you put your code snippet in the question itself. it might be useful for others to see the full class. But anyway, my point still stands. Instead of initializing `comm = Comm()`, receive `comm` in the constructor ( i guess `__init__` in python). that way when testing you inject a mock comm – Hilikus May 25 '15 at 17:20
  • I edited the answer to show the case of injecting `comm` in the class rather than passing it as a param in `changeValue` – Hilikus May 25 '15 at 20:50