web-dev-qa-db-ja.com

プライベート関数を模擬するための最も/最もPython的な方法は何ですか?

次のような「パブリック」関数を持つモジュールを考えてみます。

def func(arg):
    val = _generate_something(arg)
    _do_something(val)

ご覧のとおり、これは「void関数」です。そのコアロジックは_generate_something

この関数の単体テストを記述したいと思います。そうするためのいくつかのアプローチを考えることができます。一般的なプログラミングプラクティスとPython具体的には)のベストプラクティスの両方の観点から、どちらが最良かを知りたいと思います。

基本的に、この質問は、プライベート関数をモックするPythonの方法(お金のパッチ適用、関数の注入、オブジェクトの注入など)に関するものです。


オプションA

テストではmonkeypatch _do_something(これはモジュールの「プライベート」関数です)なので、適切な値を受け取っていることを確認できます。


オプションB

注入_do_somethingfuncの引数として、次のように指定します。

def func(arg, do_something=None):
    do_something = do_something or _do_something  # in production use default
    val = _generate_something(arg)
    do_something(val)

その後、私のdo_somethingモック正しい値を受け取ったことを確認できます。


オプションC

funcをクラスのメソッドにして、そのクラスにdo_somethingの引数として__init__

class Something(object):
    def __init__(self, do_something=None):
        self._do_something = do_something or _production_default

    def func(self, arg):
        val = self._generate_something(arg)
        self.do_something(val)

    # ... omitted for brevity ...

オプションD

オプションBまたはCに似ていますが、do_somethingは、ベア関数ではなく「完全な」オブジェクトとして挿入されます。例えば:

def func(arg, something_doer=None):
    something_doer = something_doer or _something_doer # in production use default
    val = _generate_something(arg)
    something_doer.do_something(val)

私が言ったように、私はまた、 'Pythonicness'に関して、最良の選択肢が何であるかを知りたいです。たとえば、Python=でそれを含むことができるオブジェクトではなく)裸の関数をモックすることは「受け入れられる」ですか?

1
Aviv Cohn

PythonおそらくオプションA、 monkey patching にある)最も単純で最短のアプローチを使用することをお勧めします。ここで変更する唯一のことは、このソリューションをより明確にすることです(以下を参照)。

モジュールをクラスに変換する(オプションC)の動機は、単一の関数をモックしたいという事実ではなく、モジュールではなくクラスが必要なことです。このオプションは、JavaまたはC#などの言語で、Pythonに匹敵する方法で「モジュール」と「クラス」の区別がなく、funcのような関数がある場合に適しています。おそらく最初はクラスの中で「生きている」でしょう。

メソッドの置き換えdo_somethingメソッドを含むオブジェクト(オプションD)は同じ理由で実行しないでください。スタンドアロン関数が「不十分」である場合は、スタンドアロン関数の代わりにオブジェクトを使用してください。モックするという純粋な目的のため、利点はありません。

したがって、オプションBまたはAが残ります。ただし、モックを目的としてfuncのパラメーターリストを汚染することはありません。また、単純な代替手段がある場合は、モックするためだけにモックコードを関数funcに詰め込みません。 「公式」APIと関数コードをできるだけクリーンに保つことをお勧めします。 「プライベート」メソッドを1つだけではなくモックしたい場合、およびモジュールの5つのパブリックメソッドをテストしたい場合を想像してみてください。APIには90%のパラメーターが含まれているため、テストにのみ使用され、可読性が大幅に低下します。 。

モンキーパッチアプローチがプライベートなものを変更するために懸念がある場合は、モジュールに「パブリック」(または「内部」)パッチ関数を追加できます。これにより、「公式」関数を介して操作を実行できます。

def setup_mock_for_do_something(do_something):
    _do_something = do_something

これにより、コード全体への不要な構造変更を同時に回避することにより、このモジュールで「サルのパッチ」の可能性がより明確になります。

では、なぜこれがすべて「Pythonic」なのでしょうか。 "The Zen of Python" を見ると、次の原則がここで適用されていることがわかります。

  • 明示的は暗黙的よりも優れています。
  • シンプルは複雑よりも優れています。
  • 読みやすさが重要です。
  • 実装が簡単に説明できる場合は、良いアイデアかもしれません。
2
Doc Brown

注:OPは質問を大幅に編集および変更したため、この回答は少し時代遅れですが、今のところ履歴に残しておきます。元の答えは次のとおりです:

_generate_something()_do_something()が機能し、適切にユニットテストが個別にされていると仮定すると、適切な引数func()のテストをまったく邪魔しないようにします。 IMO、それはユニットテストでのリターンの減少のポイントをはるかに超えています。 YMMV。 (注:例の2行の間にコード行が多数あった場合、テストは理にかなっています)

たとえば、オプションBDは、私が理解しているように、_generate_something()の結果をテストするための複雑な方法であり、直接テストできますさて、多分それらはあなたのプログラムの全体的な設計で意味をなすかもしれませんが、ユニットテストのためだけにそのような複雑さを加えることは間違いです。

1
user949300