2011-11-25

mock ライブラリの Mock クラス

mock ライブラリを使ったテストのことを書こうと思ったのだけれど、テストの仕方なんて教えてもらう立場なんだから、いい例を思いつくわけもなく、挫折。

というわけで、mock ライブラリで提供されている機能を、簡単に書きだしていくことにします。mock を使ってテストする具体的な方法は、また今度。

今回は Mock クラスの基本機能です。ちなみにこれだけでは、テストを書くにあたってのメリットは限定的です。

Mock クラスは引数なしで、インスタンス化できます。以後、Mock オブジェクトと呼びます。

>>> from mock import Mock
>>> wozozo = Mock()
>>> wozozo
<mock.Mock object at 0x106fc4390>

Mock オブジェクトにドットでつなげると、別の Mock オブジェクトが取り出せます。

>>> wozozo.foo
<Mock name='mock.foo' id='4412162832'>
>>> wozozo.bar
<Mock name='mock.bar' id='4412163024'>
>>> wozozo.bar
<Mock name='mock.bar' id='4412163024'>

同じプロパティ名を指定すると、同じ Mock オブジェクトが返ってきます。上の例では、wozozo.bar は必ず同じ Mock オブジェクトを参照しています。

もちろん、任意の属性に、任意のオブジェクトを代入できます。

>>> wozozo.baz = 3
>>> wozozo.baz
3

Mock オブジェクトを、関数のように呼び出すこともできます。また、 call_count プロパティは呼び出した回数を返します。

>>> wozozo()
<mock.Mock object at 0x106fc44d0>
>>> wozozo.call_count
1
>>> wozozo()
<mock.Mock object at 0x106fc44d0>
>>> wozozo.call_count
2

Mock オブジェクトなら呼び出せるので、ドットでつないで得られるプロパティも呼び出せます。引数を渡すこともできます。

>>> wozozo.unko(1)
<mock.Mock object at 0x106fc4590>

call_count の他にも呼び出し系のプロパティがあります。call_args は、最後に呼び出されたときの引数を返します。また、call_args_list は呼び出されたときの引数の履歴を返します。

>>> wozozo.unko.call_count
1
>>> wozozo.unko.call_args
((1,), {})
>>> wozozo.unko(1, 'a', foo='hoge')
<mock.Mock object at 0x106fc4590>
>>> wozozo.unko.call_count
2
>>> wozozo.unko.call_args
((1, 'a'), {'foo': 'hoge'})
>>> wozozo.unko.call_args_list
[((1,), {}), ((1, 'a'), {'foo': 'hoge'})]

戻り値を指定することもできます。下の例では、wozozo.unko() で 999 を返すようにしています。

>>> wozozo.unko.return_value = 999
>>> wozozo.unko()
999

任意の関数を割り当てることもできます。

>>> def f(x, y):
...     return x + y
... 
>>> wozozo.unko = f
>>> wozozo.unko(2, 3)
5

てな具合です。

何が嬉しいかと言うと、簡単に何かのフリをさせることができる、ということです。すぐに思いつくのは (1) 生成するのがだるいインスタンスのフリをさせる、 (2) 関数やメソッドのフリをさせる、の2つです。

1つめの例として datetime オブジェクトを受け取って、年の情報だけを使うような関数を考えます。

>>> def next_year(today):
...     return today.year + 1
... 
>>> next_year(datetime.now())
2012
>>> today = Mock()
>>> today.year = 2000
>>> next_year(today)
2001
datetime なら、年月日を直接指定できるので大したことないですが、複雑なプロセスを経て、他のオブジェクトへの参照をしているようなオブジェクトで、かつ、一部のプロパティしか使わないのであれば、 Mock オブジェクトで代替すると便利です。

2つめの例としては random 関数を考えます。戻り値が決定論的に定まらないので、テストするのが大変です。そこで random の名前のくせに、常に0.5を返すようにしてみます。

>>> random = Mock()
>>> random.random.return_value = 0.5
>>> random.random()
0.5
>>> random.random()
0.5
>>> random.random()
0.5

こうすると、テストがやりやすくなりますね。

問題は、何かの関数の「中で」random 関数が呼び出される、ということです。もちろん、random 関数を引数に与えるような設計にするといいのでしょうが、人からもらったコードはそうなってないこともあるでしょう。

それを、うまくやってくれるのが mock.patch() 関数です。

眠い。寝よ寝よ。つづきは、またこんど。