4

特定の関数の単体テストを作成するには、パッチを適用する必要があり''.join(...)ます。

これを行うために(モックライブラリを使用して)多くの方法を試しましたが、そのライブラリで単体テストを作成した経験があるにもかかわらず、機能させることができません。

発生する最初の問題strは、組み込みクラスであるため、モックできないことです。William John Bert による投稿は、これに対処する方法を示しています (datetime.date彼の場合)。ライブラリの公式ドキュメントの「部分的なモック」セクションにも可能な解決策があります。

2 番目の問題はstr、実際には直接使用されないことです。代わりに、joinリテラルのメソッド''が呼び出されます。では、パッチへのパスはどうあるべきでしょうか?

これらのオプションはどれも機能しませんでした:

  • patch('__builtin__.str', 'join')
  • patch('string.join')
  • patch('__builtin__.str', FakeStr)(FakeStrは のサブクラスstr)

どんな助けでも大歓迎です。

4

5 に答える 5

4

組み込みクラスに属性を設定することはできないため、できません。

>>> str.join = lambda x: None
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'str'

strリテラルを使用するため、 にパッチを適用することはできません。したがって、をどのように置き換えようとしても''.join、インタープリターは常にを作成します。strstr__builtin__

生成されたバイトコードを読むと、これを確認できます。

>>> import dis
>>> def test():
...     ''.join([1,2,3])
... 
>>> dis.dis(test)
  2           0 LOAD_CONST               1 ('')
              3 LOAD_ATTR                0 (join)
              6 LOAD_CONST               2 (1)
              9 LOAD_CONST               3 (2)
             12 LOAD_CONST               4 (3)
             15 BUILD_LIST               3
             18 CALL_FUNCTION            1
             21 POP_TOP             
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE

バイトコードはコンパイル時に生成され、ご覧のとおり、実行時に の値をどのように変更しても、最初にLOAD_CONSTロード''されるのはです。strstr

できることは、モックできるラッパー関数を使用するか、リテラルの使用を避けることです。たとえば、str()代わりにを使用すると、必要に応じてメソッドを実装するサブクラスを持つクラスを''使用できます(ただし、これはコードに影響を与えすぎて、使用するモジュールによっては実行できない場合があります)。mockstrjoin

于 2013-02-28T22:21:42.137 に答える
3

運が良ければ、コード オブジェクト内の文字列 const を調べてパッチを当てることができます。

def patch_strings(fun, cls):
    new_consts = tuple(
                  cls(c) if type(c) is str else c
                  for c in fun.func_code.co_consts)

    code = type(fun.func_code)

    fun.func_code = code(
           fun.func_code.co_argcount,
           fun.func_code.co_nlocals, 
           fun.func_code.co_stacksize,
           fun.func_code.co_flags,
           fun.func_code.co_code,
           new_consts,
           fun.func_code.co_names,
           fun.func_code.co_varnames,
           fun.func_code.co_filename,
           fun.func_code.co_name,
           fun.func_code.co_firstlineno,
           fun.func_code.co_lnotab,
           fun.func_code.co_freevars,
           fun.func_code.co_cellvars)

def a():
    return ''.join(['a', 'b'])

class mystr(str):
    def join(self, s):
        print 'join called!'
        return super(mystr, self).join(s)

patch_strings(a, mystr)
print a()      # prints "join called!\nab"

Python3 バージョン:

def patch_strings(fun, cls):
    new_consts = tuple(
                   cls(c) if type(c) is str else c
                   for c in fun.__code__.co_consts)

    code = type(fun.__code__)

    fun.__code__ = code(
           fun.__code__.co_argcount,
           fun.__code__.co_kwonlyargcount,
           fun.__code__.co_nlocals, 
           fun.__code__.co_stacksize,
           fun.__code__.co_flags,
           fun.__code__.co_code,
           new_consts,
           fun.__code__.co_names,
           fun.__code__.co_varnames,
           fun.__code__.co_filename,
           fun.__code__.co_name,
           fun.__code__.co_firstlineno,
           fun.__code__.co_lnotab,
           fun.__code__.co_freevars,
           fun.__code__.co_cellvars)
于 2013-02-28T22:42:58.840 に答える
0

私の解決策は少しトリッキーですが、ほとんどの場合に機能します。ところで、モック ライブラリは使用しません。私のソリューションの利点は、''.join醜い変更をせずに使い続けることです。

Python3.3用に書かれたコードをPython3.2で実行しなければならなかったときに、このアプローチを見つけました(に置き換えられstr(...).casefoldましたstr(...).lower

このモジュールがあるとします:

# my_module.py

def my_func():
    """Print some joined text"""
    print('_'.join(str(n) for n in range(5)))

それをテストするための単体テストの例があります。これは Python 2.7 用に書かれていますが、Python 3 用に簡単に変更できることに注意してください (コメントを参照)。

import re
from imp import reload  # for Python 3

import my_module


class CustomJoinTets(unittest.TestCase):
    """Test case using custom str(...).join method"""
    def setUp(self):
        """Replace the join method with a custom function"""
        with open(my_module.__file__.replace('.pyc', '.py')) as f:
            # Replace `separator.join(` with `custom_join(separator)(`
            contents = re.sub(r"""(?P<q>["'])(?P<sep>.*?)(?P=q)[.]join\(""",
                              r"custom_join(\g<q>\g<sep>\g<q>)(",
                              f.read())

        # Replace the code in the module
        # For Python 3 do `exec(contents, my_module.__dict__)`
        exec contents in my_module.__dict__

        # Create `custom_join` object in the module
        my_module.custom_join = self._custom_join

    def tearDown(self):
        """Reload the module"""
        reload(my_module)

    def _custom_join(self, separator):
        """A factory for a custom join"""
        separator = '+{}+'.format(separator)
        return separator.join

    def test_smoke(self):
        """Do something"""
        my_module.my_func()

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

本当にmockライブラリが必要な場合は、_custom_joinメソッドが MagicMock オブジェクトを返すようにすることができます。

    def _custom_join(self, separator):
        """A factory for a custom join"""
        import mock

        return mock.MagicMock(name="{!r}.join".format(separator))
于 2014-05-22T11:42:02.237 に答える
0

ここでは、テストしているモジュール内の変数にパッチを当てています。テストに合わせてコードを変更しているので、このアイデアは好きではありませんが、うまくいきます。

tests.py

import mock

from main import func


@mock.patch('main.patched_str')
def test(patched_str):
    patched_str.join.return_value = "hello"

    result = func('1', '2')

    assert patched_str.join.called_with('1', '2')
    assert result == "hello" 


if __name__ == '__main__':
    test()

main.py

patched_str = ''

def func(*args):
    return patched_str.join(args)
于 2013-02-28T23:08:55.013 に答える
0

これらは常に組み込みクラスを使用するため、文字列リテラルで機能するこれを行う方法は実際にはありませんstr。これは、おわかりのように、この方法ではパッチできません。

もちろん、join(seq, sep='')代わりに使用し''.join()てパッチを適用する関数を作成することも、操作に使用される文字列を明示的に構築するために常に使用するstrサブクラス クラスを作成することもできます(例: )。これらの回避策はちょっと醜いですが、そうでなければメソッドにパッチを当てることはできません。SeparatorjoinSeparator('').join(....)

于 2013-02-28T22:26:55.440 に答える