2013-06-07 14 views
29

Rubyist Pythonをここに書く。私はちょっとこのようになりますいくつかのコード持っている:unittest.mock:メソッド引数の部分一致をアサートする

result = database.Query('complicated sql with an id: %s' % id) 

database.Queryを嘲笑し、私はIDが私のテストにSQL文全体をハードコーディングすることなく、正確に注入されることをテストしたいです。ルビー/ RRで、私はこの行っているでしょう:

mock(database).query(/#{id}/) 

をしかし、私は、少なくともいくつかの毛深いside_effectロジックを使用せず、unittest.mockでそのような「選択的なモック」を設定する方法を参照することはできません。だから、代わりにアサーションで正規表現を使ってみました:

with patch(database) as MockDatabase: 
    instance = MockDatabase.return_value 
    ... 
    instance.Query.assert_called_once_with(re.compile("%s" % id)) 

しかし、これはうまくいきません。このアプローチはうまくいくが、醜い:

with patch(database) as MockDatabase: 
    instance = MockDatabase.return_value 
    ... 
    self.assertIn(id, instance.Query.call_args[0][0]) 

良いアイデア?

答えて

37
import mock 

class AnyStringWith(str): 
    def __eq__(self, other): 
     return self in other 

... 
result = database.Query('complicated sql with an id: %s' % id) 
database.Query.assert_called_once_with(AnyStringWith(id)) 
... 

EDITは:先制彼らは '現実世界' を反映して、私はいつも私のユニットテストを書く

def arg_should_contain(x): 
    def wrapper(arg): 
     assert str(x) in arg, "'%s' does not contain '%s'" % (arg, x) 
    return wrapper 

... 
database.Query = arg_should_contain(id) 
result = database.Query('complicated sql with an id: %s' % id) 
+0

悪くない...しかし、モックを設定する方法はありますか?あとでアサートするのではなく、あらかじめ一致する文字列が必要ですか? – jpatokal

+0

@jpatokalは、別のバージョンを追加しました。 – falsetru

+0

あなたの新しいバージョンのように見えるのは、効果的にあなた自身の実装の模擬ですか?それは必ずしも間違っているわけではないが、私はunittest.mockがこのようなことをしていないことに驚いている。 – jpatokal

-1

一致する文字列が必要です。私は実際にはわかりませんあなたはthe ID gets injected in correctlyを除いて、あなたがテストしたいのはです。

database.Queryは何をすべきか分かりませんが、後で呼び出すか接続に渡すことができるクエリオブジェクトを作成すると思いますか?

これを実際の例を試すためにテストする最良の方法です。クエリでidが発生しているかどうかを確認するような簡単な処理を行うと、エラーが発生しやすくなります。私はしばしばユニットテストで魔法のことをしたいと思う人々を見るのですが、このは常ににつながります。単体テストを単純かつ静的に保つ。あなたのケースでは、あなたが行うことができます:直接データベースでクエリを実行し

class QueryTest(unittest.TestCase): 
    def test_insert_id_simple(self): 
     expected = 'a simple query with an id: 2' 
     query = database.Query('a simple query with an id: %s' % 2) 
     self.assertEqual(query, expected) 

    def test_insert_id_complex(self): 
     expected = 'some complex query with an id: 6' 
     query = database.Query('some complex query with an id: %s' 6) 
     self.assertEqual(query, expected) 

database.Query場合は、代わりにdatabase.queryまたはdatabase.executeようなものを使用して検討する必要があります。 Queryの首都は、オブジェクトを作成することを意味します。オブジェクトがすべて小文字であれば、関数を呼び出すことを意味します。それは命名規則と私の意見ですが、私はそこに投げているだけです。 ;-)

database.Queryに直接問い合わせると、呼び出しているメソッドに最もよくパッチを当てることができます。あなたがデータベースに行くから単体テストを防ぐためにmock.patchを使用することができます

def Query(self, query): 
    self.executeSQL(query) 
    return query 

を:それはこのようになりますたとえば、

@mock.patch('database.executeSQL') 
def test_insert_id_simple(self, mck): 
    expected = 'a simple query with an id: 2' 
    query = database.Query('a simple query with an id: %s' % 2) 
    self.assertEqual(query, expected) 

余分なヒントとして、str.formatメソッドを使用してみてください。 %の書式設定は、将来廃止される可能性があります。詳細については、this questionを参照してください。

また、テスト文字列の書式設定が冗長であると感じることもありません。 'test %s' % 'test'が機能しない場合は、Pythonに問題があることを意味します。カスタムクエリ構築をテストする場合にのみ意味があります。例えば文字列は引用符で囲む必要があります挿入し、数字はいけない、特殊文字をエスケープなど

+3

これはユニットテストではなく、統合テストです:私は気にしますIDがメソッド呼び出しに正しく渡されていることを確認します。 (サンプルは簡略化されており、実際のところでは文字列の代入だけではありません。)このテストのレベルでは、呼び出されたメソッドが内部的に何をしていないのか、そして(IMHO)他のライブラリ - もし私が '現実の世界'のテストをしたいのであれば、スタック全体を通って真ん中のビットを模倣しない統合テストを書くつもりです。 – jpatokal

3

は、ここで説明したようにあなただけのunittest.mock.ANY :)

from unittest.mock import Mock, ANY 

def foo(some_string): 
    print(some_string) 

foo = Mock() 
foo("bla") 
foo.assert_called_with(ANY) 

を使用することができます - https://docs.python.org/3/library/unittest.mock.html#any

+0

これは、何かが送信されたと主張しますが、IDがそれを行ったことを確認しません。 – jpatokal

関連する問題