requests と mock を使ってみる

テストを書くときに、外部の HTTP API を叩く処理が組み込まれている場合は、何かしらモックが必要です。

どう書くのがスマートなのか良くわからないので色々試してみる

準備するモノ

  • python 2.7.x
  • mock
  • simplejson
  • requests
  • nose
  • covarage

テストのデファクト

あまりまとまってるのがないので、まとめておきます。

  • テストランナーは nose または py.test
    • nose はプラグインがステキ
    • py.test はなにやら色々嬉しい事があるらしい
  • モック/スタブは mock
  • テストは unittest (unittest2)
  • カヴァレッジは covarage ? これは良くわからず、基本 Jenkins に食べさせらるタイプで出力できればおk

この辺が今のところデファクトでしょうか、ご意見お待ちしております。

mock

patch を使うと綺麗に書けます。
デコレータで嘘付いて return_value にあとは返して欲しい値を突っ込むだけ。

実行

後は書いてみて nosetests 実行すればテストが実行できます。
せっかくなので nosetests -v --with-coverage を実行しておきましょう。

$ nosetests -v --with-coverage
$ coverage html 

これでカヴァレッジデータが html で出力されます。

## encoding=utf8

import unittest

import simplejson

import requests

import mock
from mock import patch

def spam():
  req = requests.get('http://127.0.0.1/spam')
  return req.status_code

def eggs():
  req = requests.get('http://127.0.0.1/eggs')
  if req.status_code == 200:
    return simplejson.loads(req.content)
  else:
    return None

class Test(unittest.TestCase):

  @patch('requests.get')
  def test_spam_200(self, m):
    res = requests.Response()
    res.status_code = 200
    m.return_value = res
    self.assertEqual(200, spam())

  @patch('requests.get')
  def test_eggs_200(self, m):
    response = requests.Response()
    response.status_code = 200
    ## content は property なので _content にいれること
    response._content = "{}"
    m.return_value = response
    self.assertEqual({}, eggs())

  @patch('requests.get')
  def test_eggs_404(self, m):
    response = requests.Response()
    response.status_code = 404
    m.return_value = response
    self.assertEqual(None, eggs())