JY-CONTENTS

JY

JY-CONTENTS
search

+

MENU

【Python】テスト関連モジュール

【Python】テスト関連モジュール

(DATE)

-

2017.06.26

(CATEGORY)

-

unittest

unittest はテストケースを実行するための python の標準モジュールです。

使い方

  1. unittestモジュールをimportして、unittest.TestCase を継承したクラスを作成。
  2. 任意のメソッドを用意し、(※1)assert~メソッドでテストをします。
    任意メソッドの名前は test_ で始まるメソッド名にする。(別にどんな名前でも問題なく実行する)
  3. unittest.main() を追加して、コマンドプロンプトンからファイルを実行すればテスト結果が表示される

※)1 結果を確認するためのメソッドです。メソッドの一覧はコチラの表を参照。

import unittest
 
class SimpleTests(unittest.TestCase):
    #任意のメソッドを用意
    def test_it(self):
        result = " ".join("abc")
        #assert~メソッドでテストを実行
        self.assertEqual(result, "a b c")
 
if __name__ == "__main__":
    unittest.main()

doctest

doctestモジュールは、テストコードとその結果をコメントとして書くことで自動的にテストを行ってくれます。
doctestのようにコメントにテストコードを書けば、それは実行例としてコメントに残り、コードを変更した場合にはそれを使ってテストができます。

使い方

  1. テストコードはコメントとして記述
  2. >>>で始まる行に実行する内容(テストコード)を記述
  3. その直後の行に 2. の実行結果の予測値を記述
    この予測した値が実行結果として正しければ何も出力されない。
    実行結果として誤っていればエラー内容が表示される。
  4. doctest モジュールの testmod 関数を実行することで呼び出し元(下記の例では sam.py)のクラスや関数内に書かれているdoctest(コメント内のコード)を実行して結果を表示する。

def sam(arg):
    #▼コメント内に記述
    """ 
    引数を10倍にして返す関数。
 
    >>> sam(5)
    50
    >>> sam(10)
    100
    """
    #▲コメント内に記述
 
    return arg * 10
  
 
if __name__ == "__main__":
    import doctest
    doctest.testmod()

上記の場合は実行結果の予測値は正しいのでなにも表示されない。

def sam(arg):
    #▼コメント内に記述
    """ 
    引数を10倍にして返す関数。
 
    >>> sam(5)
    10
    >>> sam(10)
    100
    """
    #▲コメント内に記述
 
    return arg * 10
  
 
if __name__ == "__main__":
    import doctest
    doctest.testmod()

上記の場合はsam(5) の実行結果として 10 を記述しているが、これは正しくないので下記のエラーが表示される。

空行の扱い

doctest では空行がテスト内容の終了と判断されてしまうため、テスト内容で空行を扱い場合は<BLANKLINE> と記述します。

def safe_str(s):
    """
    >>> print(safe_str(None))
    <BLANKLINE>
    >>> print(safe_str("test"))
    test
    """
    if s is None:
        return ""
    else:
        return s
 
if __name__ == '__main__':
    import doctest
    doctest.testmod()

モック

ランダムに返す値が変わったり、外部サービスと連携するようなメソッドやクラスをテストするときMock ライブラリを使用することで、テスト対象以外のオブジェクトはダミーなクラスや関数を作って、置き換えることで、テストがしやすくなります。
Mockでは、Mockオブジェクトを作り、置き換えたい関数(ダミーにしたい関数)に代入することで、その関数のダミーを作ることができます。
使用するダミーの関数の戻り値はreturn_valueで指定できます。

基本的な使い方

モック適用前

import random
 
def rand():
    return random.randint(0, 10)
 
def sam():
    if 5 > rand():
        return(True)
    else:
        return(False)
 
if __name__ == '__main__':
    #実行のたびに値が変更するので assert ではテストできない()
    assert ● == sam()

モック適用後

import random
from unittest import mock
 
def rand():
    return random.randint(0, 10)
 
def sam():
    if 5 > rand():
        return(True)
    else:
        return(False)
 
if __name__ == '__main__':
    #Mockオブジェクトを作り、置き換えたい関数(ダミーにしたい関数、ここでは rand 関数)に代入する。
    rand = mock.Mock()
    #ダミー化した rand 関数は 10 を返すように設定。
    rand.return_value = 10
 
    print(rand())  #10 を表示
    print(sam())   #False を表示

(注)Mockで置き換えた関数はそれ以降ずっとダミーの関数のままになってしまう。

assert

例外を故意に発生させるステートメントのひとつ。
assert ステートメントは特定の条件が満たされなかった場合にのみ raise ステートメントが実行されるというコードを短くするためのもの。

try:
    for c in range(5):
        print(c)
        assert c != 3  #この条件(c != 3)満たされなかったときexceptへ
except:
    print("except: %d" % c)
 
#出力結果
#0
#1
#2
#3
#except: 3

mock.patch(動的な置き換え)

Mockで置き換えた関数はそれ以降ずっとダミーの関数のままになってしまうので、テスト中のみダミー関数に置き換えるようにする。

※)Mockにはpatchデコレータやwith構文を使ったコンテキストマネージャーが用意されており、テスト中のみダミー関数に置き換えることができる。

例1(デコレータ)

外部モジュール test.py

class TestClass():
   def test_method():
      print("TestClass")
from unittest import mock
 
#関数 test_func が実行中のときだけ、ダミー関数に置き換えたいメソッドを
#patch デコレータの第1引数に渡す。
#ダミー関数の戻り値を(return_value=値)第2引数に渡す。
@mock.patch("test.TestClass.test_method", return_value=10)
def test_func(m): 
 
    import test
    print(test.TestClass.test_method())   #10 を表示
    assert test.TestClass.test_method() == 10
 
    #引数で受け取ったmはモック(ダミー)に置き換えられたtest_method関数
    #今回はメソッドを置き換えたが、クラスの場合はクラスになる。
    print(m())   #10 を表示
 
if __name__ == '__main__':
    test_func()
    #出力結果
    #10
    #10

上記はテストをするために関数 test_func を patch デコレータによってデコレーションしています。
test_func 関数がデコレーションされることによって、patch デコレータに渡されたメソッド(上記では外部モジュールの test_method)はtest_func 関数が実行中のときだけモック(ダミー関数)になります。

from unittest import mock
import test
 
@mock.patch("test.TestClass.test_method", return_value=10)
def test_func(m): 
 
    import test
    print(test.TestClass.test_method())
    assert test.TestClass.test_method() == 10
 
    print(m())
 
test_func()   #10 10 を表示
test.TestClass.test_method()   #TestClass を表示

test_func 関数を実行したときはダミー関数の戻り値 10 が表示されて、test.TestClass.test_method と普通に test_method メソッドを実行したときは“TestClass” が表示されているのがわかります。

例2(クラス内にデコレータを置いた場合)

外部モジュール mod1.py

import random
 
def func1():
    return random.randint(0, 100)

メインモジュール mod2.py

import mod1
 
def func2():
    value = mod1.func1()
    if value % 2 == 0:
        return "even"
    else:
        return "odd"

モジュール mod2 の func2 メソッドのテストを行うモジュール test.py

import unittest
from unittest import mock
from mod2 import func2
 
class func2Tests(unittest.TestCase):
    @mock.patch("mod1.func1", return_value = 33)
    #メソッドの第1引数にはクラス(func2Tests)が渡されます。
    #メソッドの第2引数には@mock.patchの第1引数のメソッドが渡されます。
    def test_odd(self, func1):
        result = func2()
        self.assertEqual(result, "odd")
 
        print(result)   #addと出力
        self.test_even()   #evenと出力
 
    @mock.patch("mod1.func1")
    def test_even(self, func1):
        func1.return_value = 24
        result = func2()
        self.assertEqual(result, "even")
 
        print(result)   #evenと出力
 
 
func2Tests().test_odd()   #add even と表示
func2Tests().test_even()   #even と表示

mod2 モジュールは mod1 モジュールの func1 メソッドから返された0~100までのランダムな値を受け取り、その値が偶数の場合は「even」、奇数の場合は「odd」を返します。
mod2 モジュールの func2 メソッドの動きをテストするとき毎回ランダムな値が返されてはfunc2 メソッドの動きをテストできないため test.py のテストメソッド(test_odd、test_even)ではランダムな値を返す mod1 モジュールの func1 メソッドをモックに置き換えています。

例3(with文)

import unittest
from unittest import mock
from mod2 import func2
 
def test_func():
    #このwithスコープ実行中だけ置き換えるメソッドと戻り値を指定
    with mock.patch("mod1.func1", return_value=9) as m:
        result = func2()
        print(result)
        try:
            assert result == "even"
        except:
            print('エラー')
 
 
    #以降は元の関数が使用される
    ...
 
if __name__ == '__main__':
    test_func()
    #odd 
    #エラー 
    #を表示

NEW TOPICS

/ ニュー & アップデート

SEE ALSO

/ 似た記事を見る

JY CONTENTS UNIQUE BLOG

search-menu search-menu