私のPythonでのUnitテスト

注意

pytestに重きを置いています。

Unitテストの鉄則

処理結果が期待した値と一致することを確認するテストで、期待した値は決め打ちで用意する。

※期待した値を用意する際に、テスト対象の処理と同等の処理を行うと値が同じことは明白であり、処理結果が期待した値か不明瞭なため。

# テスト対象の関数
def getFullName(name1, name2):
 return name1 + ' ' + name2

# Bestなテスト
assert getFullName('コード', '太郎') == 'コード 太郎'

# Badなテスト(テスト時に関数と同じ処理を書いてしまっている)
assert getFullName('コード', '太郎') == 'コード' + ' ' + '太郎'

1テストにつき、1Outputだけテストする

検証であれば以下のように1メソッドにつき1種類だけテストします。下記の例では検証が正しい/正しくないの2つで分けています。これを1つのメソッドで全てテストするのはよくないといったところです。

# テスト対象の関数
def check_number(num):
    return 0 <= num and num <= 100

# テスト
class Test_check_number:
    @pytest.mark.parametrize("text", [0, 50, 100])
    def test_invalid(self, text):
        """ 検証が正しい場合
        """
        assert check_number(text)
    
    @pytest.mark.parametrize("text", [-1, 101])
    def test_invalid(self, text):
        """ 検証が正しくない場合
        """
        assert not check_number(text)

テストコードでも難読化させない!

テスト対象の処理を実行とその準備、実行結果の検証を明確にしよう!検証を行うために、特に準備が複雑になりがちですが、使いまわせる部分は分けたりして簡素化しましょう。

Arrange Act Assert にベストプラクティスがまとめられていそう。

以下のサンプルがサンプルにならないほどいい加減ですが…。

# 
class TestAPI:
    # これも準備
    @pytest.fixture
    def target_url(self):
        return "/api/process"
    
    def test_do(self, target_url, django_app):
        # 準備
        param = {
            "key": "assert word"
            , "item": 0
        }
        
        # 実行
        res = django_app.post_json(target_api, params=param)
        
        # 検証
        assert res.status_code == 200

テストデータは完了後、削除する!

単純に作成したデータを残したままにしていると容量が莫大で圧迫することになる、というのもある。

が、それ以外にもデータが都度消されなければ大量のデータが混じってしまい、意図しないテストを行ってしまう可能性もあります。

import pytest

class TestCal(object):

    @classmethod
    def setup_class(cls):
        # テストクラス開始時に
        print('Test start: TestCal')

    @classmethod
    def teardown_class(cls):
        # テストクラス終了時に
        print('Test finish: teardown_class')

    def setup_method(self,method):
        # テストメソッドの開始毎に
        print('setup: method={}'.format(method.__name__))
        
        import tempfile
        self.test_fp = tempfile.NamedTemporaryFile(mode="w", encoding="utf-8")
        self.test_csv = self.test_fp.name
        self.test_fp.writelines([
            'item1,item2\n'
            , 'item1,item2\n'
            , 'item1,item2\n'
        ])
        self.test_fp.seek(0)

    def teardown_method(self, method):
        # テストメソッドの終了毎に
        print('teardown: method={}'.format(method.__name__))
        self.test_fp.close()

    def test_add_and_double(self):
        from test.function import read_file
        
        read_file(self.test_csv)
        # assert ~~

外部環境に依存する機能はモックしよう!

例えばWeb APIを利用しているケースで、それがテスト対象のコードではない場合、自動テストでテストケース毎にキックするなんて困ったちゃんです。とはいえコードに含まれている以上自動テストに組み込まれるわけです。

 

Web APIサービス:responsesでrequestsをモック

RDB:バックエンドをSQLiteに切り替え

Redis:fakeredis

AWSサービス:moto

from .functions import call_function

class TestCallFunction:
    @responses.activate
    def test_call_function(self):
        responses.add(
            responses.POST
            , 'https://sample.com/path'
            , json={'item1': "テストデータとしての返り値"}
        )
        
        data = call_function()
        
        assert data['body'] == "レスポンス本文"
        
        assert len(responses.calls) == 1
        assert responses.calls[0].request.body == '{"body": "投稿の本文"}'

コメントを残す