Monday, 20 December 2010

Mocking empty collections with FalseMock

A friend of mine described to me a PITA he had with mock - it doesn't play well with a common Python idiom:

if collection:
    for element in collection:
        do_something(element)
What he expected as default behaviour was for the mock to be iteratable as an empty collection. Instead, he got:
>>> m = mock.Mock()
>>> for x in m:
...     print x
... 
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'Mock' object is not iterable
As it turns out, there is a simple solution to this problem: all you need to do is to implement the magic method yourself.
>>> class FalseMock(mock.Mock):
...     def __nonzero__(self):
...             return False
This behaves as you would expect: It evaluates to False.
>>> m = FalseMock()
>>> bool(m)
False
It still works as every other Mock object would:
>>> m.a.b.c.d.return_value = "hi"
>>> m.a.b.c.d()
'hi'
It keeps its behaviour in all Mocks generated as a result of getting an attribute
>>> m.a.b.c.d

Monday, 16 August 2010

Distutils2 Summer of Code

This summer, I took part in Google Summer of Code project as a student. I worked on distutils2 project implementing new commands and improving the existing ones. In poarticular, my tasks were:

  • to implement a test command (similar to the one from setuptools/distribute
  • implement command hooks
  • port the upload_docs command
  • enhance the check command
I started with a low hanging fruit: porting upload_docs. This didn't take very long. In fact I spent most of my time with the test code, implementing mock PyPI server, which - I'm proud to say - turned out quite useful for other student's tests (although, my initial implementation used wsgiref - not available in Python 2.4 and Alexis refactored practically all of it, so it's rather his child).

After that, I started working on the test command. The new command's API is a little bit different than the one you might know from setuptools/distribute: the options "test-suite" and "test-loader" got replaced with "suite" and "runner". You would use "suite" option in place of "test-suite", but setting the "runner" to a dotted path to a Python callable will cause the test command to invoke that callable in place of default unittest test runner. In absence of both options, the test command will invoke test discovery as implemented in unittest (Python 2.7, 3.2 and newer) or unittest2 (if installed, for older Python versions).

The next task was to implement command hooks. The command pre- and post-hooks are Python callables that accept the command instance (giving it access to all its options). You specify them in your setup.cfg file as the command's options. In addition to that, you have to specify a postfix for you hook, so that it wouldn't override hooks from other files:

[install]
  pre-hook.my_postfix = path.to.hook

The improvements to the check command were arguably most neglected task in my project. I gathered the optional checks (originally only check for validity of ReStructuredText in package's description) under a single option --all, and implemented additional check: one that validates importability of the hooks.

Not all changes are upstream yet. The hooks and the upload_docs command are already in the central repo. The test command and check improvements are waiting for the merge. Tomorrow, I'm leaving for my summer kayak trip, but I'm looking forward to contributing more to distutils in the future.

Overall, I am very happy with the time I spent working on the project. Even in spite of some problems in my way, I enjoyed the experience of working with a team of extremely smart guys. The regular IRC meetings were instructive and enjoyable. I would also like to thank my mentor: Fred Drake, who offered all the help and feedback I needed.

Saturday, 12 June 2010

Mock recipes

I posted recently a simple introduction to the mock library. Today, I'd like to show you some of its indirect uses, that I found helpful in my everyday testing needs.

Custom patcher

It is very easy to set up mock objects to respond to events. You just access attributes and set either them or their return value. When using the patch decorator, a mock instance is provided to your test method as the last argument, so you set it up just before excercising your production code.

@mock.patch("sys.stdin")
def test_stdin_reader(mock_stdin):
mock_stdin.write.return_value = "hello" # setting up mock_stdin
run_code_under_test()
validate_results()

In this example the setting up part is really easy - just a single line. But in real-life projects, you might find yourself setting whole attribute hierarchies on mocks, repeatedly in every many test methods.

That's when a custom patcher allows you to implement the DRY principle in an elegant manner.

class MyTest(unittest.TestCase):    
@patch_db
def test_one(self):
pass
@patch_db
def test_two(self):
pass

It's very easy to write a custom patcher.

def patch_db(func):    
@patch("db.module")
def wrapper(*args):
mock_db = args[-1]
# ... perform complicated setup on mock_db
func(*args[:-1])
return wrapper


Cutting off

The cut_off patcher is useful when you need to mock out several objects, and don't care about the mock instances. Say you want to deactivate internet access in a module that uses both urllibs.

@cut_off("mymodule.urllib", "mymodule.urllib2")
def test_something():
pass

The implementation of cut_off is very simple too.

def cut_off(*patches):    
def decorator(meth):
def wrapper(self, *args):
return meth(self, *args[:-len(patches)])
for obj in patches:
wrapper = mock.patch(obj)(wrapper)
return wrapper
return decorator

Monday, 7 June 2010

Analyzing PyPI packages

PyPI, which stands for Python Package Index is a global repository for Python
packages. Every time you need a Python tool or library, you can simply type
easy_install mypackage, and have it downloaded and
installed for you. It is also a great source when trying to investigate current
practices in the Python world.

Disclaimer


There are couple of troubles when analyzing PyPI. First - it is a moving target.
Since I first run the download script (which was 3 days ago), it grew by 20 new
packages. So, please bear in mind, this information won't very exact. Still, it
provides a nice overview. Second - not all packages are hosted in PyPI. For some
(quite a lot, actually) cases, we only get a link to the actual download source.
This grows the chance of a host being, and causes the download to fail. Third -
PyPI packages are terribly diverse. In order to analyze it in a timely manner, I
picked only the ones that could be downloaded as either tarballs or zips. This
reduced the sample by a quarter (from 10112 to 7625), which I believe is still a
representative enough.

Setup.py usage


Most of the packages (96%) used setup.py. The rest either simply didn't use it
or used a non-standard directory layout (accordingly: 187 and 47). Out of
setup.py users, setuptools was more than three time more popular than standard
distutils. 73 packages couldn't be identified as using either of these, and this
is mostly caused by custom setup function wrappers (see 4Suite for example of
this).



Test runners


I was curious, how people run their tests, so I identified several ways it could
be done:
  1. using a top-level shell script: 20
  2. using a top-level python script: 326
  3. using setuptools' test command: 961



Note: these stats don't include another popular way of running tests, used by
Django apps.

There where 1048 packages having a toplevel directory containing string "test",
among which the most popular varations were unsurprisingly "test" (477) and
"tests"(456).

Tuesday, 1 June 2010

5 things you can do with a Python list in one line

This is directly inspired by an excellent post by Drew Olson 5 things you can do with a Ruby array in one line. When reading it, I couldn't help but thinking of the Python versions (and how I like them more :>). So here it is:

  1. Summing elements
    puts my_array.inject(0){|sum,item| sum + item}
    sum(my_list)
  2. Double every item.
    my_array.map{|item| item*2 }
    [2 * x for x in my_list]
  3. Finding all items that meet your criteria.
    my_array.find_all{|item| item % 3 == 0 }
    [x for x in my_list if x % 3 == 0]
  4. Combine techniques.
    my_array.find_all{|item| item % 3 == 0 }.inject(0){|sum,item| sum + item }
    sum(x for x in my_list if x % 3 == 0)
  5. Sorting.
    my_array.sort
    my_array.sort_by{|item| item*-1}
    sorted(my_list)
    sorted(my_list, reverse=True)

Friday, 28 May 2010

Mockity mock mock - some love for the mock module

It looks like Python mocking libraries are the new web frameworks - everyone wrote one. Let me show you my favourite mocking library so far - the mock module, written by Michael Foord. It's easy_installable, so you can get to play with it in a moment.

easy_install mock
What makes it different than other modules like that? Most mocking libraries follow the old record-replay pattern, which works roughly like this:
  1. Teach a mock what to expect.
  2. Stick it into your code and expect an explosion if something didn't go as planned.

The excercise-inspect pattern in mock module reverses that approach:
  1. Stick the Mock object into your code (Mock being the class provided by mock module).
  2. Make sure that things happened the way you intended.


The tricky part is: how the hell is Mock able to fit everywhere? Well, it lets you do anything with it: get and set all attributes, and even call it when needed. And it leaves other Mocks everywhere it goes (it pretty much works like cancer). Let's play with Mock a little bit:

>>> from mock import Mock
>>> m = Mock()
>>> m.foo = 1
>>> m.foo # all attributes are recorded
1
>>> bar_attribute = m.bar # if no attribute was set, a mock instance is returned
<mock.Mock object at 0xdcff0>
>>> m.bar == bar_attribute # an attribute that was queried once stays there
True
>>> ret_val = m() # we can even treat mock as a function
>>> m() == ret_val == m.return_value # with similar cacheing behaviour
True
>>> m.bar() # this is true of all methods as well (they're Mocks to, right?)
<mock.Mock object at 0xe4070>
>>> m.bar.called # mock remembers everything that happened to it
True
>>> m.baz.return_value = "hello!" # if we really need some setup
>>> m.baz() # nothing prevents us from doing it
'hello!'
>>> m.who.was.this.demeter.anyway.return_value.and_its_attribute # so just remember
<mock.Mock object at 0xe42f0>
>>> _ == m.who.was.this.demeter.anyway().and_its_attribute # there are Mocks all the way down!
True

Pretty cool, huh? But wait, there's more! Why should we laborously prepare, and then revert all the mocking ourselves? Let the library stick it where we need. We just need to import patch decorator.

import sys
from mock import patch

def read_4_chars():
    return sys.stdin.read(4)

@patch("sys.stdin")
def test_something(mock_stdin):
    mock_stdin.read.return_value = "abcd"
    assert read_4_chars() == "abcd"
    assert mock_stdin.read.called
    assert mock_stdin.read.call_args == ((4,), {})

def test_something_2():
    """You can use it with *with* statement as well!"""
    with patch("sys.stdin") as mock_stdin:
    mock_stdin.read.return_value = "abcd"
    assert read_4_chars() == "abcd"
    assert mock_stdin.read.called
    assert mock_stdin.read.call_args == ((4,), {})

There is much more that mock module has to offer. I suggest reading mock's excellent documentation.