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() 関数です。
眠い。寝よ寝よ。つづきは、またこんど。