概要
Jupyter/ColabのPythonコードセルで標準のdoctestやunittestが動かせたのでメモとして残しておく。
doctest
一般的にはメソッド内にdoctestを記述してdoctest.testmod()
を呼び出すことでテストを実行する仕組みですが、コードセル内でも同様のコードでテストすることができます。vervose=True
を渡すとテストが成功した場合でも何をテストをしたのわかるので渡すようにしています。(Falseだと成功した場合は何も出力しません)
def add(a, b):
"""
>>> add(1, 2)
3
"""
return a + b
if __name__ == "__main__":
import doctest
doctest.testmod(verbose=True)
関数内にテストコードを書かずに別のセルに書く事もできます。一般的には別ファイルにテストコードを書いてdoctest.testfile()
で読み込んでテストするのですが、doctest.run_docstring_examples()
を使えばテストコードをファイルではなく文字列で渡してテストすることができるようです。
def add3(a, b, c):
return a + b + c
import doctest
teststr = """
>>> add3(1, 2, 3)
6
"""
doctest.run_docstring_examples(teststr, globals(), verbose=True)
最終的にPythonスクリプトを出力する前提で、出力したいコード部分を%%writefile
マジックコマンドをつけたコードセルに書いておき、出力したPythonファイルに対して別のコードセルからテストを実行できると便利なのでやってみました。ただ単純にimportするだけだと更新前のコードをキャッシュしてコードの変更が反映されないので、ちょっと工夫する必要がありました。
%%writefile add5.py
def sum5(a, b, c, d, e):
return a + b + c + d + e
import doctest
import sys
import importlib
sys.dont_write_bytecode = True
import add5
importlib.reload(add5)
from add5 import sum5
teststr = """
>>> sum5(1, 2, 3, 4, 5)
15
"""
doctest.run_docstring_examples(teststr, globals(), verbose=True)
sys.dont_write_bytecode = True
はimportの際__pycache__
ディレクトリにバイトコードを出力させないようにする設定です。変更の反映には影響なさそうですが、コーディング・テスト中にバイトコードを出力されてもしょうがないのでつけています。
importlib.reload(add5)
を呼び出すことで変更したコード(モジュール)を再度読み直してくれて変更が反映されるようです。
Colabでも全く同じようにテストの実行が可能なので、確認用に用意してみました。
unittest
unittest標準ライブラリでもdoctestと同じようにコードセルからテストができるようです。
一般的にはunittest.TestCase
を継承したテストクラスを定義してunittest.main()
を呼び出すのですが、コードセルでも同様にして実行できます。ただし、コードセル上では引数としてargv
とexit=False
を渡さないとエラーが出るようです。
import unittest
def add(a, b):
return a + b
class TestNotebook(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 2), 4)
unittest.main(argv=[""], verbosity=2, exit=False)
普通に考えればそりゃそうだろうとは思いますが、一応テスト対象のコードとテストクラスを別々のコードセルに書くこともできます
def add3(a, b, c):
return a + b + c
import unittest
class TestNotebook3(unittest.TestCase):
def test_add3(self):
self.assertEqual(add3(2, 2, 2), 6)
unittest.main(argv=["", "TestNotebook3.test_add3"], verbosity=2, exit=False)
引数argv
に渡すリストの2番目に実行したいメソッドを渡すとそのメソッドだけテストを実行してくれます。何も指定しなかった場合は実行済みのコードセルに書いたテストクラスすべてをテストします。
同様に、別ファイルに出力したPythonファイルに対して別のコードセルからテストを実行することができます。
%%writefile add6.py
def sum6(a, b, c, d, e, f):
return a + b + c + d + e + f
import unittest
import sys
import importlib
sys.dont_write_bytecode = True
import add6
importlib.reload(add6)
from add6 import sum6
class TestNotebook6(unittest.TestCase):
def test_sum6(self):
self.assertEqual(sum6(1, 2, 3, 4, 5, 6), 21)
unittest.main(argv=["", "TestNotebook6.test_sum6"], verbosity=2, exit=False)
こちらもColabでもテスト実行可能なので、用意しました
感想
Jupyterはコードの試行錯誤やテストにもってこいの環境であり、そこにテストライブラリを導入することによりさらにJupyter内でPythonコードの完成度を上げることができるので積極的に活用したい。