VS CodeでPythonの単体テストをしてみよう


はじめに

Visual Studio Codeにて、Python単体テストを試します。
参考にしたのは、公式のドキュメントです。

unittestとは

VS CodeのPython拡張にあらかじめ組み込まれているunittestを使用しました。

まずはunittestの基本的な考え方をおさらいします。

たとえば、引数aと引数bを加算する関数があったとします。

def add(a, b):
    return a+b

この関数に期待するのは、以下のような結果です。
(これは、関数の動作の正しさを保証するために、三通りのシナリオを用意したということですね)

  • add(1,3) → 4
  • add(0,0) → 0
  • add(-1,3) → 2

一つ目のシナリオについて、動作を確認するためのテストコードを書いてみます。
※実際にunittestで使用するコードではないので、ご注意を。

resualt = add(1,3)
if result == 4:
    print('OK')
else:
    print('NG')

今後、関数内部のコードを変更しても、このテストコードを走らせて正しい結果が出れば、関数が正しく動いていることを確認することができます。

そればかりでなく、さまざまなクラスやメソッドが複雑怪奇に絡み合い、コード変更の影響範囲がよくわからない場合でも、種々のテストコードにパスさえすれば、健全性が担保されます。問題が出たら出たで、大歓迎。考慮漏れを一つ潰して、コードの精度があがっていきます。

実際には、こうしたシナリオ(に基づいたテストコード)は、何十、何百と必要になってきますが、書くのも大変なら、実行するのも面倒くさいです。

そこで、テストコードを簡潔に書くことができ、ワンタッチで実行できれば、どんなにか便利なことでしょうか。

VS Codeでは、その仕組が提供されています。

準備

今回は、UNITTESTという名前のワークスペースを作りました。
テスト対象のファイルは、add.pyとし、コードを記入します。

add.py
def add(a, b):
    return a+b

テスト用のフォルダを作る

ワークスペースの中に、testsというフォルダを作りました。
テストコードは、この中に格納していくことになります。
VS Codeのエクスプローラーの「新しいフォルダ」ボタンを使ってもいいですし、普通にファインダー(エクスプローラー)を使っても構いません。

テストの設定をする

ここがちょっと独特の手順となりますが、コマンドパレットを使って、セットアップします。

command + shift + p (OSX)
ctrl + shift + p (windows)

でコマンドパレットを表示させ、

python test

等と入力します。
候補として、
Python:Configure Tests
が、現れますので、それを選択します。

表示が切り替わり、次はテストの種類の選択です。
pytestも候補で提案されていますが、今はunittestをお試ししたいので、Unittest Standard Python test frameworkを選択します。

今度はテスト用のフォルダの指定です。
先ほど作成した「tests」フォルダが選択肢にありますので、それを選びましょう。

最後に、テスト用ファイルの命名ルールを選択します。このルールに従った名前を持つファイルを見つけると、VS Codeはそれをテスト用ファイルとみなしてくれます。

ここでは、標準的であろう(たぶん)test_*.pyを選びました。

以上で、セットアップは完了です。
設定ファイルやフォルダが、自動的に作られています。

テストコードを書く

testsフォルダの直下に、test_add.pyというファイルを作成します。前段で、テスト用ファイルの命名ルールにtest_*.pyを選択しましたので、ファイル名はそれに従わなければなりません。

作成したファイルに、テストコードを書いていきます。

test_add.py
import unittest  # 標準モジュールを読み込みます
import add       # テスト対象のファイルを読み込みます

class TestA(unittest.TestCase):  # クラスを派生させて自分用のクラスを作ります
    def test_1(self):
        self.assertEqual(add.add(1, 2), 3)     # シナリオ1
        self.assertEqual(add.add(0, 0), 0)     # シナリオ2
        self.assertEqual(add.add(-1, -2), -3)  # シナリオ3

if __name__ == '__main__':
    unittest.main()

unittestの使い方としては、標準のunittest モジュールをインポートし、unittest.TestCase からテストクラスを派生させます。
まあ、僕は毎回、これをコピペです。
注目すべきは、

テスト対象のファイルをインポートしておくことと、

import add

あとは、テストクラス内で、テストのシナリオを書いていることですね。
実際にテストをするときは、これを書き書きしていくわけです。

self.assertEqual(add.add(1, 2), 3)
self.assertEqual(add.add(0, 0), 0)
self.assertEqual(add.add(-1, -2), -3)

self.assertEqual(A, B)というメソッドは、引数Aと引数Bの値が等しいことを検査するメソッドになります。

最後の記述は、テストファイル単体で実行するときに必要なものですので、今はなくてもいいですが、あっても困りません。

if __name__ == '__main__':
    unittest.main()

さあ、テストコードを書いたら(コピペしたら)、サイドバーにあるフラスコの形をした「テスト中」アイコン(変な名前だ)をクリックして、テスト中表示に切り替えてください。

設定した命名ルールに従ってテストファイルを作っていれば(かつテストコードに問題がなければ)、上図のように、作成したテストファイルやクラス、メソッドが表示されているはずです。

もし表示されていなければ、リロードボタンを押してください。

テストの実行

テスト中表示にすると、ファイルやクラス、メソッドのとなりに、右向きの白三角形アイコンが表示されています。

これをクリックすると、テストが実行されます。
メソッドごと、クラスごと、ファイルごとに実行できるので、便利ですね。

サイドバーに、

 「○個中○個のテストが成功しました(100%)」

と表示がでれば、成功です。

せっかくですので、テストに失敗するケースも見ておきましょうか。
三つ目のシナリオ、

self.assertEqual(add.add(-1, -2), -3)

は、-3という結果がでることを期待していますが、0を正解とするように修正しています。

self.assertEqual(add.add(-1, -2), 0)

この状態でテストを実行すると、「0じゃなくて、-3という結果が出たぞ」と警告してくれます。なかなか詳しいですね。

ちょっと手を加えて、assertNotEqual()(等しくない検査)にすれば、テストにパスします。

self.assertNotEqual(add.add(-1, -2), 0)

以上で、unittestの設定は完了です!

この先

必要最小限なことにしぼって、説明と設定を行いましたが、もちろん他にも様々な機能が準備されています。

・数々のアサートメソッド
作例では、値が等しいことを検査するself.assertEqualというメソッドを使いましたが、検査用メソッドは、他にもいろいろあります。

下記のアサートメソッド一覧は、参照する頻度が高いものでしょう。

アサートメソッド一覧(Python公式)

・setUp()、tearDown()
テストの実行に先立って、あらかじめ実行しておきたい処理があることがあります。
例えばCSVファイルを開いたり、DBにアクセスしたり、WEBサイトの中身を取得したり。

そんなときには、setupメソッドを使います。事後処理に使うのは、tearDownメソッドです。

セットアップ系メソッド

・例外処理
例外処理を絡めたテストをしたいという需要もあるかと思います。
assertRaisesなどのメソッドを使用します。

アサート系メソッド

以上です。