TestCase
いくつかのtest_*メソッドを持つ抽象を作成することは可能ですか?しかし、これTestCase
は呼び出されず、それらのメソッドはサブクラスでのみ使用されますか?テストスイートに1つの抽象TestCase
があり、単一のインターフェイスのいくつかの異なる実装用にサブクラス化されると思います。これが、すべてのテストメソッドが一部の唯一の内部メソッド変更である理由です。どうすればエレガントな方法でそれを行うことができますか?
12 に答える
私はあなたが何をするつもりなのかよく理解していませんでした-経験則は「テストで賢くならないこと」です-ただそこにそれらを平易に書いてください。
しかし、必要なことを達成するために、unittest.TestCaseから継承する場合、unittest.main()を呼び出すたびに、「抽象」クラスが実行されます。これは避けたい状況だと思います。
これを行うだけです。TestCaseからではなく、「object」から継承する「abstract」クラスを作成します。また、実際の「具体的な」実装では、多重継承を使用します。つまり、unittest.TestCaseと抽象クラスの両方から継承します。
import unittest
class Abstract(object):
def test_a(self):
print "Running for class", self.__class__
class Test(Abstract, unittest.TestCase):
pass
unittest.main()
update:継承の順序を逆にしました-Abstract
最初に、その定義がTestCase
デフォルトで上書きされないようにします。また、以下のコメントで指摘されています。
これまで誰もが見逃していた非常に簡単な方法があります。また、いくつかの回答とは異なり、テストドライバーを切り替えるとすぐに失敗するのではなく、すべてのテストドライバーで機能します。
通常どおり継承を使用してから、次を追加します。
del AbstractTestCase
モジュールの最後に。
多重継承は、主に次の2つの理由から、ここでは優れたオプションではありません。
- 使用中のメソッドはないため、やのようなメソッドを使用
TestCase
するにsuper()
は、最初にクラスをリストする必要があります。setUp()
tearDown()
- pylintは、基本クラスがその時点で
self.assertEquals()
定義されていないetcを使用していることを警告します。self
これが私が思いついた恨みです:run()
基本クラスのみのノーオペレーションになります。
class TestBase( unittest.TestCase ):
def __init__( self, *args, **kwargs ):
super( TestBase, self ).__init__( *args, **kwargs )
self.helper = None
# Kludge alert: We want this class to carry test cases without being run
# by the unit test framework, so the `run' method is overridden to do
# nothing. But in order for sub-classes to be able to do something when
# run is invoked, the constructor will rebind `run' from TestCase.
if self.__class__ != TestBase:
# Rebind `run' from the parent class.
self.run = unittest.TestCase.run.__get__( self, self.__class__ )
else:
self.run = lambda self, *args, **kwargs: None
def newHelper( self ):
raise NotImplementedError()
def setUp( self ):
print "shared for all subclasses"
self.helper = self.newHelper()
def testFoo( self ):
print "shared for all subclasses"
# test something with self.helper
class Test1( TestBase ):
def newHelper( self ):
return HelperObject1()
class Test2( TestBase ):
def newHelper( self ):
return HelperObject2()
ミックスインの代わりに継承を本当に使用したい場合、簡単な解決策は、抽象テストを別のクラスにネストすることです。
テストランナーの検出に関する問題を回避し、別のモジュールから抽象テストをインポートできます。
import unittest
class AbstractTests(object):
class AbstractTest(unittest.TestCase)
def test_a(self):
print "Running for class", self.__class__
class Test(AbstractTests.AbstractTest):
pass
私の2セントを入れると、慣例に反する可能性がありますが、抽象テストケースを保護されたメンバーとして定義して、その実行を防ぐことができます。私はDjangoで以下を実装し、必要に応じて動作します。以下の例を参照してください。
from django.test import TestCase
class _AbstractTestCase(TestCase):
"""
Abstract test case - should not be instantiated by the test runner.
"""
def test_1(self):
raise NotImplementedError()
def test_2(self):
raise NotImplementedError()
class TestCase1(_AbstractTestCase):
"""
This test case will pass and fail.
"""
def test_1(self):
self.assertEqual(1 + 1, 2)
class TestCase2(_AbstractTestCase):
"""
This test case will pass successfully.
"""
def test_1(self):
self.assertEqual(2 + 2, 4)
def test_2(self):
self.assertEqual(12 * 12, 144)
unittest.SkipTest
で育つsetUpClass()
もう1つのアプローチは、基本クラスのunittest.SkipTest
inを上げ、子クラスsetUpClass()
でオーバーライドすることです。setUpClass()
class BaseTestCase(TestCase):
@classmethod
def setUpClass(cls):
"Child classes must override this method and define cls.x and cls.y"
raise unittest.SkipTest
def test_x(self):
self.assertEqual(self.x * 3, self.x)
def test_y(self):
self.assertEqual(self.y * 3, self.y + self.y + self.y)
def test_z(self):
self.assertEqual(self.x + self.y, self.y)
class IntegerTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
cls.x = 0
cls.y = 2
class StringTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
cls.x = ''
cls.y = 'zuzuka'
独自のテストケースを定義するカスタムTestCaseを使用する必要がsetUpClass()
あり、を呼び出す必要があるsuper().setUpClass()
場合は、独自のメソッドを定義して「データを設定」し、そのメソッド内でのみSkipTestを発生させることができます。
class BaseTestCase(ThidPartyTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass() # if ThirdPartyTestCase has own setUpClass()
cls.setUpTestCaseData()
@classmethod
def setUpTestCaseData(cls):
"Override and set up cls.x and cls.y here"
raise unittest.SkipTest
... # tests
class IntegerTestCase(BaseTestCase):
@classmethod
def setUpTestCaseData(cls):
cls.x = 0
cls.y = 2
run_unittestにすべてのテストクラスを明示的にリストするという規則に従う場合(たとえば、その規則の多くの使用法についてはPythonテストスイートを参照)、特定のクラスをリストしないのは簡単です。
unittest.mainの使用を継続し、unittest2の使用を許可できる場合(Python 2.7など)、そのload_testsプロトコルを使用して、テストケースを含むクラスを指定できます。以前のバージョンでは、TestLoaderをサブクラス化し、loadTestsFromModuleをオーバーライドする必要があります。
Pythonユニットテストライブラリにはload_testsプロトコルがあり、これを使用して目的を正確に達成できます。
# Add this function to module with AbstractTestCase class
def load_tests(loader, tests, _):
result = []
for test_case in tests:
if type(test_case._tests[0]) is AbstractTestCase:
continue
result.append(test_case)
return loader.suiteClass(result)
このunittest
モジュールには、テストをスキップするためのいくつかのオプションがあります。
私の推奨する解決策はsetUpClass
、「abstract」基本クラスのメソッドをオーバーライドしてunittest.SkipTest
、必要に応じて例外を発生させることです。
class BaseTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
if cls is BaseTestCase:
raise unittest.SkipTest("%s is an abstract base class" % cls.__name__)
else:
super(BaseTestCase, cls).setUpClass()
OPが行っていることを実行したいもう一つの理由は、いくつかの環境/シナリオで再現する必要がある一連のコアテストの多くを実装する高度にパラメータ化された基本クラスを作成することです。私が説明しているのは、基本的に、ユニットテストを使用して、パラメーター化されたフィクスチャ、lapytestを作成することです。
あなた(私のように)が多重継承ベースのソリューションからできるだけ早く逃げることを決定したとすると、load_tests()を使用してロードされたスイートから基本クラスを除外することで次の問題が発生する可能性があります。
標準のTestLoaderでは、クラスからの自動ロードが実行された後にload_testsが呼び出されます。理由:*このauto-loading-from-classは、標準の署名init(self、name)を使用して基本クラスからインスタンスを構築しようとします。*この基本クラスに非常に異なるctor署名を持たせたい場合、または*他の理由で、基本クラスインスタンスの構築をスキップしてから削除することをお勧めします
..基本クラスからのテストインスタンスのこの自動ロードを完全に防止したい場合があります。
編集:この他のスレッドでのVadimのソリューションは、これを行うためのよりエレガントで簡潔な独立した方法です。「ネストされたクラストリック」を実装し、TestLoaderがTestCaseベースを「検出」するのを防ぐ目的で美しく機能することを確認しました。
私はもともと、TestLoader.loadTestsFromModuleを変更して、モジュール内の他のTestCaseクラスの基本クラスとして機能するTestCaseクラスをスキップすることでこれを実行していました。
for name in dir(module):
obj = getattr(module, name)
# skip TestCase classes:
# 1. without any test methods defined
# 2. that are base classes
# (we don't allow instantiating TestCase base classes, which allows test designers
# to implement actual test methods in highly-parametrized base classes.)
if isinstance(obj, type) and issubclass(obj, unittest.TestCase) and \
self.getTestCaseNames(obj) and not isbase(obj, module):
loaded_suite = self.loadTestsFromTestCase(obj)
# ignore empty suites
if loaded_suite.countTestCases():
tests.append(loaded_suite)
どこ:
def isbase(cls, module):
'''Returns True if cls is base class to any classes in module, else False.'''
for name in dir(module):
obj = getattr(module, name)
if obj is not cls and isinstance(obj, type) and issubclass(obj, cls):
return True
return False
上記で説明したパラメータ化は、各子クラスにフィクスチャの詳細(パラメータ)を定義させ、それらを基本クラスのTestCase ctorに渡して、すべての一般的なimplメソッド(「フィクスチャ」メソッドsetUp * / tearDown * / cleanup *とテストメソッド自体)には、その子TestCaseクラスが操作する非常に具体的なフィクスチャを定義するすべての情報が含まれています。
私にとって、これは、ユニットテストでいくつかのパラメーター化されたフィクスチャをすばやく実装するための一時的なソリューションでした。チームのテストをできるだけ早くpytestに移行する予定だからです。
これは、一般的なテストがTestCaseから継承できるようにする(タイプチェックとIDEツールが満足できる)比較的単純なアプローチであり、文書化された単体テスト機能のみを使用し、「スキップ」テストステータスを回避します。
import unittest
class CommonTestCases(unittest.TestCase):
def __init__(self, methodName='runTest'):
if self.__class__ is CommonTestCases:
# don't run these tests on the abstract base implementation
methodName = 'runNoTestsInBaseClass'
super().__init__(methodName)
def runNoTestsInBaseClass(self):
print('not running tests in abstract base class')
pass
def test_common(self):
# This will run *only* in subclasses. Presumably, this would
# be a test you need to repeat in several different contexts.
self.assertEqual(2 + 2, 4)
class SomeTests(CommonTestCases):
# inherited test_common *will* be run here
def test_something(self):
self.assertTrue(True)
# Also plays nicely with MRO, if needed:
class SomeOtherTests(CommonTestCases, django.test.SimpleTestCase):
# inherited test_common *will* be run here
def test_something_else(self):
self.client.get('/') # ...
仕組み:unittest.TestCase
ドキュメントによると、「TestCaseの各インスタンスは、methodNameという名前のメソッドという単一の基本メソッドを実行します。」デフォルトの「runTests」は、クラスですべてのtest *メソッドを実行します。これが、TestCaseインスタンスが通常機能する方法です。ただし、抽象基本クラス自体で実行する場合は、何もしないメソッドでその動作を単純にオーバーライドできます。
副作用として、テスト数が1つ増えます。つまり、runNoTestsInBaseClass "test"は、CommonTestCasesで実行されると、成功したテストとしてカウントされます。
私はそれを次のように行いました、多分それはあなたにインスピレーションを与えることができます:
class AbstractTest(TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def _test_1(self):
# your test case here
class ConcreteTest(AbstractTest)
def test_1(self):
self._test_1()
これは最も便利なソリューションではありませんが、多重継承から逃れることができます。また、Dan Wardによって提案されたソリューションは、PyCharmでのDjangoテストでは機能しませんでした。