Source

blog / source / en_posts / testing_python / going-deeper.rst

Going deeper with unit-testing

Note

This page is a part of :doc:`../2011-03-09-testing-python` post.

So from here if you understood the idea of unit-testing you just need to go, read the Mock library docs, read python unittest module docs (for asserts that you have) and experiment. Rules are simple:

  • try to keep your "preparation" test code small
  • tests should not speak to external world, mock everything external out (then look at previous item again)
  • every test should test for one (well, ok, sometimes more) thing

Then you will have dozens of problems where you will sit and think. That's where fun comes in. You will really strain your brain a lot and think about your code, why it isn't testible (sometimes even "is it really that horrible?"), about how python imports work, "why the hell Cow object needs a Rocketjump instance", "how do I create a BlogEntry if inside it's __init__ it speaks to database for getting list of bad words" and a lot of other things.

That's when unit-testing will imply on your code heavily. I will try to touch some of the moments that you may see in future.

General testing approach -- the record/replay way

So, let us go back and talk about technique Mock library uses. The most popular approach I saw is called record/replay and it works like this: you create your mock objects, then you "record" something will be done under them, then you "rewind" them to the state before calls, then you do the actual code under test, and you check that what it did was what you recorded. Good idea.

Let's look at this Java code to get a taste:

@Test
public void testAddDocument() {
    mock.documentAdded("New Document"); // 2
    replay(mock); // 3
    classUnderTest.addDocument("New Document", new byte[0]);
    verify(mock);
}

It will expect that .documentAdded was called on mock with "New Document" string as a parameter.

Python takes a shortcut

As I know, there are frameworks like this in python, but what I like about Mock library is that it takes a shortcut. In some complex situation when you want to test everything "in time", record/replay is great and simpler, but for most situations, you are better with python Mock's ideology called action/assertion. You create mocks, do the actions, assert actions you are interested at were done.

I think it's time to just insert a bit more complex example of unit-test without any context on it:

class TestDelete(BaseTestCase):
    @ppatch('count_for_category')
    @ppatch('bl_shopping_list')
    @ppatch('get_from_receipt_item', autospec=True)
    def test_should_delete_fact(
        self, get_from_receipt_item_mock,
        bl_shopping_list_mock,
        count_for_category_mock):

        receipt = MagicMock()
        receipt_item = MagicMock()
        fact = MagicMock()
        get_from_receipt_item_mock.return_value = fact

        # do
        bl_bought_from_category_fact.delete(receipt, receipt_item)

        fact.delete.assert_called_with()

You do what you want, then you assert what you want to assert (that ORM record .delete() method was called).

Also you may notice ppatch decorator here -- I'll show it to you later. It's just a shortcut to patch with small "improvements".

Head over to :doc:`two-approaches`.