[Tutor] mock

Peter Otten __peter__ at web.de
Sat Jan 23 05:48:55 EST 2016


Alex Kleider wrote:

> Some weeks (perhaps months) ago, I posted a question about testing
> and got many responses but had trouble grasping the concepts
> so I settled on the suggestion that I thought would be the easiest
> to implement (using unittest.mock.) Here it is-
> 
> """
> from Peter Otten:
> I find Ben's example instructive, but when you're just starting you
> might prefer a simpler approach:
> 
> import unittest
> from unittest import mock
> import mycode
> 
> class TestCollectData(unittest.TestCase):
>      def test(self):
>          with mock.patch(
>                  "builtins.input",
>                  side_effect=["foo", "bar", "baz"]):
>              self.assertEqual(
>                  mycode.collect_data(),
>                  dict(first="foo", last="bar", phone="baz"))
> 
> if __name__ == "__main__":
>      unittest.main()
> """
> 
> I've successfully implemented mock.patch parameters but don't
> understand how to implement the parameters for the assertEqual
> call as it pertains to my particular situation.

The biggest change when you adopt unit tests is not that you now test what 
you have, but that you write your code in such a way that it is easy to 
test. That means that you break it into small parts and that if at all 
possible you design these parts like pure functions, i. e. the behaviour of 
a part is completely determined by external input and only affects the 
result.

Hard to test:

def square_v1():
    x = int(input("give me a number "))
    return x * x

Easy to test:

def square_v2(x):
    return x * x

While you can test square_v1 by patching input() it is not something you 
would do if you can avoid it, and your actual code is even more complex, 
similar to

def square_kleider():
    while True:
        s = input("give me a number ")
        if not s:
            break
        x = int(s)
        print(x * x)

You can only test that by patching print() as well; there is no clean way to 
get data into the function and no clean way to get data out.

Now back to your actual code. A simple start would be to make entities a 
global:

def menu():
   global entities


Your test can then become

with mock.patch("builtins.input",
        side_effect=['1', 'testentity', '', '0']):
    menu.menu()
self.assert_("testentity" in menu.entities.get_list())

(Why isn't list an attribute by the way? Python is not Java...)

In the long run you should try to separate user interaction (print, input) 
and data processing to allow standard unit tests without mock for the 
latter. For every function or method the first question you should ask is:
Can I verify its correctness? Some people formalize this question by 
actually writing the test first.



More information about the Tutor mailing list