15

I have a class like the following:

class A:
    def __init__(self, arg1, arg2, arg3):
        self.a=arg1
        self.b=arg2
        self.c=arg3
        # ...
        self.x=do_something(arg1, arg2, arg3)
        self.y=do_something(arg1, arg2, arg3)

        self.m = self.func1(self.x)
        self.n = self.func2(self.y)
        # ...

    def func1(self, arg):
        # do something here

    def func2(self, arg):
        # do something here

As you can see, initializing the class needs to feed in arg1, arg2, and arg3. However, testing func1 and func2 does not directly require such inputs, but rather, it's simply an input/output logic.

In my test, I can of course instantiate and initialize a test object in the regular way, and then test func1 and func2 individually. But the initialization requires input of arg1 arg2, arg3, which is really not relevant to test func1 and func2.

Therefore, I want to test func1 and func2 individually, without first calling __init__. So I have the following 2 questions:

  1. What's the best way of designing such tests? (perferably, in py.test)
  2. I want to test func1 and func2 without invoking __init__. I read from here that A.__new__() can skip invoking __init__ but still having the class instantiated. Is there a better way to achieve what I need without doing this?

NOTE:

There have been 2 questions regarding my ask here:

  1. Is it necessary to test individual member functions?
  2. (for testing purpose) Is it necessary to instantiating a class without initializing the object with __init__?

For question 1, I did a quick google search and find some relevant study or discussion on this:

We initially test base classes having no parents by designing a test suite that tests each member function individually and also tests the interactions among member functions.

For question 2, I'm not sure. But I think it is necessary, as shown in the sample code, func1 and func2 are called in __init__. I feel more comfortable testing them on an class A object that hasn't been called with __init__ (and therefore no previous calls to func1 and func2).

Of course, one could just instantiate a class A object with regular means (testobj = A()) and then perform individual test on func1 and func2. But is it good:)? I'm just discussing here as what's the best way to test such scenario, what's the pros and cons.

On the other hand, one might also argue that from design perspective one should NOT put calls to func1 and func2 in __init__ in the first place. Is this a reasonable design option?

4

3 に答える 3

11

通常、クラスをインスタンス化せずに(実行を含む__init__)クラスのメソッドをテストすることは有用ではなく、不可能ですらあります。通常、クラスメソッドはクラスの属性を参照します(例:)self.a。を実行しない__init__と、これらの属性は存在しないため、メソッドは機能しません。(メソッドがインスタンスの属性に依存しない場合、スタンドアロン関数だけでなくメソッドであるのはなぜですか?)この例では、初期化プロセスのように見え、func1そのfunc2一部であるため、の一部としてテストする必要があります。それ。

__new__理論的には、必要なメンバーだけを使用して追加することで、クラスを「準インスタンス化」することができます。例:

obj = A.__new__(args)
obj.a = "test value"
obj.func1()

ただし、これはおそらくテストを行うのにあまり良い方法ではありません。1つには、初期化コードにすでに存在していると思われるコードを複製することになります。つまり、テストが実際のコードと同期しなくなる可能性が高くなります。もう1つは、この方法で多くの初期化呼び出しを複製する必要がある場合があります。これは__init__、クラスから呼び出される基本クラスのメソッドによって行われることを手動でやり直す必要があるためです。

テストの設計方法については、unittestモジュールnoseモジュールをご覧ください。これで、テストの設定方法の基本がわかります。実際にテストに何を入れるかは、明らかにコードが何をするかによって異なります。

編集:あなたの質問1への答えは「間違いなくそうですが、必ずしもすべてのものではありません」です。あなたの質問2への答えは「おそらくそうではない」です。最初に提供するリンクでも、クラスのパブリックAPIの一部ではないメソッドをテストする必要があるかどうかについては議論があります。func1とfunc2が純粋に初期化の一部である内部メソッドである場合、初期化とは別にそれらをテストする必要はおそらくありません。

これは、内からfunc1とfunc2を呼び出すことが適切かどうかについての最後の質問になります__init__。コメントで繰り返し述べたように、それはこれらの関数が何をするかに依存します。func1とfunc2が初期化の一部を実行する場合(つまり、インスタンスに対して何らかの「セットアップ」作業を実行する場合)、;からそれらを呼び出すことは完全に合理的__init__です。ただし、その場合は、初期化プロセスの一部としてテストする必要があり、個別にテストする必要はありません。func1とfunc2が初期化の一部でない場合は、はい、それらを個別にテストする必要があります。しかし、その場合、なぜ彼らは入っているの__init__ですか?

クラスのインスタンス化の不可欠な部分を形成するメソッドは、クラスのインスタンス化のテストの一部としてテストする必要があります。クラスのインスタンス化の不可欠な部分を形成しないメソッドは、内から呼び出さないでください__init__

func1とfunc2が「単に入出力ロジック」であり、インスタンスへのアクセスを必要としない場合、それらはクラスのメソッドである必要はまったくありません。それらはスタンドアロン機能にすることができます。それらをクラスに保持したい場合は、それらをstaticmethodsとしてマークしてから、インスタンス化せずにクラスで直接呼び出すことができます。次に例を示します。

>>> class Foo(object):
...     def __init__(self, num):
...         self.numSquared = self.square(num)
...     
...     @staticmethod
...     def square(num):
...         return num**2
>>> Foo.square(2) # you can test the square "method" this way without instantiating Foo
4
>>> Foo(8).numSquared
64

非常に複雑な初期化プロセスを必要とするモンスタークラスがあるかもしれないと想像することができます。このような場合、そのプロセスの一部を個別にテストする必要がある場合があります。ただし、このような巨大なinitシーケンスは、それ自体が扱いにくい設計の警告になります。

于 2012-07-24T03:45:14.317 に答える
3

選択肢があれば、初期化ヘルパー関数をstaticmethodsとして宣言し、テストから呼び出すだけです。

アサートする入力/出力値が異なる場合は、py.testを使用してパラメータ化の例を調べることができます。

クラスのインスタンス化がやや重い場合は、依存性注入を調べて、次のようにインスタンスをキャッシュすることをお勧めします。

# content of test_module.py

def pytest_funcarg__a(request):
    return request.cached_setup(lambda: A(...), scope="class")

class TestA:
    def test_basic(self, a):
        assert .... # check properties/non-init functions

これにより、各テストクラスで同じ「a」インスタンスが再利用されます。他の可能なスコープは、「セッション」、「関数」、または「モジュール」です。コマンドラインオプションを定義してスコープを設定することもできます。これにより、テストソースコードを変更することなく、迅速な開発のためにより多くのキャッシュを使用し、継続的インテグレーションのためにより分離されたリソースセットアップを使用できます。

個人的には、過去12年間で、リファクタリングが容易になり、全体的に時間を有効に活用できるようになったため、きめ細かい単体テストからより機能的/統合的なタイプのテストに移行しました。もちろん、PDBへのドロップ、簡潔なトレースバックなど、障害が発生したときに適切なサポートとレポートを用意することは重要です。複雑なアルゴリズムの中には、非常にきめ細かい単体テストを作成するものもありますが、通常、アルゴリズムを非常に独立して分離します。テスト可能なもの。

HTH、ホルガー

于 2012-07-25T08:05:12.147 に答える
1

インスタンス化後に呼び出されるメソッドにfunc1etc呼び出しを移動するなど、インスタンス化で実行される作業の量を減らすことで、この問題を回避する方が一般的に良いという以前のコメントに同意します。configure(self)

self.func1などへの呼び出しを維持する強い理由が__init__ある場合は、役立つ可能性のあるアプローチがpytestあります。

(1)これをモジュールに入れます:

_called_from_test = False

(2)conftest.pyに以下を入れてください

import your_module

def pytest_configure(config):
    your_module._called_from_test = True

の適切な名前your_module

(3)テストを実行しているときに、早期にif実行を終了するステートメントを挿入します。__init__

   if _called_from_test:
      pass
   else:
      self.func1( ....)

次に、個々の関数呼び出しをステップ実行して、実行しながらそれらをテストできます。

_called_from_test同じことは、のオプションの引数を作成することによって達成できます__init__

ドキュメントのpytestrunセクション内から実行している場合は、Detectifでより多くのコンテキストが提供されpytestます。

于 2020-06-15T16:33:25.430 に答える