Python標準ライブラリのユニットテストフレームワーク-unittest

Pythonで自動テストフレームワークを構築するには、ユースケースとテスト実行を整理する必要があります。ここでは、ブロガーはPythonの標準ライブラリであるunittestを推奨しています。

Unittestは、xUnitシリーズのフレームワークのメンバーです。xUnitの他のメンバーを知っている場合は、unittestを簡単に使用できるはずです。これらはすべて同じように機能します。

 

ユニットテストのコア動作原理

unittestの4つのコアコンセプトは、テストケース、テストスイート、テストランナー、テストフィクスチャです。

これらの4つの概念の意味を個別に説明しましょう。まず、unittestの静的クラス図を見てください(次のクラス図と説明はインターネットからのもので、元のリンクです)。

ユニットテストクラス図

  • TestCaseのインスタンスはテストケースです。テストケースとは何ですか?これは、テスト前の環境のセットアップ(setUp)、テストコードの実行(run)、およびテスト後の環境の復元(tearDown)を含む完全なテストプロセスです。ユニットテストの本質はここにあります。テストケースは完全なテストユニットです。このテストユニットを実行することにより、特定の問題を検証できます。

  • また、複数のテストケースが一緒に収集されます。これはTestSuiteであり、TestSuiteはTestSuiteをネストすることもできます。

  • TestLoaderは、TestCaseをTestSuiteにロードするために使用されます。さまざまな場所からTestCaseを検索し、それらのインスタンスを作成し、TestSuiteに追加して、TestSuiteインスタンスを返すいくつかのloadTestsFrom __()メソッドがあります。

  • TextTestRunnerはテストケースを実行するためのもので、run(test)はTestSuite / TestCaseのrun(result)メソッドを実行します。
    テストの結果は、実行されたテストケースの数、成功した数、失敗した数などの情報を含めて、TextTestResultインスタンスに保存されます。

  • テストケース環境の構築と破壊は固定具です。

クラスはテストケースであるunittest.TestCaseを継承しますが、でtest 始まるメソッドが複数ある場合、 そのような各メソッドは、ロード時にTestCaseインスタンスを生成します。たとえば、クラスには4つのtest_xxxメソッドがあります。最後に、スイートにロードされたときの4つのテストケース。

全体のプロセスはここで明確です:

TestCaseを記述し、TestLoaderによってTestCaseをTestSuiteにロードしてから、TextTestRunnerによってTestSuiteを実行します。実行の結果はTextTestResultに保存されます。コマンドラインまたはunittest.main()を使用して実行すると、mainはを呼び出します。 TextTestRunnerで実行して実行します。または、TextTestRunnerを介してユースケースを直接実行することもできます。説明は次のとおりです。ランナーを実行すると、デフォルトで実行結果がコンソールに出力されます。ファイルに出力して結果をファイルで表示するように設定できます(HTMLTestRunnerについて聞いたことがあるかもしれませんが、そうです。 、結果をに出力することができますHTMLでは、名前からわかるTextTestRunnerと同じ美しいレポートが生成されます。これについては後で説明します)。

 

unittestインスタンス

いくつかの例を通して、ユニットテストをよりよく理解しましょう。

まず、テストするいくつかのメソッドを準備しましょう。

mathfunc.py

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

def minus(a, b):
    return a-b

def multi(a, b):
    return a*b

def divide(a, b):
    return a/b

簡単な例

次に、これらのメソッドのテストを作成します。

test_mathfunc.py

# -*- coding: utf-8 -*-

import unittest
from mathfunc import *


class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""

    def test_add(self):
        """Test method add(a, b)"""
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        self.assertEqual(1, minus(3, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

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

結果:

.F..
======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/py/test_mathfunc.py", line 26, in test_divide
    self.assertEqual(2.5, divide(5, 2))
AssertionError: 2.5 != 2

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)

合計4つのテストが実行され、1つが失敗し、失敗の理由が示され2.5 != 2 ていることがわかります。これは、除算方法に問題があること意味します。

これは簡単なテストです。説明が必要な点がいくつかあります。

  1. 最初の行には、各ユースケース実行の結果の識別が示され、成功は .、失敗は F、エラーは E、スキップは Sです。上記から、テストの実行はメソッドの順序とは関係がないこともわかります。Test_divideは4番目に記述されていますが、2番目に実行されます。

  2. 各テストメソッド test はで始まります。そうでない場合、unittestによって認識されません。

  3. unittest.main()にverbosity パラメーターを追加 すると、エラーレポート出力の詳細レベルを制御できます。デフォルトでは 1、設定され 0ている場合、各ユースケースの実行結果は出力されません。つまり、上記の1行目はありません。結果;設定されている場合 2、詳細な実行結果は次のように出力されます。

test_add (__main__.TestMathFunc)
Test method add(a, b) ... ok
test_divide (__main__.TestMathFunc)
Test method divide(a, b) ... FAIL
test_minus (__main__.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (__main__.TestMathFunc)
Test method multi(a, b) ... ok

======================================================================
FAIL: test_divide (__main__.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/py/test_mathfunc.py", line 26, in test_divide
    self.assertEqual(2.5, divide(5, 2))
AssertionError: 2.5 != 2

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=1)

各ユースケースの詳細な実行状況、ユースケースの名前、ユースケースの説明が出力されていることがわかります(テストメソッドのコード例に "" "Doc String" "を追加し、ユースケースの実行時に文字が使用されます。このユースケースの説明として、適切なコメントを追加すると、出力テストレポートが読みやすくなります

 

TestSuiteを整理する

上記のコード例は簡単なテストの書き方を示していますが、2つの問題があります。ユースケースの実行順序をどのように制御するのでしょうか。(ここでの例のテストメソッドは必ずしも関連しているわけではありませんが、後で作成するユースケースが優先される場合があります。最初にメソッドAを実行してから、メソッドBを実行する必要があります)、TestSuiteを使用します。TestSuite追加するケースは、追加された順序で実行されます。

2番目の質問は、現在テストファイルが1つしかないため、ファイルを直接実行できるということですが、テストファイルが複数ある場合、それらを整理する方法、1つずつ実行することはできません。答えは、TestSuiteにもあります。

次に例を示します。

このフォルダーに、新しいファイルtest_suite.pyを作成します

# -*- coding: utf-8 -*-

import unittest
from test_mathfunc import TestMathFunc

if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus"), TestMathFunc("test_divide")]
    suite.addTests(tests)

    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

結果:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... FAIL

======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\py\test_mathfunc.py", line 26, in test_divide
    self.assertEqual(2.5, divide(5, 2))
AssertionError: 2.5 != 2

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

ご覧のとおり、実行は予想と同じです。3つのケースが実行され、スイートに追加した順序で順序が実行されました。

TestSuiteaddTests() メソッドは上記使用され 、TestCaseリストは直接渡されます。次のこともできます。

# 直接用addTest方法添加单个TestCase
suite.addTest(TestMathFunc("test_multi"))

# 用addTests + TestLoader
# loadTestsFromName(),传入'模块名.TestCase名'
suite.addTests(unittest.TestLoader().loadTestsFromName('test_mathfunc.TestMathFunc'))
suite.addTests(unittest.TestLoader().loadTestsFromNames(['test_mathfunc.TestMathFunc']))  # loadTestsFromNames(),类似,传入列表

# loadTestsFromTestCase(),传入TestCase
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

TestLoaderメソッドを使用してケースを並べ替えることはできません。同時に、スイートをスイートに設定することもできます。

 

結果をファイルに出力します

ユースケースは整理されていますが、結果はコンソールにしか出力できないため、以前の実行レコードを表示する方法はありません。結果をファイルに出力する必要があります。非常に単純です。例を見てください。

test_suite.pyを変更します

# -*- coding: utf-8 -*-

import unittest
from test_mathfunc import TestMathFunc

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

    with open('UnittestTextReport.txt', 'a') as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(suite)

このファイルを実行すると、UnittestTextReport.txtが同じディレクトリに生成され、すべての実行レポートがこのファイルに出力されていることがわかります。これで、txt形式のテストレポートが作成されました。

 

テストフィクスチャ之setUp()tearDown()

上記のテスト全体は基本的に実行されましたが、いくつかの特別な状況が発生する可能性があります。テストが各実行前に環境を準備する必要がある場合、または各実行後にクリーンアップする必要がある場合はどうなりますか?たとえば、実行前にデータベースに接続し、実行後にデータを復元して切断する必要があります。すべてのテストメソッドで環境を準備およびクリーンアップするためのコードを追加することはできません。

これには、前述のテストフィクスチャが含まれます。test_mathfunc.pyを変更します

# -*- coding: utf-8 -*-

import unittest
from mathfunc import *


class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""

    def setUp(self):
        print "do something before test.Prepare environment."

    def tearDown(self):
        print "do something after test.Clean up."

    def test_add(self):
        """Test method add(a, b)"""
        print "add"
        self.assertEqual(3, add(1, 2))
        self.assertNotEqual(3, add(2, 2))

    def test_minus(self):
        """Test method minus(a, b)"""
        print "minus"
        self.assertEqual(1, minus(3, 2))

    def test_multi(self):
        """Test method multi(a, b)"""
        print "multi"
        self.assertEqual(6, multi(2, 3))

    def test_divide(self):
        """Test method divide(a, b)"""
        print "divide"
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

我々は、添加 setUp() と tearDown() 二つの方法は、(実際には、テストケースのこれらの2つの方法が書き換えられた)。これらの2つの方法は、前に一度、各テストメソッドの実行後に実行される。セットアップは、テストのための環境を準備するために使用され、及びティアダウンをするために使用されクリーンアップ環境は将来のテストの準備ができています。

もう一度実行してみましょう:

test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... FAIL
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (test_mathfunc.TestMathFunc)
Test method multi(a, b) ... ok

======================================================================
FAIL: test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\py\test_mathfunc.py", line 36, in test_divide
    self.assertEqual(2.5, divide(5, 2))
AssertionError: 2.5 != 2

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=1)
do something before test.Prepare environment.
add
do something after test.Clean up.
do something before test.Prepare environment.
divide
do something after test.Clean up.
do something before test.Prepare environment.
minus
do something after test.Clean up.
do something before test.Prepare environment.
multi
do something after test.Clean up.
  • setUpとtearDownは、それぞれのケースが実行される前後に1回実行されることがわかります。

あなたが任意の実行前に、環境のケースを用意し、すべてのケースの実装後に環境をクリーンアップしたい場合は、我々は使用することができます setUpClass() と tearDownClass()

...

class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""

    @classmethod
    def setUpClass(cls):
        print "This setUpClass() method only called once."

    @classmethod
    def tearDownClass(cls):
        print "This tearDownClass() method only called once too."

...

実行結果は以下のとおりです。

...
This setUpClass() method only called once.
do something before test.Prepare environment.
add
do something after test.Clean up.
...
do something before test.Prepare environment.
multi
do something after test.Clean up.
This tearDownClass() method only called once too.

setUpClassとtearDownClassの両方が1回だけ実行されていることがわかります。

ケースをスキップする

特定のケースを一時的にスキップして実行したくない場合はどうなりますか?Unittestは、いくつかの方法も提供します。

  1. デコレータをスキップする
...

class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""

    ...

    @unittest.skip("I don't want to run this case.")
    def test_divide(self):
        """Test method divide(a, b)"""
        print "divide"
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

実施した:

...
test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... skipped "I don't want to run this case."
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (test_mathfunc.TestMathFunc)
Test method multi(a, b) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=1)

テストの総数はまだ4であることがわかりますが、divide()メソッドはスキップされています。

デコレータ3の合計を飛ばし unittest.skip(reason)、、 unittest.skipIf(condition, reason)unittest.skipUnless(condition, reason)無条件スキップスキップ、SKIPIFは、条件が真であるとき、条件が偽のとき、skipUnlessはスキップスキップ。

  1. TestCase.skipTest()メソッド
...

class TestMathFunc(unittest.TestCase):
    """Test mathfuc.py"""

    ...

    def test_divide(self):
        """Test method divide(a, b)"""
        self.skipTest('Do not run this.')
        print "divide"
        self.assertEqual(2, divide(6, 3))
        self.assertEqual(2.5, divide(5, 2))

出力:

...
test_add (test_mathfunc.TestMathFunc)
Test method add(a, b) ... ok
test_divide (test_mathfunc.TestMathFunc)
Test method divide(a, b) ... skipped 'Do not run this.'
test_minus (test_mathfunc.TestMathFunc)
Test method minus(a, b) ... ok
test_multi (test_mathfunc.TestMathFunc)
Test method multi(a, b) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK (skipped=1)

効果は上記のデコレータと同じですが、除算方法をスキップします。

 

高度-HTMLTestRunnerを使用して美しいHTMLレポートを出力します

テキスト実行レポートをtxt形式で出力できますが、テキストレポートが単純すぎるため、より背の高いHTMLレポートが必要ですか?ただし、unittest自体にはHTMLレポートがないため、外部ライブラリにしか頼ることができません。

HTMLTestRunnerは、サードパーティの単体テストHTMLレポートライブラリです。まず、HTMLTestRunner.pyをダウンロードして現在のディレクトリ、または「C:\ Python27 \ Lib」の下に配置してから、インポートして実行できます。

ダウンロードリンク:

公式のオリジナルバージョン:http//tungwaiyip.info/software/HTMLTestRunner.html

グレーとブルーの変更バージョン:HTMLTestRunner.py(フォーマットが調整されました、中国語の表示)

私たちを変更します test_suite.py

# -*- coding: utf-8 -*-

import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))

    with open('HTMLReport.html', 'w') as f:
        runner = HTMLTestRunner(stream=f,
                                title='MathFunc Test Report',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                )
        runner.run(suite)

このようにして、実行中に、次のようにコンソールで実行を確認できます。

ok test_add (test_mathfunc.TestMathFunc)
F  test_divide (test_mathfunc.TestMathFunc)
ok test_minus (test_mathfunc.TestMathFunc)
ok test_multi (test_mathfunc.TestMathFunc)

Time Elapsed: 0:00:00.001000

そしてHTMLReport.html、図に示すように、HTMLテストレポートを出力します

htmlレポート

今では美しいHTMLレポートもあります。実際、HTMLTestRunnerの実行方法はTextTestRunnerと非常に似ていることがわかります。クラス図のランナーをHTMLTestRunnerに置き換え、TestResultをHTMLで表示する上記の例と比較できます。十分に深く、より複雑で美しいレポートを生成するために独自のランナーを作成できます。

 

要約すると

  1. Unittestは、Pythonに付属する単体テストフレームワークであり、自動テストフレームワークのユースケース編成および実行フレームワークとして使用できます。
  2. unittestのプロセス:TestCaseを記述し、TestLoaderでTestCaseをTestSuiteにロードし、TextTestRunnerでTestSuiteを実行します。操作の結果はTextTestResultに保存されます。コマンドラインまたはunittest.main()を使用して実行すると、mainはTextTestRunnerを呼び出します。実行して実行するか、TextTestRunnerを介して直接ユースケースを実行できます。
  3. unittest.TestCaseを継承するクラスはTestCaseであり test 、それで始まるメソッドは、ロード時に実際のTestCaseとしてロードされます。
  4. 冗長性パラメータは、実行結果の出力を制御できます。これ0 は、単純なレポート、1 一般的なレポート、または2 詳細なレポートです。
  5. addTestおよびaddTestsを使用して、ケースまたはスイートをスイートに追加できます。また、TestLoaderのloadTestsFrom __()メソッドを使用できます。
  6. および 環境浄化と実行環境の実施形態の前、及び実施の形態に配置してもよいですsetUp()tearDown()setUpClass()tearDownClass()
  7. skip、skipIf、skipUnlessデコレータを使用してケースをスキップするか、TestCase.skipTestメソッドを使用できます。
  8. パラメータにストリームを追加すると、レポートをファイルに出力できます。TextTestRunnerを使用してtxtレポートを出力でき、HTMLTestRunnerを使用してhtmlレポートを出力できます。

おすすめ

転載: blog.csdn.net/smilejiasmile/article/details/109445830