前回の記事で扱ったとおり,unittestがどのようにファイル内から継承したクラスを取り出すのかを確認しました。今回は,前回割愛した"継承したクラスからテスト用メソッドを取り出す方法"を確認したいと思います。
前回のおさらい
unittest/loader.pyの119行目以降にあるコードによりunittestはファイル(モジュール)に定義されたcase.TestCase
を継承するクラスを見つけ,tests
リストに追加(append)されます。その際,抽出されたクラスにself.loadTestsFromTestCase
メソッドが適用され,その結果がtests
に追加されます。
tests = []
for name in dir(module):
obj = getattr(module, name)
if isinstance(obj, type) and issubclass(obj, case.TestCase):
tests.append(self.loadTestsFromTestCase(obj))
loadTestsFromTestCaseメソッド
上記のように,loadTestsFromTestCase
メソッドが抽出されたクラスをもとに戻り値をappend
メソッドにに渡しているため,こちらのメソッド(unittest/loader.pyの83行目以降)の中身を見ていきます。
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all test cases contained in testCaseClass"""
if issubclass(testCaseClass, suite.TestSuite):
raise TypeError("Test cases should not be derived from "
"TestSuite. Maybe you meant to derive from "
"TestCase?")
testCaseNames = self.getTestCaseNames(testCaseClass)
if not testCaseNames and hasattr(testCaseClass, 'runTest'):
testCaseNames = ['runTest']
loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
return loaded_suite
メソッド内のコメント,メソッド名,変数名などから推測できるように,中段にあるgetTestCaseNames
でtestCaseClass
クラス内に定義されたテスト用メソッドを抽出しているようです。
getTestCaseNamesメソッド
getTestCaseNames
メソッドは,unittest/loader.pyの222行目以降にあります。
def getTestCaseNames(self, testCaseClass):
"""Return a sorted sequence of method names found within testCaseClass
"""
def isTestMethod(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
return attrname.startswith(prefix) and \
callable(getattr(testCaseClass, attrname))
testFnNames = list(filter(isTestMethod, dir(testCaseClass)))
if self.sortTestMethodsUsing:
testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing))
return testFnNames
コメントにそのまま記述されているとおり,引数として受け取ったtestCaseClass
を継承したクラス内からテスト用に定義したメソッドを抽出することが,このメソッドの目的のようです。ここで2つのことに目が行きます。1つは前回の処理と同様,対象候補を抽出するためにdir
を利用していることです。前回は,ファイル(モジュール)内のクラスを抽出する方法としてdirを利用していましたが,これと似た方法として,クラスからメソッドを抽出する際にもdirを利用しています。もう1つは,メソッド内に定義されたisTestMethod
メソッドです。filter
関数の第1引数として利用されていることからも分かるとおり,このメソッドは判定に使用され,その判定条件は以下になります。
- dirで取得した属性名の先頭が"test"で始まるかどうか
- その属性名はコール可能か(すなわちメソッドかどうか)
上記の1つ目は,isTestMethod
内でstartswith(prefix)
を利用していることからわかります。2つ目は,getattr
を利用して属性名からオブジェクトを取り出し,callable
によって呼び出し可能かどうかを判定していることからわかります。
まとめ
以上のように,ファイル(モジュール)から特定のクラスを抽出するケースやクラス内の特定のメソッドを抽出するケースでは,以下の流れで進めることが有効であることがわかりました。
- dir関数でそのモジュール(クラス)の属性名のリストを取得する
- getattr関数で属性名からオブジェクトを取得し,そのオブジェクトが持つ性質を判定できるようにする
- if文やfilter関数を利用して,条件にマッチする属性名を抽出する
個人的には,getattr
関数をどの場面で場面で利用すればよいのかよくわかっていなかったため,上記のように,"dirで取得した属性名が抽出したい対象かどうかを判定するため,その属性名のオブジェクトを取得する"場面で利用できることがわかったのは大きな収穫でした。