Python單元測試淺析

測試的意義

  人們針對一個具體問題,經過分析和設計,最後用編程語言寫出了一個程序,若是它經過了語言解釋器(編譯器)的檢查,能夠運行了,那麼下一步的工做就是設法確認它確實知足了咱們需求。這篇文章就是討論怎麼確認程序是否知足用戶提出的需求。php

知足需求,換言之就是功能正常,確認功能正常能夠從如下幾個方面確認:html

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

  量化後的作法就是經過一系列的試運行,檢查程序的行爲、輸入和輸出,若是檢查中發現了問題,就糾正、改進。這個也是功能測試和安全測試的初衷。java

測試用例

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

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

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

  • 白盒測試
    白盒測試的基礎是看程序的內部結構(代碼)和可能產生的執行路徑,根據內部結構來選擇測試的用例,使程序在試驗性運行中就能表示出儘量多的不一樣行爲。這個作法的基本理念就是:若是全部可能執行的路徑(順序、條件、while、for、嵌套...執行結構)都能給出正確的結果,那麼程序的正確性就能獲得保證。app


測試函數功能案例

  各種的語言都會提供單元測試的庫,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的斷言很是簡單易用,基本看一眼就能懂,可是懂斷言的用法,不表明你就會寫測試用例,能看到程序代碼的執行結構纔是寫出測試的關鍵所在。一句話就是:寫斷言易,看代碼不易。
相關文章
相關標籤/搜索