web-dev-qa-db-ja.com

クラスメソッドをモックし、Python

Pythonのモックは初めてです。テスト中にクラスメソッドを別のクラスメソッドに置き換える(モックする)方法を知りたい。例えば:

def some_method(self):   
    self.x = 4   
    self.y = 6   

したがって、ここではモックのreturn_valueを変更することはできません。新しい関数を定義して(元の関数を置き換える必要があります)、モックのside_effectとして指定しました。しかし、モック機能でクラス内のオブジェクトの属性を変更するにはどうすればよいですか。これが私のコードです:

@patch('path.myClass.some_method')
def test_this(self,someMethod):

    def replacer(self):
        self.x = 5
        self.y = 16

some_method.side_effect = replacer

では、Pythonは、replacerのself引数をどのように理解するのでしょうか?それはテストクラスの自己ですか、それともテスト中のクラスのオブジェクトとしての自己ですか?

10
Ahmed Ayadi

何をしようとしているのかわからない場合は、事前にお詫びしますが、これでうまくいくと思います。

import unittest
from unittest.mock import patch

class MyClass:

    def __init__(self):
        self.x = 0
        self.y = 0

    def some_method(self):   
        self.x = 4   
        self.y = 6    

class OtherClass:

    def other_method(self):
        self.x = 5
        self.y = 16

class MyTestClass(unittest.TestCase):

    @patch('__main__.MyClass.some_method', new=OtherClass.other_method)
    def test_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 5)
        self.assertEqual(a.y, 16)

    def test_not_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 4)
        self.assertEqual(a.y, 6)

if __name__ == "__main__":
    unittest.main()

これにより、パッチが適用されるとsome_method()がother_method()に置き換えられ、属性x、yに異なる値が設定され、テストが実行されると結果が得られます。

..
----------------------------------------------------------------------
Ran 2 tests in 0.020s

OK

編集:クラスをモックせずにテスト関数内で行う方法についての質問に答える...

def test_inside_patch(self):
    def othermethod(self):
        self.x = 5
        self.y = 16
    patcher = patch('__main__.MyClass.some_method', new=othermethod)
    patcher.start()
    a = MyClass()
    a.some_method()
    self.assertEqual(a.x, 5)
    self.assertEqual(a.y, 16) 
    patcher.stop()

パッチャーでstart()およびstop()を呼び出すようにしてください。そうしないと、パッチがアクティブでありたくない状況になる可能性があります。テストコード関数内でモック関数を定義するために、パッチで 'new'キーワードを使用する前にモック関数を定義する必要があるため、パッチをデコレータとして使用しなかったことに注意してください。パッチをデコレータとして使用したい場合は、パッチの前のどこかでモック関数を定義する必要があります。MyTestClass内で定義することもできますが、テスト関数コード内でモック関数を定義する必要があるようです。

編集:これを行うために私が見る4つの方法の要約を追加しました...

# first way uses a class outside MyTest class
class OtherClass:
    def other_method(self):
        ...

class MyTest(unittest.TestCase):

    @patch('path_to_MyClass.some_method', new=OtherClass.other_method)
    def test_1(self)
        ...

    # 2nd way uses class defined inside test class    
    class MyOtherClass:
        def other_method(self):
            ...
    @patch('path_to_MyClass.some_method', new=MyOtherClass.other_method)    
    def test_2(self):
        ...

    # 3rd way uses function defined inside test class but before patch decorator 
    def another_method(self):
        ...
    @patch('path_to_MyClass.some_method', new=another_method)    
    def test_3(self):
        ...

    # 4th way uses function defined inside test function but without a decorator
    def test_4(self):
        def yet_another_method(self):
            ...
        patcher = patch('path_to_MyClass.some_method', new=yet_another_method)
        patcher.start()
        ...
        patcher.stop()

これらはいずれもside_effectを使用していませんが、クラスメソッドをモックして一部の属性を変更するという問題を解決します。どちらを選択するかは、アプリケーションによって異なります。

16
Steve Misuta