というわけで、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'>
もちろん、任意の属性に、任意のオブジェクトを代入できます。
>>> 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) 2001datetime なら、年月日を直接指定できるので大したことないですが、複雑なプロセスを経て、他のオブジェクトへの参照をしているようなオブジェクトで、かつ、一部のプロパティしか使わないのであれば、 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() 関数です。
眠い。寝よ寝よ。つづきは、またこんど。