15

在最近的代碼審查一個,我在那沒有立即容易發現問題絆倒 - 有用來代替assertEqual()assertTrue(),基本上導致成一個測試,是測試沒有。這裏是一個簡化的例子:檢測不正確的斷言方法

from unittest import TestCase 


class MyTestCase(TestCase): 
    def test_two_things_equal(self): 
     self.assertTrue("a", "b") 

這裏的問題是測試會通過;從技術上講,代碼是有效的,因爲assertTrue has this optional msg argument(在這種情況下獲得"b"值)。

我們可以做得比依靠審查代碼的人發現這類問題更好嗎?有沒有辦法自動檢測它使用靜態代碼分析與flake8pylint

+1

但萎靡不振的技術上有效的代碼可能會創造一個全新的不同的問題; *信息溢出*。 –

+0

@ Ev.Kounis對,如果我們天真地接近它,可能會有很多誤報。我正在考慮只強制使用'msg'作爲關鍵字參數。在這種情況下,我們可能會通過警告消息未正確傳遞給斷言方法來捕獲此特定問題。我不太喜歡這個想法,但希望看看有沒有其他的想法..謝謝。 – alecxe

+1

我強烈建議@Leon提供的答案。通過添加你知道應該失敗的測試,你會發現一個被濫用的測試用例。 –

回答

5

幾年前,我想出了一種通用方法/方法來確保測試質量。測試的規範可以減少到兩個子句:

  1. 它必須正確執行該功能的被測試,並且
  2. 它必須失敗不正確/斷實施被測試的功能

據我所知,雖然要求1.經常被執行,但很少關注p援助要求2

通常

  • 一個測試套件被創建,
  • 代碼對其運行,
  • 任何失敗(因爲無論是在代碼或在測試中的錯誤的)是固定的
  • 當我們相信我們的代碼和測試是好的時,我們就會遇到這種情況。

實際情況可能是(某些)測試包含(會)阻止它們捕獲代碼中的錯誤的錯誤。因此,看到測試通過不應該提示關心繫統質量的人很安靜,直到他們確信測試確實能夠檢測到他們針對設計的問題。而一個簡單的方法就是實際引入這些問題並檢查它們是否不被測試所忽視!

在TDD(測試驅動開發)中,這個想法只被部分地遵循 - 建議在代碼之前添加測試,看看它失敗(應該,因爲沒有代碼),然後修復它編寫代碼。但是由於缺少代碼而導致的測試失敗並不意味着它在錯誤代碼的情況下也會失敗(這對您的情況似乎是正確的)!

因此,測試套件的質量可以衡量爲它能夠檢測到的錯誤的百分比。任何合理的轉義測試套件的錯誤提示涵蓋該場景的新測試用例(或者,如果測試套件應該已經捕獲該錯誤,測試套件中的錯誤被發現)。這也意味着套件的每個測試必須能夠捕獲至少一個錯誤(否則,該測試完全沒有意義)。

我在考慮實施一個有助於採用這種方法的軟件系統(即允許在代碼庫中注入和維護人工錯誤並檢查測試如何響應它們)。這個問題成爲我即將開始研究的觸發器。希望能在一週內把東西放在一起。敬請關注!

編輯

工具的原型版本現已在https://bitbucket.org/leon_manukyan/trit。我建議克隆存儲庫並運行演示流程。


此語句的更一般化版本爲更廣泛的系統/情況真(所有通常具有安全做/安全):

一種系統設計對某些必須定期對這些事件進行事件測試,否則它很容易降解到完全無法對感興趣的事件作出反應。

只是一個例子 - 你家有火警系統嗎?你上次什麼時候見證過它的工作?如果在火災中保持沉默,該怎麼辦?現在就在房間裏抽一些煙吧!

在這種方法的範圍,像臭蟲後門(例如,當功能行爲不端只有的在URL中傳遞等於https://www.formatmyharddrive.com/?confirm=yesofcourse)是不是一個合理的

+0

你說得對,很棒。嚴格地說,我們應該總是看到一個測試失敗「故意」 - 確保我們正在測試我們的目標。謝謝! – alecxe

+0

@alecxe該工具的原型版本已準備就緒。在https://bitbucket.org/leon_manukyan/trit找到它。我提供了一個演示流程,其問題類似於您的問題。 – Leon

+0

'trit'是關於我一直在想的很長一段時間,這太棒了!我一定會回顧一下,看看我是否可以將它應用到我的日常工作流程中。非常感謝! – alecxe

10

Python現在有一個類型提示系統,可以進行靜態代碼分析。使用這個系統,你可以要求像assertTrue這樣的函數的第一個參數總是布爾值。問題是assertTrue不是由你定義的,而是由unittest包定義的。不幸的是,unittest包沒有添加類型提示。儘管有一個相當簡單的方法:只需定義你自己的包裝。

from unittest import TestCase 

class TestCaseWrapper(TestCase): 
    def assertTrue(self, expr: bool, msg=None): #The ": bool" requires that the expr parameter is boolean. 
     TestCase.assertTrue(self, expr, msg) 

class MyTestCase(TestCaseWrapper): 
    def test_two_things_equal(self): 
     self.assertTrue("a", "b") #Would give a warning about the type of "a". 

然後,您可以運行類型檢查就像這樣:

python -m mypy my_test_case.py 

這應該然後給你一個關於如何報警「a」是一個字符串,而不是一個布爾值。關於這一點的好處是它可以在自動化測試框架中自動運行。此外,PyCharm會檢查代碼中的類型,如果您提供它們並突出顯示任何錯誤的內容。

+3

當製作這樣的包裝時,使用'functools.wraps'來製作文檔以及從原始函數繼承它也是有用的。 – Ghostkeeper

+0

建議第一種類型是布爾值是一個可怕的想法,因爲您可能只是檢查值是否真實。 –

2

快速解決方案將是,以提供檢查正確性一個混合:

import unittest 


class Mixin(object): 
    def assertTrue(self, *args, **kwargs): 
     if len(args) > 1: 
      # TypeError is just an example, it could also do some warning/logging 
      # stuff in here. 
      raise TypeError('msg should be given as keyword parameter.') 
     super().assertTrue(*args, **kwargs) 


class TestMixin(Mixin, unittest.TestCase): # Mixin before other parent classes 
    def test_two_things_equal(self): 
     self.assertTrue("a", "b") 

的密新還可以檢查是否傳遞表達是一個布爾:

class Mixin(object): 
    def assertTrue(self, *args, **kwargs): 
     if type(args[0]) is bool: 
      raise TypeError('expression should be a boolean') 
     if len(args) > 1: 
      raise TypeError('msg should be given as keyword parameter.') 
     super().assertTrue(*args, **kwargs) 

但是,這不是靜態的,它需要手動更改測試類(添加Mixin)並運行測試。此外,它會拋出大量的誤報,因爲將消息作爲關鍵字參數傳遞並不常見(至少不是我見過的),並且在很多情況下,您想要檢查表達式的隱含真實性而不是顯式的bool。要檢查不-空虛if aalistdict

你也可以使用一些setUpteardown代碼,改變了特定類的assertTrue方法:

import unittest 


def decorator(func): 
    def wrapper(*args, **kwargs): 
     if len(args) > 1: 
      raise TypeError() 
     return func(*args, **kwargs) 
    return wrapper 


class TestMixin(unittest.TestCase): 
    def setUp(self): 
     self._old = self.assertTrue 
     self.assertTrue = decorator(self.assertTrue) 

    def tearDown(self): 
     self.assertTrue = self._old 

    def test_two_things_equal(self): 
     self.assertTrue("a", "b") 

但謹慎使用這些方法之前的注意事項:在改變現有測試之前,務必小心謹慎。不幸的是,測試的記錄有時很差,所以測試和測試的方式並不總是很明顯。某些時候測試沒有意義,並且可以改變它,但有時它會以奇怪的方式測試某個特定功能,當您更改測試時會改變正在測試的內容。因此,至少要確保在更改測試用例時不會發生覆蓋範圍變化。如有必要,請確保通過更新方法名稱,方法文檔或內嵌評論來闡明測試的目的。

2

這種問題的一個解決方案是使用"mutation testing"。這個想法是通過在代碼中引入小的變化來自動生成代碼的「突變體」。然後,您的測試套件針對這些突變體運行,如果它們是好的,則大多數應該被殺死,這意味着您的測試套件檢測到突變並且測試失敗。

突變測試實際評估您的測試質量。在你的例子中,沒有突變體會被殺死,你會很容易發現測試有問題。

在Python中,有幾種突變框架可供選擇: