為什麼要測試?
Web程式開發過程一般包括以下幾個階段:[需求分析,設計階段,實作階段,測試階段]。其中測試階段通過人工或自動來運作測試某個系統的功能。目的是檢驗其是否滿足需求,并得出特定的結果,以達到弄清楚預期結果和實際結果之間的差别的最終目的。
測試的分類:
測試從軟體開發過程可以分為:
- 單元測試
- 對單獨的代碼塊(例如函數)分别進行測試,以保證它們的正确性
- 內建測試
- 對大量的程式單元的協同工作情況做測試
- 系統測試
- 同時對整個系統的正确性進行檢查,而不是針對獨立的片段
在衆多的測試中,與程式開發人員最密切的就是單元測試,因為單元測試是由開發人員進行的,而其他測試都由專業的測試人員來完成。是以我們主要學習單元測試。
什麼是單元測試?
程式開發過程中,寫代碼是為了實作需求。當我們的代碼通過了編譯,隻是說明它的文法正确,功能能否實作則不能保證。 是以,當我們的某些功能代碼完成後,為了檢驗其是否滿足程式的需求。可以通過編寫測試代碼,模拟程式運作的過程,檢驗功能代碼是否符合預期。
單元測試就是開發者編寫一小段代碼,檢驗目标代碼的功能是否符合預期。通常情況下,單元測試主要面向一些功能單一的子產品進行。
舉個例子:一部手機有許多零部件組成,在正式組裝一部手機前,手機内部的各個零部件,CPU、記憶體、電池、攝像頭等,都要進行測試,這就是單元測試。
在Web開發過程中,單元測試實際上就是一些“斷言”(assert)代碼。
斷言就是判斷一個函數或對象的一個方法所産生的結果是否符合你期望的那個結果。 python中assert斷言是聲明布爾值為真的判定,如果表達式為假會發生異常。單元測試中,一般使用assert來斷言結果。
斷言方法的使用:
斷言語句類似于:
if not expression:
raise AssertionError
AssertionError
常用的斷言方法:
assertEqual 如果兩個值相等,則pass
assertNotEqual 如果兩個值不相等,則pass
assertTrue 判斷bool值為True,則pass
assertFalse 判斷bool值為False,則pass
assertIsNone 不存在,則pass
assertIsNotNone 存在,則pass
如何測試?
簡單的測試用例:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,
def fibo(x):
if x == 0:
resp = 0
elif x == 1:
resp = 1
else:
return fibo(x-1) + fibo(x-2)
return resp
assert fibo(5) == 5
單元測試的基本寫法:
首先,定義一個類,繼承自unittest.TestCase
import unittest
class TestClass(unitest.TestCase):
pass
其次,在測試類中,定義兩個測試方法
import unittest
class TestClass(unittest.TestCase):
#該方法會首先執行,方法名為固定寫法
def setUp(self):
pass
#該方法會在測試代碼執行完後執行,方法名為固定寫法
def tearDown(self):
pass
最後,在測試類中,編寫測試代碼
import unittest
class TestClass(unittest.TestCase):
#該方法會首先執行,相當于做測試前的準備工作
def setUp(self):
pass
#該方法會在測試代碼執行完後執行,相當于做測試後的掃尾工作
def tearDown(self):
pass
#測試代碼
def test_app_exists(self):
pass
登入測試
- 被測試的代碼邏輯
@app.route('/login', methods=['POST']) def login(): username = request.form.get('username') password = request.form.get('password') # 判斷參數是否為空 if not all([username, password]): result = { "errcode": -2, "errmsg": "params error" } return jsonify(result) # a = 1 / 0 # 如果賬号密碼正确 # 判斷賬号密碼是否正确 if username == 'skylark' and password == 'python': result = { "errcode": 0, "errmsg": "success" } return jsonify(result) else: result = { "errcode": -1, "errmsg": "wrong username or password" } return jsonify(result)
- 單元測試代碼
import json
import unittest
from demo1_login import app
class LoginTest(unittest.TestCase):
"""為登入邏輯編寫測試案例"""
def setUp(self):
app.testing = True
self.client = app.test_client()
def test_empty_username_password(self):
"""測試使用者名與密碼為空的情況[當參數不全的話,傳回errcode=-2]"""
response = app.test_client().post('/login', data={})
json_data = response.data
json_dict = json.loads(json_data)
self.assertIn('errcode', json_dict, '資料格式傳回錯誤')
self.assertEqual(json_dict['errcode'], -2, '狀态碼傳回錯誤')
# TODO 測試使用者名為空的情況
# TODO 測試密碼為空的情況
def test_error_username_password(self):
"""測試使用者名和密碼錯誤的情況[當登入名和密碼錯誤的時候,傳回 errcode = -1]"""
response = app.test_client().post('/login', data={"username": "aaaaa", "password": "12343"})
json_data = response.data
json_dict = json.loads(json_data)
self.assertIn('errcode', json_dict, '資料格式傳回錯誤')
self.assertEqual(json_dict['errcode'], -1, '狀态碼傳回錯誤')
# TODO 測試使用者名錯誤的情況
# TODO 測試密碼錯誤的情況
if __name__ == '__main__':
unittest.main()
資料庫測試:
#coding=utf-8
import unittest
from author_book import *
#自定義測試類,setUp方法和tearDown方法會分别在測試前後執行。以test_開頭的函數就是具體的測試代碼。
class DatabaseTestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:mysql@localhost/test0'
self.app = app
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
#測試代碼
def test_append_data(self):
au = Author(name='skylark')
bk = Book(info='python')
db.session.add_all([au,bk])
db.session.commit()
author = Author.query.filter_by(name='skylark').first()
book = Book.query.filter_by(info='python').first()
#斷言資料存在
self.assertIsNotNone(author)
self.assertIsNotNone(book)