單元測試與安全測試淺析



測試的意義

  人們針對一個具體問題,通過分析和設計,最后用編程語言寫出了一個程序,如果它通過了語言解釋器(編譯器)的檢查,可以運行了,那么下一步的工作就是設法確認它確實滿足了我們需求。這篇文章就是討論怎么確認程序是否滿足用戶提出的需求。

滿足需求,換言之就是功能正常,確認功能正常可以從以下幾個方面確認:

  • 定義的函數對於所有正確的參數都能返回正確的結果
  • 寫出的程序對所有合適的輸入都能產生正確的輸出

  量化后的做法就是通過一系列的試運行,檢查程序的行為、輸入和輸出,如果檢查中發現了問題,就糾正、改進。這個也是功能測試和安全測試的初衷。

測試用例

  測試考慮的基本問題就是怎么運行程序,需要提供什么數據,才能最大限度的檢查程序的各種行為和情況,最大可能的挖出程序中的錯誤和缺陷。基於設計什么測試流程、提供什么參數這種檢查程序運行的一套數據被稱為一個測試用例。一個測試用例就是可量化的測試流程。

確認測試用例又區分兩類方式:

  • 黑盒測試
    就是不看代碼,直接上手程序的使用測試。這里不討論黑盒測試。

  • 白盒測試
    白盒測試的基礎是看程序的內部結構(代碼)和可能產生的執行路徑,根據內部結構來選擇測試的用例,使程序在試驗性運行中就能表示出盡可能多的不同行為。這個做法的基本理念就是:如果所有可能執行的路徑(順序、條件、while、for、嵌套...執行結構)都能給出正確的結果,那么程序的正確性就能得到保證。


測試函數功能案例

  各類的語言都會提供單元測試的庫,Python也不例外,python一般使用PyUnit(unittest)庫,unittest是Python自帶的單元測試框架,用於編寫和運行可重復的測試,下面介紹怎么用unittest來測試函數的用法,我這里只是簡單用了幾個測試方法,更多測試方法請查閱官網(https://docs.python.org/3/library/unittest.html)。

3個需要測試的函數:

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

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

def is_evenNumbers(x):
    if (x % 2) == 0:
        return True
    else:
        return False

測試函數的方法:

import unittest
import testbox.mymath as mymath


class Test(unittest.TestCase):
    def setUp(self):
        print("The unit test function start.")

    def test_mysum(self):
        self.assertEqual(mymath.mysum(1, 2), 3, "mysum function have error!")

    def test_mysubtraction(self):
        self.assertEqual(mymath.mysubtraction(2, 1), 1, "mysubtraction function have error!")

    def test_is_evenNumbers(self):
        self.assertTrue(mymath.is_evenNumbers(2), "error")

    def test_is_evenNumbers2(self):
        self.assertFalse(mymath.is_evenNumbers(3), "error")

    def tearDown(self):
        print("The unit test end.")

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

輸出:

Testing started at 12:26 PM ...
The unit test function start.
The unit test end.
The unit test function start.
The unit test end.
The unit test function start.
The unit test end.
The unit test function start.
The unit test end.

assert關鍵字的用法

  功能其實和上面測試函數用法是一樣的,只不過assert可以直接使用在代碼里。這個關鍵字也比較生僻,也沒見什么場景需要用它,也就這里為了做個案例,我才用它寫了個demo。

def testasserts(a):
    assert a == 2, Exception("parameter a not is 2, so have a error.")
    if a == 2:
        print("function run.")
    print("OK. function end.")

if __name__ == '__main__':
    testasserts(1)
    print("Program is end.")

輸出:

Traceback (most recent call last):
  File "/Users/Mysticbinary/Document/code/personage/python/TestPython1/testbox/testadd.py", line 9, in <module>
    testasserts(1)
  File "/Users/Mysticbinary/Document/code/personage/python/TestPython1/testbox/testadd.py", line 2, in testasserts
    assert a == 2, Exception("parameter a not is 2, so have a error.")
AssertionError: parameter a not is 2, so have a error.

測試類功能案例

  類功能的測試和函數測試一樣,只不過有一個竅門就是,測試、使用類的時候都需要先實例化類,而實例化類的操作,都可以放在setUp()里面操作。

需要測試的類:

class Library:
    allBook = ["php", "java"]

    def __init__(self):
        print("Library class create completion.")

    def savebook(self, bookname):
        self.allBook.append(bookname);
        return self.allBook

    def showbook(self):
        print(self.allBook)
        return self.allBook

測試類的方法:

import unittest
import testbox.myclass as myc

class TestClass(unittest.TestCase):
    def setUp(self):
        print("The unit test class start.")
        self.library = myc.Library()
        self.newbook = "python"

    def test_savebook(self):
        self.assertIn(self.newbook, self.library.savebook(self.newbook), "errow 1")

    def test_showbook(self):
        self.assertIn(self.newbook, self.library.showbook(), "errow 2")

    def tearDown(self):
        print("The unit test end.")

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

輸出:

Testing started at 12:31 PM ...
The unit test class start.
Library class create completion.
The unit test end.
The unit test class start.
Library class create completion.
['php', 'java', 'python']
The unit test end.

安全測試案例-短信轟炸

  我前面說過,功能測試和安全測試都有同樣的初衷,但是具體的測試手法兩者不太一樣,但是一些特定的場景下,使用單元測試的方法,也是能測試一些安全問題的,比如說測接口越權、短信接口重復導致的短信轟炸問題等。我這里只是拋磚引玉一下通過單元測試的手法來做安全測試的例子,但為做深入研究。

def send_message(phone):
    keys = phones_dict.keys()
    if phone not in keys:
        phones_dict[phone] = 0
    else:
        if phones_dict[phone] < 10:
            # 執行發短信的流程
            phones_dict[phone] = (phones_dict[phone] + 1)
            print("已經發送了{}次短信".format(phones_dict[phone]))
            return "success"
        else:
            print("抱歉,該{}手機號 已經達到今天發短信的上限,請明天再來。".format(phone))
            return "error"

測試發短信函數安全的測試用例:

    def test_send_message(self):
        result = list()
        for i in range(0, 11):
            result.append(sms.send_message("13193388105"))
        print(result)
        self.assertNotIn("error", result, "send_message have error.")

輸出:

Testing started at 9:48 PM ...
The unit test function start.
已經發送了1次短信
已經發送了2次短信
已經發送了3次短信
已經發送了4次短信
已經發送了5次短信
已經發送了6次短信
已經發送了7次短信
已經發送了8次短信
已經發送了9次短信
已經發送了10次短信
[None, 'success', 'success', 'success', 'success', 'success', 'success', 'success', 'success', 'success', 'success']
The unit test end.

__main__全局變量解釋

  除了單元測試,設置模塊的運行入口(main)也是一種測試方式,就是針對每個模塊單獨的調用里面的函數。__main__其實是一個全局變量,解釋器發現如果該模塊是被導入的,那么__main__就會被賦值為這個模塊的名字,如果這個模塊是作為主模塊啟動時,那么解釋器就會給__main__賦值為“main”字符串。


總結

  1. 建議不要在程序上線的時候還帶着assert、print之類的調試語句、避免信息泄露。
  2. 單元測試不單只用於功能測試,也可以用在一些特定的安全測試里(具體范圍有那些,我沒有研究,如果有需求的話,我可能繼續深入研究)。
  3. unittest的斷言非常簡單易用,基本看一眼就能懂,但是懂斷言的用法,不代表你就會寫測試用例,能看到程序代碼的執行結構才是寫出測試的關鍵所在。一句話就是:寫斷言易,看代碼不易。
  4. 目前還有一種流行的開發方式叫作測試驅動開發,這種方式強調先編寫測試用例,然后再編寫函數和方法。也就是在開發某個功能之前,先定義好該功能的最終結果(測試用例關注函數的執行結果),然后再去開發該功能。就像建築工人在砌牆之前,要先拉好一根筆直的繩子(作用相當於測試用例),然后再開始砌牆,這樣砌出來的牆就會符合標准。所以說測試驅動開發也是一種先定義接口,后開發的模式,這樣做比較規范,但是對於開發人員來說成本可能有點高,因為有些接口需要在開發的時候才知道返回什么,或者開發的時候也需要經常修改。這種預先定義開發方式,只能說優缺點各半吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM