單元測試框架 - 快速指南



單元測試框架 - 概述

單元測試是一種軟體測試方法,透過它可以測試原始碼的各個單元(例如函式、方法和類),以確定它們是否適合使用。直觀地說,可以將單元視為應用程式中最小的可測試部分。單元測試是由程式設計師在開發過程中建立的簡短程式碼片段。它構成了元件測試的基礎。

單元測試可以透過以下兩種方式進行:

手動測試 自動化測試

在沒有任何工具支援的情況下手動執行測試用例稱為手動測試。

  • 由於測試用例是由人力資源執行的,因此非常耗時且繁瑣

  • 由於需要手動執行測試用例,因此手動測試需要更多測試人員。

  • 它不太可靠,因為由於人為錯誤,每次測試的精確度可能不一致。

  • 無法編寫獲取隱藏資訊的複雜測試程式。

利用工具支援並使用自動化工具執行測試用例稱為自動化測試。

  • 快速的自動化比人力資源快得多地執行測試用例。

  • 對人力資源的投資較少,因為測試用例是使用自動化工具執行的。

  • 自動化測試每次執行時執行完全相同的操作,並且更可靠

  • 測試人員可以編寫複雜的測試程式來獲取隱藏資訊。

JUnit 是 Java 程式語言的單元測試框架。JUnit 在測試驅動開發的發展中非常重要,並且是 xUnit 系列單元測試框架中的一員,該系列源自 JUnit。您可以在此處找到JUnit 教程

Python 單元測試框架,有時也稱為“PyUnit”,是 Kent Beck 和 Erich Gamma 開發的 JUnit 的 Python 版本。從 Python 2.1 版本開始,PyUnit 就成為 Python 標準庫的一部分。

Python 單元測試框架支援測試自動化、共享測試的設定和關閉程式碼、將測試聚合到集合中以及測試與報告框架的獨立性。unittest 模組提供了一些類,這些類使得輕鬆支援這些測試集合的特性變得容易。

本教程是為初學者準備的,以幫助他們瞭解 Python 測試框架的基本功能。完成本教程後,您將掌握使用 Python 測試框架的中等水平的專業知識,您可以從這裡提升到更高的水平。

您應該具備使用 Python 語言進行軟體開發的合理專業知識。我們的Python 教程是學習 Python 的一個好地方。瞭解軟體測試的基礎知識也是可取的。

環境設定

編寫測試所需的類位於 'unittest' 模組中。如果您使用的是較舊版本的 Python(Python 2.1 之前的版本),則可以從http://pyunit.sourceforge.net/下載該模組。但是,unittest 模組現在是標準 Python 發行版的一部分;因此它不需要單獨安裝。

單元測試框架 - 框架

'unittest' 支援測試自動化、共享測試的設定和關閉程式碼、將測試聚合到集合中以及測試與報告框架的獨立性。

unittest 模組提供了一些類,這些類使得輕鬆支援這些測試集合的特性變得容易。

為了實現這一點,unittest 支援以下重要概念:

  • 測試夾具 - 這表示執行一個或多個測試所需的準備工作以及任何相關的清理操作。例如,這可能包括建立臨時或代理資料庫、目錄或啟動伺服器程序。

  • 測試用例 - 這是測試的最小單元。這檢查特定輸入集的特定響應。unittest 提供了一個基類TestCase,可用於建立新的測試用例。

  • 測試套件 - 這是一個測試用例、測試套件或兩者的集合。它用於聚合應一起執行的測試。測試套件由 TestSuite 類實現。

  • 測試執行器 - 這是一個協調測試執行並將結果提供給使用者的元件。執行器可以使用圖形介面、文字介面或返回特殊值來指示測試執行的結果。

建立單元測試

編寫簡單的單元測試涉及以下步驟:

步驟 1 - 在您的程式中匯入 unittest 模組。

步驟 2 - 定義要測試的函式。在下面的示例中,add() 函式將進行測試。

步驟 3 - 透過子類化 unittest.TestCase 建立測試用例。

步驟 4 - 將測試定義為類中的方法。方法名稱必須以 'test' 開頭。

步驟 5 - 每個測試都呼叫 TestCase 類的 assert 函式。有許多型別的斷言。下面的示例呼叫 assertEquals() 函式。

步驟 6 - assertEquals() 函式將 add() 函式的結果與 arg2 引數進行比較,如果比較失敗則丟擲 AssertionError 異常。

步驟 7 - 最後,呼叫 unittest 模組的 main() 方法。

import unittest
def add(x,y):
   return x + y
   
class SimpleTest(unittest.TestCase):
   def testadd1(self):
      self.assertEquals(add(4,5),9)
      
if __name__ == '__main__':
   unittest.main()

步驟 8 - 從命令列執行上述指令碼。

C:\Python27>python SimpleTest.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK

步驟 9 - 測試可能產生的三種結果:

序號 訊息和描述
1

OK

測試透過。“A”顯示在控制檯上。

2

FAIL

測試未透過,並引發 AssertionError 異常。“F”顯示在控制檯上。

3

ERROR

測試引發了 AssertionError 以外的異常。“E”顯示在控制檯上。

這些結果分別以“.”、“F”和“E”顯示在控制檯上。

命令列介面

unittest 模組可用於從命令列執行單個或多個測試。

python -m unittest test1
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method

unittest 支援以下命令列選項。有關所有命令列選項的列表,請使用以下命令:

Python –m unittest -h

序號 選項和描述
1

-h, --help

顯示此訊息

2

v, --verbose

詳細輸出

3

-q, --quiet

最小輸出

4

-f, --failfast

在第一次失敗時停止

5

-c, --catch

捕獲 Control-C 並顯示結果

6

-b, --buffer

在測試執行期間緩衝標準輸出和標準錯誤

單元測試框架 - API

本章討論 unittest 模組中定義的類和方法。此模組中有五個主要類。

TestCase 類

此類的物件表示最小的可測試單元。它包含測試例程,並提供用於準備每個例程和隨後清理的掛鉤。

TestCase 類中定義了以下方法:

序號 方法和描述
1

setUp()

呼叫此方法以準備測試夾具。這在呼叫測試方法之前立即呼叫

2

tearDown()

在呼叫測試方法並記錄結果後立即呼叫此方法。即使測試方法引發異常,也會呼叫此方法。

3

setUpClass()

在執行單個類中的測試之前呼叫的類方法。

4

tearDownClass()

在執行單個類中的所有測試後呼叫的類方法。

5

run(result = None)

執行測試,並將結果收集到作為result傳遞的測試結果物件中。

6

skipTest(reason)

在測試方法或 setUp() 中呼叫此方法將跳過當前測試。

7

debug()

執行測試但不收集結果。

8

shortDescription()

返回測試的一行描述。

夾具

可以在 TestCase 類中編寫許多測試。這些測試方法可能需要初始化資料庫連線、臨時檔案或其他資源。這些稱為夾具。TestCase 包含一個特殊的鉤子來配置和清理測試所需的任何夾具。要配置夾具,請覆蓋 setUp()。要進行清理,請覆蓋 tearDown()。

在下面的示例中,在 TestCase 類中編寫了兩個測試。它們測試兩個值的加法和減法的結果。setup() 方法根據每個測試的 shortDescription() 初始化引數。teardown() 方法將在每個測試結束時執行。

import unittest

class simpleTest2(unittest.TestCase):
   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      if name == "Add":
         self.a = 10
         self.b = 20
         print name, self.a, self.b
      if name == "sub":
         self.a = 50
         self.b = 60
         print name, self.a, self.b
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertTrue(result == 100)
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)
      
if __name__ == '__main__':
   unittest.main()

從命令列執行上述程式碼。它給出以下輸出:

C:\Python27>python test2.py
Add 10 20
F
end of test Add
sub 50 60
end of test sub
.
================================================================
FAIL: testadd (__main__.simpleTest2)
Add
----------------------------------------------------------------------
Traceback (most recent call last):
   File "test2.py", line 21, in testadd
      self.assertTrue(result == 100)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.015s

FAILED (failures = 1)

類夾具

TestCase 類有一個 setUpClass() 方法,可以覆蓋它以在 TestCase 類中單個測試的執行之前執行。同樣,tearDownClass() 方法將在類中的所有測試之後執行。這兩個方法都是類方法。因此,它們必須用 @classmethod 指令進行裝飾。

以下示例演示了這些類方法的使用:

import unittest

class TestFixtures(unittest.TestCase):

   @classmethod
   def setUpClass(cls):
      print 'called once before any tests in class'

   @classmethod
   def tearDownClass(cls):
      print '\ncalled once after all tests in class'

   def setUp(self):
      self.a = 10
      self.b = 20
      name = self.shortDescription()
      print '\n',name
   def tearDown(self):
      print '\nend of test',self.shortDescription()

   def test1(self):
      """One"""
      result = self.a+self.b
      self.assertTrue(True)
   def test2(self):
      """Two"""
      result = self.a-self.b
      self.assertTrue(False)
      
if __name__ == '__main__':
unittest.main()

TestSuite 類

Python 的測試框架提供了一種有用的機制,可以透過它根據測試用例例項測試的功能將它們組合在一起。unittest 模組中的 TestSuite 類提供了這種機制。

建立和執行測試套件涉及以下步驟:

步驟 1 - 建立 TestSuite 類的例項。

suite = unittest.TestSuite()

步驟 2 - 將 TestCase 類中的測試新增到套件中。

suite.addTest(testcase class)

步驟 3 - 您還可以使用 makeSuite() 方法從類中新增測試

suite = unittest.makeSuite(test case class)

步驟 4 - 單個測試也可以新增到套件中。

suite.addTest(testcaseclass(""testmethod")

步驟 5 - 建立 TestTestRunner 類的物件。

runner = unittest.TextTestRunner()

步驟 6 - 呼叫 run() 方法以執行套件中的所有測試

runner.run (suite)

TestSuite 類中定義了以下方法:

序號 方法和描述
1

addTest()

在測試套件中新增測試方法。

2

addTests()

從多個 TestCase 類新增測試。

3

run()

執行與此套件關聯的測試,並將結果收集到測試結果物件中

4

debug()

執行與此套件關聯的測試,但不收集結果。

5

countTestCases()

返回此測試物件所代表的測試數量。

以下示例演示如何使用 TestSuite 類:

import unittest
class suiteTest(unittest.TestCase):
   def setUp(self):
      self.a = 10
      self.b = 20
      
   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertTrue(result == 100)
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)
      
def suite():
   suite = unittest.TestSuite()
##   suite.addTest (simpleTest3("testadd"))
##   suite.addTest (simpleTest3("testsub"))
   suite.addTest(unittest.makeSuite(simpleTest3))
   return suite
   
if __name__ == '__main__':
   runner = unittest.TextTestRunner()
   test_suite = suite()
   runner.run (test_suite)

您可以透過取消註釋包含 makeSuite() 方法的行和註釋語句來試驗 addTest() 方法。

TestLoader 類

unittest 包包含 TestLoader 類,用於從類和模組建立測試套件。預設情況下,當呼叫 unittest.main() 方法時,會自動建立 unittest.defaultTestLoader 例項。顯式例項允許自定義某些屬性。

在以下程式碼中,使用 TestLoader 物件將來自兩個類的測試收集到列表中。

import unittest
testList = [Test1, Test2]
testLoad = unittest.TestLoader()

TestList = []
for testCase in testList:
   testSuite = testLoad.loadTestsFromTestCase(testCase)
   TestList.append(testSuite)
   
newSuite = unittest.TestSuite(TestList)
runner = unittest.TextTestRunner()
runner.run(newSuite)

下表顯示 TestLoader 類中方法的列表:

序號 方法和描述
1

loadTestsFromTestCase()

返回 TestCase 類中包含的所有測試用例的套件。

2

loadTestsFromModule()

返回給定模組中包含的所有測試用例的套件。

3

loadTestsFromName()

返回給定字串指定符的所有測試用例的套件。

4

discover()

從指定的起始目錄遞迴進入子目錄,查詢所有測試模組,並返回一個 TestSuite 物件。

TestResult 類

此類用於編譯有關已成功測試和已失敗測試的資訊。TestResult 物件儲存一組測試的結果。TestResult 例項由 TestRunner.run() 方法返回。

TestResult 例項具有以下屬性:

序號 屬性與描述
1

Errors

一個列表,包含 TestCase 例項和包含格式化回溯的字串的 2 元組。每個元組表示引發意外異常的測試。

2

Failures

一個列表,包含 TestCase 例項和包含格式化回溯的字串的 2 元組。每個元組表示使用 TestCase.assert*() 方法明確發出失敗訊號的測試。

3

Skipped

一個列表,包含 TestCase 例項和包含跳過測試原因的字串的 2 元組。

4

wasSuccessful()

如果迄今為止執行的所有測試都已透過,則返回 True;否則返回 False。

5

stop()

可以呼叫此方法來發出應中止正在執行的測試集的訊號。

6

startTestRun()

在執行任何測試之前呼叫一次。

7

stopTestRun()

在執行所有測試後呼叫一次。

8

testsRun

迄今為止執行的測試總數。

9

Buffer

如果設定為 true,則sys.stdoutsys.stderr 將在呼叫 startTest() 和 stopTest() 之間進行緩衝。

以下程式碼執行測試套件:

if __name__ == '__main__':
   runner = unittest.TextTestRunner()
   test_suite = suite()
   result = runner.run (test_suite)
   
   print "---- START OF TEST RESULTS"
   print result

   print "result::errors"
   print result.errors

   print "result::failures"
   print result.failures

   print "result::skipped"
   print result.skipped

   print "result::successful"
   print result.wasSuccessful()
   
   print "result::test-run"
   print result.testsRun
   print "---- END OF TEST RESULTS"

執行程式碼時顯示以下輸出:

---- START OF TEST RESULTS
<unittest.runner.TextTestResult run = 2 errors = 0 failures = 1>
result::errors
[]
result::failures
[(<__main__.suiteTest testMethod = testadd>, 'Traceback (most recent call last):\n
   File "test3.py", line 10, in testadd\n 
   self.assertTrue(result == 100)\nAssert
   ionError: False is not true\n')]
result::skipped
[]
result::successful
False
result::test-run
2
---- END OF TEST RESULTS

單元測試框架 - 斷言

Python 測試框架使用 Python 的內建 assert() 函式來測試特定條件。如果斷言失敗,則會引發 AssertionError。然後,測試框架會將測試識別為失敗。其他異常被視為錯誤。

unittest 模組中定義了以下三組斷言函式:

  • 基本布林斷言
  • 比較斷言
  • 集合斷言

基本斷言函式評估操作結果是 True 還是 False。所有 assert 方法都接受一個msg 引數,如果指定了該引數,則在失敗時將其用作錯誤訊息。

序號 方法和描述
1

assertEqual(arg1, arg2, msg = None)

測試arg1arg2 是否相等。如果值不相等,則測試將失敗。

2

assertNotEqual(arg1, arg2, msg = None)

測試arg1arg2 是否不相等。如果值相等,則測試將失敗。

3

assertTrue(expr, msg = None)

測試expr 是否為真。如果為假,則測試失敗。

4

assertFalse(expr, msg = None)

測試expr 是否為假。如果為真,則測試失敗。

5

assertIs(arg1, arg2, msg = None)

測試arg1arg2 是否評估為相同的物件。

6

assertIsNot(arg1, arg2, msg = None)

測試arg1arg2 是否不評估為相同的物件。

7

assertIsNone(expr, msg = None)

測試expr 是否為 None。如果不是 None,則測試失敗。

8

assertIsNotNone(expr, msg = None)

測試expr 是否不為 None。如果為 None,則測試失敗。

9

assertIn(arg1, arg2, msg = None)

測試arg1 是否在 arg2 中。

10

assertNotIn(arg1, arg2, msg = None)

測試arg1 是否不在 arg2 中。

11

assertIsInstance(obj, cls, msg = None)

測試obj 是否是 cls 的例項。

12

assertNotIsInstance(obj, cls, msg = None)

測試obj 是否不是 cls 的例項。

以下程式碼實現了上述一些斷言函式:

import unittest

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertEqual(4 + 5,9)
   def test2(self):
      self.assertNotEqual(5 * 2,10)
   def test3(self):
      self.assertTrue(4 + 5 == 9,"The result is False")
   def test4(self):
      self.assertTrue(4 + 5 == 10,"assertion fails")
   def test5(self):
      self.assertIn(3,[1,2,3])
   def test6(self):
      self.assertNotIn(3, range(5))

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

執行上述指令碼時,test2、test4 和 test6 將顯示失敗,其他測試將成功執行。

FAIL: test2 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 9, in test2
      self.assertNotEqual(5*2,10)
AssertionError: 10 == 10

FAIL: test4 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 13, in test4
      self.assertTrue(4+5==10,"assertion fails")
AssertionError: assertion fails

FAIL: test6 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python27\SimpleTest.py", line 17, in test6
      self.assertNotIn(3, range(5))
AssertionError: 3 unexpectedly found in [0, 1, 2, 3, 4]

----------------------------------------------------------------------            
Ran 6 tests in 0.001s                                                             
                                                                                  
FAILED (failures = 3)

第二組斷言函式是比較斷言:

  • assertAlmostEqual (first, second, places = 7, msg = None, delta = None)

    透過計算差值、四捨五入到給定的十進位制位數places(預設為 7)並與零進行比較來測試firstsecond 是否近似(或不近似)相等。

  • assertNotAlmostEqual (first, second, places, msg, delta)

    透過計算差值、四捨五入到給定的十進位制位數(預設為 7)並與零進行比較來測試 first 和 second 是否不近似相等。

    在上述兩個函式中,如果提供 delta 而不是 places,則 first 和 second 之間的差值必須小於或等於(或大於)delta。

    同時提供 delta 和 places 將引發 TypeError。

  • assertGreater (first, second, msg = None)

    根據方法名稱測試first 是否大於second。如果不是,則測試將失敗。

  • assertGreaterEqual (first, second, msg = None)

    根據方法名稱測試first 是否大於或等於second。如果不是,則測試將失敗。

  • assertLess (first, second, msg = None)

    根據方法名稱測試first 是否小於second。如果不是,則測試將失敗。

  • assertLessEqual (first, second, msg = None)

    根據方法名稱測試first 是否小於或等於second。如果不是,則測試將失敗。

  • assertRegexpMatches (text, regexp, msg = None)

    測試正則表示式搜尋是否與文字匹配。如果失敗,錯誤訊息將包含模式和文字。regexp 可以是正則表示式物件,也可以是包含適合re.search() 使用的正則表示式的字串。

  • assertNotRegexpMatches (text, regexp, msg = None)

    驗證regexp 搜尋是否不匹配text。如果失敗,則會顯示一條錯誤訊息,其中包含模式和與text 匹配的部分。regexp 可以是正則表示式物件,也可以是包含適合re.search() 使用的正則表示式的字串。

以下示例實現了斷言函式:

import unittest
import math
import re

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertAlmostEqual(22.0/7,3.14)
   def test2(self):
      self.assertNotAlmostEqual(10.0/3,3)
   def test3(self):
      self.assertGreater(math.pi,3)
   def test4(self):
      self.assertNotRegexpMatches("Tutorials Point (I) Private Limited","Point")

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

上述指令碼將 test1 和 test4 報告為失敗。在 test1 中,22/7 的除法結果在 7 位小數內不等於 3.14。類似地,由於第二個引數與第一個引數中的文字匹配,因此 test4 導致 AssertionError。

=====================================================FAIL: test1 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 7, in test1
      self.assertAlmostEqual(22.0/7,3.14)
AssertionError: 3.142857142857143 != 3.14 within 7 places
================================================================
FAIL: test4 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 13, in test4
      self.assertNotRegexpMatches("Tutorials Point (I) Private Limited","Point")
AssertionError: Regexp matched: 'Point' matches 'Point' in 'Tutorials Point (I)
Private Limited'
----------------------------------------------------------------------

Ran 4 tests in 0.001s                                                             
                                                                                  
FAILED (failures = 2)

集合斷言

這組斷言函式旨在與 Python 中的集合資料型別一起使用,例如列表、元組、字典和集合。

序號 方法和描述
1

assertListEqual (list1, list2, msg = None)

測試兩個列表是否相等。如果不相等,則會構造一個錯誤訊息,該訊息只顯示兩個列表之間的差異。

2

assertTupleEqual (tuple1, tuple2, msg = None)

測試兩個元組是否相等。如果不相等,則會構造一個錯誤訊息,該訊息只顯示兩個元組之間的差異。

3

assertSetEqual (set1, set2, msg = None)

測試兩個集合是否相等。如果不相等,則會構造一個錯誤訊息,該訊息列出集合之間的差異。

4

assertDictEqual (expected, actual, msg = None)

測試兩個字典是否相等。如果不相等,則會構造一個錯誤訊息,該訊息顯示字典中的差異。

以下示例實現了上述方法:

import unittest

class SimpleTest(unittest.TestCase):
   def test1(self):
      self.assertListEqual([2,3,4], [1,2,3,4,5])
   def test2(self):
      self.assertTupleEqual((1*2,2*2,3*2), (2,4,6))
   def test3(self):
      self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})

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

在上述示例中,test1 和 test3 顯示 AssertionError。錯誤訊息顯示列表和字典物件中的差異。

FAIL: test1 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 5, in test1
      self.assertListEqual([2,3,4], [1,2,3,4,5])
AssertionError: Lists differ: [2, 3, 4] != [1, 2, 3, 4, 5]

First differing element 0:
2
1

Second list contains 2 additional elements.
First extra element 3:
4

- [2, 3, 4]
+ [1, 2, 3, 4, 5]
? +++       +++

FAIL: test3 (__main__.SimpleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "asserttest.py", line 9, in test3
      self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})
AssertionError: {1: 11, 2: 22} != {1: 11, 2: 22, 3: 33}
- {1: 11, 2: 22}
+ {1: 11, 2: 22, 3: 33}
?              +++++++
                                                                                  
----------------------------------------------------------------------            
Ran 3 tests in 0.001s                                                             
                                                                                  
FAILED (failures = 2)

單元測試框架 - 測試發現

TestLoader 類具有 discover() 函式。Python 測試框架使用此函式進行簡單的測試發現。為了相容,包含測試的模組和包必須可以從頂級目錄匯入。

以下是測試發現的基本命令列用法:

Python –m unittest discover

直譯器嘗試載入當前目錄和內部目錄中遞迴包含測試的所有模組。其他命令列選項包括:

序號 選項與描述
1

-v, --verbose

詳細輸出

2

-s, --start-directory

directory 開始發現的目錄(預設為 .)

3

-p, --pattern

pattern 匹配測試檔案的模式(預設為 test*.py)

4

-t, --top-level-directory

directory 專案的頂級目錄(預設為起始目錄)

例如,為了發現名稱以 'assert' 開頭的模組中的測試,這些模組位於 'tests' 目錄中,可以使用以下命令列:

C:\python27>python –m unittest –v –s "c:\test" –p "assert*.py"

測試發現透過匯入測試來載入測試。一旦測試發現從您指定的起始目錄找到所有測試檔案,它就會將路徑轉換為要匯入的包名稱。

如果您提供起始目錄作為包名稱而不是目錄路徑,則 discover 假定它從中匯入的任何位置都是您想要的, 因此您不會收到警告。

單元測試框架 - 跳過測試

從 Python 2.7 開始添加了對跳過測試的支援。可以有條件地或無條件地跳過單個測試方法或 TestCase 類。框架允許將某個測試標記為“預期失敗”。此測試將“失敗”,但在 TestResult 中不會被計為失敗。

要無條件跳過方法,可以使用以下 unittest.skip() 類方法:

import unittest

   def add(x,y):
      return x+y

class SimpleTest(unittest.TestCase):
   @unittest.skip("demonstrating skipping")
   def testadd1(self):
      self.assertEquals(add(4,5),9)

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

由於 skip() 是類方法,因此它以 @ 符號為字首。該方法接受一個引數:一個日誌訊息,描述跳過的原因。

執行上述指令碼時,控制檯將顯示以下結果:

C:\Python27>python skiptest.py
s
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK (skipped = 1)

字元“s”表示已跳過測試。

跳過測試的另一種語法是在測試函式內部使用例項方法 skipTest()。

def testadd2(self):
   self.skipTest("another method for skipping")
   self.assertTrue(add(4 + 5) == 10)

以下裝飾器實現測試跳過和預期失敗:

序號 方法和描述
1

unittest.skip(reason)

無條件跳過被裝飾的測試。reason 應描述跳過測試的原因。

2

unittest.skipIf(condition, reason)

如果 condition 為真,則跳過被裝飾的測試。

3

unittest.skipUnless(condition, reason)

除非 condition 為真,否則跳過被裝飾的測試。

4

unittest.expectedFailure()

將測試標記為預期失敗。如果測試執行時失敗,則該測試不計為失敗。

以下示例演示了條件跳過和預期失敗的使用。

import unittest

class suiteTest(unittest.TestCase):
   a = 50
   b = 40
   
   def testadd(self):
      """Add"""
      result = self.a+self.b
      self.assertEqual(result,100)

   @unittest.skipIf(a>b, "Skip over this routine")
   def testsub(self):
      """sub"""
      result = self.a-self.b
      self.assertTrue(result == -10)
   
   @unittest.skipUnless(b == 0, "Skip over this routine")
   def testdiv(self):
      """div"""
      result = self.a/self.b
      self.assertTrue(result == 1)

   @unittest.expectedFailure
   def testmul(self):
      """mul"""
      result = self.a*self.b
      self.assertEqual(result == 0)

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

在上面的示例中,testsub() 和 testdiv() 將被跳過。在第一種情況下 a>b 為真,而在第二種情況下 b == 0 為假。另一方面,testmul() 已被標記為預期失敗。

執行上述指令碼時,兩個跳過的測試顯示為“s”,預期失敗顯示為“x”。

C:\Python27>python skiptest.py
Fsxs
================================================================
FAIL: testadd (__main__.suiteTest)
Add
----------------------------------------------------------------------
Traceback (most recent call last):
   File "skiptest.py", line 9, in testadd
      self.assertEqual(result,100)
AssertionError: 90 != 100

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

FAILED (failures = 1, skipped = 2, expected failures = 1)

單元測試框架 - 異常測試

Python 測試框架提供以下斷言方法來檢查是否引發了異常。

assertRaises(exception, callable, *args, **kwds)

測試當用任何位置或關鍵字引數呼叫函式時是否引發異常(第一個引數)。如果引發預期異常,則測試透過;如果引發其他異常,則為錯誤;如果沒有引發異常,則測試失敗。要捕獲任何一組異常,可以將包含異常類的元組作為 exception 傳遞。

在下面的示例中,定義了一個測試函式來檢查是否引發了 ZeroDivisionError。

import unittest

def div(a,b):
   return a/b
class raiseTest(unittest.TestCase):
   def testraise(self):
      self.assertRaises(ZeroDivisionError, div, 1,0)

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

testraise() 函式使用 assertRaises() 函式檢視當呼叫 div() 函式時是否發生除零錯誤。上述程式碼將引發異常。但是,對 div() 函式的引數進行如下更改:

self.assertRaises(ZeroDivisionError, div, 1,1)

使用這些更改執行程式碼時,測試失敗,因為不會發生 ZeroDivisionError。

F
================================================================
FAIL: testraise (__main__.raiseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "raisetest.py", line 7, in testraise
      self.assertRaises(ZeroDivisionError, div, 1,1)
AssertionError: ZeroDivisionError not raised

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures = 1)

assertRaisesRegexp(exception, regexp, callable, *args, **kwds)

測試regexp 是否與引發的異常的字串表示匹配。regexp 可以是正則表示式物件,也可以是包含適合 re.search() 使用的正則表示式的字串。

以下示例顯示了 assertRaisesRegexp() 的使用方法:

import unittest
import re

class raiseTest(unittest.TestCase):
   def testraiseRegex(self):
      self.assertRaisesRegexp(TypeError, "invalid", reg,"Point","TutorialsPoint")
      
if __name__ == '__main__':
   unittest.main()

這裡,testraseRegex() 測試不會失敗,因為第一個引數。“Point” 在第二個引數字串中找到。

================================================================
FAIL: testraiseRegex (__main__.raiseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:/Python27/raiseTest.py", line 11, in testraiseRegex
      self.assertRaisesRegexp(TypeError, "invalid", reg,"Point","TutorialsPoint")
AssertionError: TypeError not raised
----------------------------------------------------------------------

但是,更改如下所示:

self.assertRaisesRegexp(TypeError, "invalid", reg,123,"TutorialsPoint")

將丟擲 TypeError 異常。因此,將顯示以下結果:

================================================================
FAIL: testraiseRegex (__main__.raiseTest)
----------------------------------------------------------------------
Traceback (most recent call last):
   File "raisetest.py", line 11, in testraiseRegex
      self.assertRaisesRegexp(TypeError, "invalid", reg,123,"TutorialsPoint")
AssertionError: "invalid" does not match 
   "first argument must be string or compiled pattern"
----------------------------------------------------------------------

單元測試框架 - 時間測試

JUnit(Java 單元測試框架,Pyunit 是 JUnit 的實現)有一個方便的超時選項。如果測試花費的時間超過指定時間,它將被標記為失敗。

Python 的測試框架不包含任何超時支援。但是,一個名為 timeout-decorator 的第三方模組可以完成這項工作。

從以下地址下載並安裝模組:

https://pypi.python.org/packages/source/t/timeout-decorator/timeout-decorator-0.3.2.tar.gz

  • 在程式碼中匯入 timeout_decorator
  • 在測試之前放置 timeout 裝飾器
  • @timeout_decorator.timeout(10)

如果此行以下的測試方法花費的時間超過此處提到的超時時間(10 分鐘),則會引發 TimeOutError。例如:

import time
import timeout_decorator

class timeoutTest(unittest.TestCase):

   @timeout_decorator.timeout(5)
   def testtimeout(self):
      print "Start"
   for i in range(1,10):
      time.sleep(1)
      print "%d seconds have passed" % i
      
if __name__ == '__main__':
   unittest.main()

單元測試框架 - Unittest2

unittest2 是對 Python 2.7 及更高版本中新增到 Python 測試框架的其他功能的反向移植。經過測試可在 Python 2.6、2.7 和 3.* 上執行。最新版本可以從 https://pypi.python.org/pypi/unittest2 下載

要使用 unittest2 代替 unittest,只需將 import unittest 替換為 import unittest2。

unittest2 中的類派生自 unittest 中的相應類,因此應該可以立即使用 unittest2 測試執行基礎設施,而無需將所有測試切換到使用 unittest2。如果您打算實現新功能,請從 unittest2.TestCase 而不是 unittest.TestCase 派生您的測試用例

以下是 unittest2 的新功能:

  • addCleanups 用於更好的資源管理

  • 包含許多新的 assert 方法

  • assertRaises 作為上下文管理器,之後可以訪問異常

  • 具有模組級 fixture,例如 setUpModuletearDownModule

  • 包括用於從模組或包載入測試的 load_tests 協議

  • TestResult 上的 startTestRunstopTestRun 方法

在 Python 2.7 中,您可以使用 python -m unittest 呼叫 unittest 命令列功能(包括測試發現)。

相反,unittest2 帶有一個名為 unit2 的指令碼。

unit2 discover
unit2 -v test_module

單元測試框架 - 訊號處理

-c/--catch 命令列選項以及 catchbreak 引數為 unittest 提供了對測試執行期間 Control-C 的更有效處理。啟用 catch break 行為後,Control-C 將允許當前執行的測試完成,然後測試執行將結束並報告迄今為止的所有結果。第二次 Control-C 將以通常的方式引發 KeyboardInterrupt。

如果呼叫了 unittest 處理程式但未安裝 signal.SIGINT 處理程式,則它將呼叫預設處理程式。這通常是替換已安裝的處理程式並委託給它的程式碼的預期行為。對於需要停用 unittest Control-C 處理的單個測試,可以使用 removeHandler() 裝飾器。

以下實用程式函式啟用測試框架內的 Control-C 處理功能:

unittest.installHandler()

安裝 Control-C 處理程式。接收到 signal.SIGINT 時,所有已註冊的結果都會呼叫 TestResult.stop()。

unittest.registerResult(result)

註冊用於 Control-C 處理的 TestResult 物件。註冊結果會儲存對它的弱引用,因此它不會阻止結果被垃圾回收。

unittest.removeResult(result)

刪除已註冊的結果。刪除結果後,響應 Control-C 將不再在該結果物件上呼叫 TestResult.stop()。

unittest.removeHandler(function = None)

在不帶引數呼叫時,此函式將刪除已安裝的 Control-C 處理程式。此函式也可以用作測試裝飾器,以在測試執行期間暫時刪除處理程式。

GUI 測試執行器

unittest 模組安裝用於互動式地發現和執行測試。此實用程式(一個名為 'inittestgui.py' 的 Python 指令碼)使用 Tkinter 模組,這是一個 Python 埠,用於 TK 圖形工具包。它提供了一個易於使用的 GUI 用於發現和執行測試。

Python unittestgui.py
Running Test

單擊“發現測試”按鈕。將出現一個小的對話方塊,您可以在其中選擇要從中執行測試的目錄和模組。

Discover Test

最後,單擊開始按鈕。將從選定的路徑和模組名稱發現測試,結果窗格將顯示結果。

Result Pane

要檢視單個測試的詳細資訊,請選擇結果框中的測試並單擊它:

Individual Test Details

如果在 Python 安裝中找不到此實用程式,您可以從專案頁面 http://pyunit.sourceforge.net/ 獲取它。

類似地,基於 wxpython 工具包的實用程式也在那裡可用。

單元測試框架 - Doctest

Python 的標準發行版包含 'Doctest' 模組。此模組的功能使其能夠搜尋看起來像互動式 Python 會話的文字片段,並執行這些會話以檢視它們是否完全按顯示的那樣工作。

Doctest 在以下情況下非常有用:

  • 透過驗證所有互動式示例仍然按文件說明工作來檢查模組的文件字串是否是最新的。

  • 透過驗證測試檔案或測試物件的互動式示例是否按預期工作來執行迴歸測試。

  • 為包編寫教程文件,並大量使用輸入輸出示例進行說明

在 Python 中,“文件字串”是一個字串文字,它作為類、函式或模組中的第一個表示式出現。執行套件時會忽略它,但編譯器會識別它並將其放入封閉類、函式或模組的 __doc__ 屬性中。由於它可透過內省獲得,因此它是物件文件的規範位置。

通常的做法是在文件字串中放入 Python 程式碼不同部分的示例用法。doctest 模組允許驗證這些文件字串是否與程式碼中的間歇性修訂保持一致。

在以下程式碼中,定義了一個階乘函式,並穿插了示例用法。為了驗證示例用法是否正確,請呼叫 doctest 模組中的 testmod() 函式。

"""
This is the "example" module.

The example module supplies one function, factorial(). For example,

>>> factorial(5)
120
"""

def factorial(x):
   """Return the factorial of n, an exact integer >= 0.
   >>> factorial(-1)
   Traceback (most recent call last):
      ...
   ValueError: x must be >= 0
   """
   
   if not x >= 0:
      raise ValueError("x must be >= 0")
   f = 1
   for i in range(1,x+1):
      f = f*i
   return f
   
if __name__ == "__main__":
   import doctest
   doctest.testmod()

輸入並儲存上述指令碼為 FactDocTest.py,然後嘗試從命令列執行此指令碼。

Python FactDocTest.py

除非示例失敗,否則不會顯示任何輸出。現在,將命令列更改為以下內容:

Python FactDocTest.py –v

控制檯現在將顯示以下輸出:

C:\Python27>python FactDocTest.py -v
Trying:
   factorial(5)
Expecting:
   120
ok
Trying:
   factorial(-1)
Expecting:
   Traceback (most recent call last):
      ...
   ValueError: x must be >= 0
ok
2 items passed all tests:
   1 tests in __main__
   1 tests in __main__.factorial
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

另一方面,如果 factorial() 函式的程式碼沒有在文件字串中給出預期的結果,則將顯示失敗結果。例如,在上述指令碼中將 f = 2 更改為 f = 1 並再次執行 doctest。結果如下:

Trying:
   factorial(5)
Expecting:
   120
**********************************************************************
File "docfacttest.py", line 6, in __main__
Failed example:
factorial(5)
Expected:
   120
Got:
   240
Trying:
   factorial(-1)
Expecting:
   Traceback (most recent call last):
      ...
   ValueError: x must be >= 0
ok
1 items passed all tests:
   1 tests in __main__.factorial
**********************************************************************
1 items had failures:
   1 of 1 in __main__
2 tests in 2 items.
1 passed and 1 failed.
***Test Failed*** 1 failures.

Doctest:檢查文字檔案中的示例

doctest 的另一個簡單應用是在文字檔案中測試互動式示例。這可以使用 testfile() 函式完成。

以下文字儲存在名為 'example.txt' 的文字檔案中。

Using ''factorial''
-------------------
This is an example text file in reStructuredText format. First import
''factorial'' from the ''example'' module:
   >>> from example import factorial
Now use it:
   >>> factorial(5)
   120

檔案內容被視為文件字串。為了驗證文字檔案中的示例,請使用 doctest 模組的 testfile() 函式。

def factorial(x):
   if not x >= 0:
      raise ValueError("x must be >= 0")
   f = 1
   for i in range(1,x+1):
      f = f*i
   return f
   
if __name__ == "__main__":
   import doctest
   doctest.testfile("example.txt")
  • 與 testmod() 一樣,除非示例失敗,否則 testfile() 不會顯示任何內容。如果示例確實失敗,則使用與 testmod() 相同的格式將失敗的示例和失敗的原因列印到控制檯。

  • 在大多數情況下,互動式控制檯會話的複製和貼上效果很好,但 doctest 並不是試圖對任何特定的 Python shell 進行精確模擬。

  • 任何預期的輸出都必須緊跟包含程式碼的最後一個“>>>”或“... ”行之後,預期輸出(如果有)延伸到下一個“>>>”或全空格行。

  • 預期輸出不能包含全空格行,因為這樣的行被認為表示預期輸出的結束。如果預期輸出確實包含空行,請在您的 doctest 示例中每個預期空行的位置放置

單元測試框架 - Doctest API

doctest API 圍繞以下兩個用於儲存來自文件字串的互動式示例的容器類展開:

  • Example - 單個 Python 語句,與其預期輸出配對。

  • DocTest - 示例的集合,通常從單個文件字串或文字檔案提取。

定義以下附加處理類以查詢、解析和執行以及檢查 doctest 示例:

  • DocTestFinder - 在給定模組中查詢所有文件字串,並使用 DocTestParser 從包含互動式示例的每個文件字串建立 DocTest。

  • DocTestParser - 從字串(例如物件的文件字串)建立 doctest 物件。

  • DocTestRunner - 執行 doctest 中的示例,並使用 OutputChecker 驗證其輸出。

  • OutputChecker − 比較 doctest 示例的實際輸出與預期輸出,並判斷是否匹配。

DocTestFinder 類

這是一個處理類,用於從給定物件的文件字串及其包含物件的文件字串中提取與該物件相關的 doctest。目前可以從以下物件型別中提取 Doctests——模組、函式、類、方法、靜態方法、類方法和屬性。

此類定義了 find() 方法。它返回由物件的文件字串或其任何包含物件的文件字串定義的 DocTest 列表。

DocTestParser 類

這是一個處理類,用於從字串中提取互動式示例,並使用它們建立 DocTest 物件。此類定義了以下方法:

  • get_doctest() − 從給定字串中提取所有 doctest 示例,並將它們收集到一個DocTest 物件中。

  • get_examples(string[, name]) − 從給定字串中提取所有 doctest 示例,並將它們作為Example 物件列表返回。行號從 0 開始。可選引數 name 用於標識此字串,僅用於錯誤訊息。

  • parse(string[, name]) − 將給定字串劃分為示例和中間文字,並將它們作為Examples 和字串的交替列表返回。Examples 的行號從 0 開始。可選引數 name 用於標識此字串,僅用於錯誤訊息。

DocTestRunner 類

這是一個處理類,用於執行和驗證 DocTest 中的互動式示例。其中定義了以下方法:

report_start()

報告測試執行器即將處理給定示例。提供此方法是為了允許DocTestRunner 的子類自定義其輸出;不應直接呼叫此方法。

report_success()

報告給定示例已成功執行。提供此方法是為了允許 DocTestRunner 的子類自定義其輸出;不應直接呼叫此方法。

report_failure()

報告給定示例失敗。提供此方法是為了允許DocTestRunner 的子類自定義其輸出;不應直接呼叫此方法。

report_unexpected_exception()

報告給定示例引發了意外異常。提供此方法是為了允許 DocTestRunner 的子類自定義其輸出;不應直接呼叫此方法。

run(test)

執行test(一個 DocTest 物件)中的示例,並使用 writer 函式out顯示結果。

summarize([verbose])

列印此 DocTestRunner 已執行的所有測試用例的摘要,並返回一個命名元組 TestResults(failed, attempted)。可選的verbose 引數控制摘要的詳細程度。如果未指定詳細程度,則使用 DocTestRunner 的詳細程度。

OutputChecker 類

此類用於檢查 doctest 示例的實際輸出是否與預期輸出匹配。

在此類中定義了以下方法:

check_output()

如果示例的實際輸出(got)與預期輸出(want)匹配,則返回True。如果這些字串完全相同,則始終認為它們匹配;但根據測試執行器使用的選項標誌,幾種非精確匹配型別也是可能的。有關選項標誌的更多資訊,請參見選項標誌指令部分。

output_difference()

返回一個字串,描述給定示例(example)的預期輸出與實際輸出(got)之間的差異。

DocTest 與 Unittest 整合

doctest 模組提供兩個函式,可用於從包含 doctest 的模組和文字檔案建立 unittest 測試套件。要與 unittest 測試發現整合,請在測試模組中包含 load_tests() 函式:

import unittest
import doctest
import doctestexample

def load_tests(loader, tests, ignore):
   tests.addTests(doctest.DocTestSuite(doctestexample))
   return tests

將形成一個來自 unittest 和 doctest 的測試組合 TestSuite,現在可以使用 unittest 模組的 main() 方法或 run() 方法執行它。

以下是用於從包含 doctest 的文字檔案和模組建立unittest.TestSuite 例項的兩個主要函式:

doctest.DocFileSuite()

它用於將一個或多個文字檔案的 doctest 測試轉換為unittest.TestSuite。返回的 unittest.TestSuite 將由 unittest 框架執行,並執行每個檔案中的互動式示例。如果檔案中的任何示例失敗,則合成的單元測試失敗,並引發failureException 異常,顯示包含測試的檔名和(有時是近似的)行號。

doctest.DocTestSuite()

它用於將模組的 doctest 測試轉換為unittest.TestSuite

返回的 unittest.TestSuite 將由 unittest 框架執行,並執行模組中的每個 doctest。如果任何 doctest 失敗,則合成的單元測試失敗,並引發failureException 異常,顯示包含測試的檔名和(有時是近似的)行號。

在幕後,DocTestSuite() 使用 doctest.DocTestCase 例項建立一個unittest.TestSuite,而 DocTestCase 是 unittest.TestCase 的子類。

類似地,DocFileSuite() 使用 doctest.DocFileCase 例項建立一個 unittest.TestSuite,而 DocFileCase 是 DocTestCase 的子類。

因此,這兩種建立 unittest.TestSuite 的方法都執行 DocTestCase 的例項。當您自己執行 doctest 函式時,可以透過將選項標誌傳遞給 doctest 函式來直接控制正在使用的 doctest 選項。

但是,如果您正在編寫 unittest 框架,unittest 最終會控制測試何時以及如何執行。框架作者通常希望控制 doctest 報告選項(例如,由命令列選項指定),但是沒有辦法透過 unittest 將選項傳遞給 doctest 測試執行器。

單元測試框架 - Py.test 模組

2004 年,Holger Krekel 將他的std 包(其名稱經常與 Python 附帶的標準庫混淆)重新命名為(只有略微不那麼令人困惑的)名稱“py”。儘管該包包含多個子包,但現在幾乎完全以其 py.test 框架而聞名。

py.test 框架為 Python 測試設定了新的標準,並已成為當今許多開發人員非常流行的工具。它為測試編寫引入的優雅且符合 Python 風格的習慣用法使得能夠以更簡潔的風格編寫測試套件。

py.test 是 Python 標準 unittest 模組的一種無需樣板程式碼的替代方案。儘管它是一個功能齊全且可擴充套件的測試工具,但它擁有簡單的語法。建立測試套件就像編寫一個包含幾個函式的模組一樣簡單。

py.test 執行在所有 POSIX 作業系統和 WINDOWS(XP/7/8)上,Python 版本為 2.6 及更高版本。

安裝

使用以下程式碼在當前 Python 發行版中載入 pytest 模組以及 py.test.exe 實用程式。可以使用兩者執行測試。

pip install pytest

用法

您可以簡單地使用 assert 語句來斷言測試預期。pytest 的 assert 自省功能將智慧地報告 assert 表示式的中間值,使您無需學習JUnit 傳統方法的許多名稱。

# content of test_sample.py
def func(x):
   return x + 1
   
def test_answer():
   assert func(3) == 5

使用以下命令列執行上述測試。測試執行後,將在控制檯上顯示以下結果:

C:\Python27>scripts\py.test -v test_sample.py
============================= test session starts =====================
platform win32 -- Python 2.7.9, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- C:\Pyth
on27\python.exe
cachedir: .cache
rootdir: C:\Python27, inifile:
collected 1 items
test_sample.py::test_answer FAILED
================================== FAILURES =====================
_________________________________ test_answer _________________________________
   def test_answer():
>  assert func(3) == 5
E     assert 4 == 5
E     + where 4 = func(3)
test_sample.py:7: AssertionError
========================== 1 failed in 0.05 seconds ====================

也可以透過使用 –m 開關包含 pytest 模組來從命令列執行測試。

python -m pytest test_sample.py

在類中分組多個測試

當您開始擁有多個測試時,通常將測試邏輯地分組到類和模組中是有意義的。讓我們編寫一個包含兩個測試的類:

class TestClass:
   def test_one(self):
      x = "this"
      assert 'h' in x
   def test_two(self):
      x = "hello"
      assert hasattr(x, 'check')

將顯示以下測試結果:

C:\Python27>scripts\py.test -v test_class.py
============================= test session starts =====================
platform win32 -- Python 2.7.9, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- C:\Pyt
on27\python.exe
cachedir: .cache
rootdir: C:\Python27, inifile:
collected 2 items
test_class.py::TestClass::test_one PASSED
test_class.py::TestClass::test_two FAILED
================================== FAILURES =====================
_____________________________ TestClass.test_two ______________________________
self = <test_class.TestClass instance at 0x01309DA0>

   def test_two(self):
      x = "hello"
>  assert hasattr(x, 'check')
E     assert hasattr('hello', 'check')

test_class.py:7: AssertionError
===================== 1 failed, 1 passed in 0.06 seconds ======================

Nose 測試 - 框架

nose 專案於 2005 年釋出,也就是py.test 獲得現代外觀的次年。它由 Jason Pellerin 編寫,用於支援 py.test 開創的相同測試習慣用法,但在一個更易於安裝和維護的包中。

可以使用 pip 實用程式安裝nose 模組。

pip install nose

這將在當前 Python 發行版中安裝 nose 模組以及 nosetest.exe,這意味著可以使用此實用程式以及 –m 開關執行測試。

C:\python>nosetests –v test_sample.py
Or
C:\python>python –m nose test_sample.py

nose 當然會從unittest.TestCase 子類中收集測試。我們也可以編寫簡單的測試函式,以及不是 unittest.TestCase 子類的測試類。nose 還提供許多有用的函式,用於編寫計時測試、測試異常和其他常見用例。

nose 自動收集測試。無需手動將測試用例收集到測試套件中。測試執行響應迅速,因為nose 在載入第一個測試模組後立即開始執行測試。

與 unittest 模組一樣,nose 支援包、模組、類和測試用例級別的夾具,因此可以儘可能少地進行昂貴的初始化。

基本用法

讓我們考慮與前面使用的指令碼類似的 nosetest.py:

# content of nosetest.py
def func(x):
   return x + 1
   
def test_answer():
   assert func(3) == 5

為了執行上述測試,請使用以下命令列語法:

C:\python>nosetests –v nosetest.py

控制檯顯示的輸出如下:

nosetest.test_answer ... FAIL
================================================================
FAIL: nosetest.test_answer
----------------------------------------------------------------------
Traceback (most recent call last):
   File "C:\Python34\lib\site-packages\nose\case.py", line 198, in runTest
      self.test(*self.arg)
   File "C:\Python34\nosetest.py", line 6, in test_answer
      assert func(3) == 5
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures = 1)

可以透過在上述命令列中使用with-doctest 選項將nose 與 DocTest 整合。

\nosetests --with-doctest -v nosetest.py

您可以在測試指令碼中使用nose

import nose
nose.main()

如果您不希望測試指令碼在成功時以 0 退出,在失敗時以 1 退出(如 unittest.main),請使用 nose.run() 代替:

import nose
result = nose.run()

如果測試執行成功,則結果為 true;如果測試執行失敗或引發未捕獲的異常,則結果為 false。

nose 支援包、模組、類和測試級別的夾具(安裝和拆卸方法)。與 py.test 或 unittest 夾具一樣,安裝總是在任何測試(或測試包和模組的測試集合)之前執行;如果安裝成功完成,則無論測試執行的狀態如何,拆卸都會執行。

Nose 測試 - 工具

nose.tools 模組提供許多您可能會覺得有用的測試輔助工具,包括用於限制測試執行時間和測試異常的裝飾器,以及在 unittest.TestCase 中找到的所有相同的 assertX 方法。

  • nose.tools.ok_(expr, msg = None) − assert 的簡寫。

  • nose.tools.eq_(a, b, msg = None) − “assert a == b, “%r != %r” % (a, b)” 的簡寫。

  • nose.tools.make_decorator(func) − 包裝測試裝飾器,以便正確複製已裝飾函式的元資料,包括 nose 的附加內容(即,安裝和拆卸)。

  • nose.tools.raises(*exceptions) − 測試必須引發一個預期異常才能透過。

  • nose.tools.timed(limit) − 測試必須在指定的時間限制內完成才能透過。

  • nose.tools.istest(func) − 裝飾器,用於將函式或方法標記為測試。

  • nose.tools.nottest(func) − 裝飾器,用於將函式或方法標記為非測試。

引數化測試

Python 的測試框架 unittest 沒有簡單的方法來執行引數化測試用例。換句話說,您不能輕鬆地從外部向unittest.TestCase 傳遞引數。

但是,pytest 模組通過幾種良好整合的方案移植了測試引數化:

  • pytest.fixture() 允許您在夾具函式級別定義引數化。

  • @pytest.mark.parametrize 允許在函式或類級別定義引數化。它為特定的測試函式或類提供多個引數/fixture 集。

  • pytest_generate_tests 能夠實現您自己的自定義動態引數化方案或擴充套件。

第三方模組 'nose-parameterized' 允許使用任何 Python 測試框架進行引數化測試。它可以從此連結下載 − https://github.com/wolever/nose-parameterized

廣告
© . All rights reserved.