14

A beginner level question.. trying to understand how I can best use the built-in unittest. In the trivial example below, the method consume_food picks a food item and then I am calling food.cut() method. In future, this method may return instance of Drink object. The #commented code indicates one possible future implementation. In this case, self.milk will not have the cut method defined.

I want to add a unit test for consume_food and pick_food methods. I would like to do this for the original implementation first and then change it after adding self.milk functionality.

EDIT: The intention is to write a unit test for an existing api, so that I can capture any such changes ( i.e. absence of Drink.cut method) forcing me to update the methods and unit tests.

Can someone please help showing me how to write a unit test for this example?

class Fruit:
    def cut(self):
        print("cut the fruit")

class Drink:
    def pour(self):
       print("pour the drink")


class A:
   def __init__(self):
       self.apple = Fruit()
       self.banana=Fruit()
       #self.milk = Drink()
       #self.liquid_diet = True

   def consume_food(self):
       food = pick_food()
       food.cut()
       print("consuming the food")

   def pick_food(self):
       return self.apple                                 
       #if self.liquid_diet: return self.milk
       #return self.apple
4
  • 1
    I think apart from unittesting you need to think about what your API is going to be. Right now your API is such that consume_food assumes that all foods can be cut. If you write a unittest that relies on being able to call food.cut(), then it will fail if you add foods that do not have .cut(). That is unavoidable; you can't write a test that will always keep working no matter how you change the API. So I don't really understand what your question is. Commented Sep 19, 2015 at 19:55
  • 1
    @BrenBarn I think that's exactly what OP: To provide a test that will fail once the assumption that all foods can be cut isn't true any more, forcing him to refactor his code before implementing the new feature. Commented Sep 19, 2015 at 19:57
  • @LukasGraf: Okay, so any test that calls consume_food at all will do that. Commented Sep 19, 2015 at 19:58
  • 1
    @BrenBarn: That is one of the intentions. I want to write a test for an existing api, so that I can capture any such changes ( i.e. absence of Drink.cut method)... yes, I can add some hasattr/ assertion or isinstance condition, but the thing I wanted to learn is , 'can I use unittest' here? if so how to write one? can someone please show me for this example please? I understand that it will need to be changed in future. Commented Sep 19, 2015 at 20:02

1 Answer 1

17

The thing is, your cut() and consume_food() methods don't really do much right now that allow you to make meaningful assertions after you execute them in a test.

So I'd suggest to expand your initial code a little bit to have those methods act upon the respective objects so that you can make meaningful assertions on their state after invoking those methods.

Right now, all they really do is write to STDOUT, which is sort of a global state - which should generally be avoided and is always difficult to test. (I'm not saying that printing output is a bad thing - but if that's the only thing your code does, it's going to be very tricky to test).

So I introduced a common superclass Food which has a consume() method, and sets a corresponding attribute. Similarly, the cut() method on Fruit now sets an attribute that you can test for.

import unittest


class Food(object):
    def __init__(self):
        self.consumed = False

    def consume(self):
        self.consumed = True


class Fruit(Food):
    def __init__(self):
        super(Fruit, self).__init__()
        self.been_cut = False

    def cut(self):
        print("cut the fruit")
        self.been_cut = True


class Consumer(object):
    def __init__(self):
        self.apple = Fruit()
        self.banana = Fruit()

    def consume_food(self):
        food = self.pick_food()
        food.cut()
        print("consuming the food")
        food.consume()

    def pick_food(self):
        return self.apple

These tests now can make assertions on the object's states after the relevant methods have been invoked. Note that they follow the AAA pattern - Arrange Act Assert:

  • First, you arrange the objects under test the way you need them (instantiate a consumer).
  • Then you act on the objects under test (invoking the method in question)
  • Finally, you make assertions on the resulting state you expect the objects to be in
class TestConsumer(unittest.TestCase):

    def test_consume_food_consumes_the_apple(self):
        c = Consumer()
        c.consume_food()
        self.assertTrue(c.apple.consumed,
                        "Expected apple to be consumed")

    def test_consume_food_cuts_the_food(self):
        c = Consumer()
        c.consume_food()
        self.assertTrue(c.apple.been_cut,
                        "Expected apple to be cut")

    def test_pick_food_always_selects_the_apple(self):
        c = Consumer()
        food = c.pick_food()
        self.assertEquals(c.apple, food,
                          "Expected apple to have been picked")


if __name__ == '__main__':
    unittest.main()
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! the print statements were added just for creating this example. Now I understand how to write a basic test in such cases.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.