為什么要測試?
Web程序開發(fā)過程一般包括以下幾個階段:需求分析龙优,設計階段羊异,實現(xiàn)階段,測試階段彤断。其中測試階段通過人工或自動來運行測試某個系統(tǒng)的功能野舶。目的是檢驗其是否滿足需求,并得出特定的結果宰衙,以達到弄清楚預期結果和實際結果之間的差別的最終目的平道。
測試的分類:
測試從軟件開發(fā)過程可以分為:單元測試、集成測試供炼、系統(tǒng)測試等一屋。在眾多的測試中,與程序開發(fā)人員最密切的就是單元測試袋哼,因為單元測試是由開發(fā)人員進行的冀墨,而其他測試都由專業(yè)的測試人員來完成。所以作為開發(fā)人員主要需要學習單元測試涛贯。
什么是單元測試诽嘉?
程序開發(fā)過程中,寫代碼是為了實現(xiàn)需求弟翘。當我們的代碼通過了編譯虫腋,只是說明它的語法正確,功能能否實現(xiàn)則不能保證稀余。 因此悦冀,當我們的某些功能代碼完成后,為了檢驗其是否滿足程序的需求睛琳『畜。可以通過編寫測試代碼,模擬程序運行的過程师骗,檢驗功能代碼是否符合預期茁影。
單元測試就是開發(fā)者編寫一小段代碼,檢驗目標代碼的功能是否符合預期丧凤。通常情況下募闲,單元測試主要面向一些功能單一的模塊進行。
舉個例子:一部手機有許多零部件組成愿待,在正式組裝一部手機前浩螺,手機內部的各個零部件靴患,CPU、內存要出、電池鸳君、攝像頭等,都要進行測試患蹂,這就是單元測試或颊。
在Web開發(fā)過程中,單元測試實際上就是一些“斷言”(assert)代碼传于。
斷言就是判斷一個函數(shù)或對象的一個方法所產(chǎn)生的結果是否符合你期望的那個結果囱挑。 python中assert斷言是聲明布爾值為真的判定,如果表達式為假會發(fā)生異常沼溜。單元測試中平挑,一般使用assert來斷言結果。
斷言方法的使用:
# 定義一個list
In [6]: a = [1,3,5,7,9]
In [7]: b = 3
# 斷言判斷 b 是否存在 a 中系草,如果正確通熄,則不會報錯
In [8]: assert b in a
# 斷言如果報錯,可以自定義打印錯誤信息找都,這里定義錯誤為 False
In [9]: assert b not in a, 'False'
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-9-a3961e666e68> in <module>
----> 1 assert b not in a, 'False'
AssertionError: False
# 斷言報錯唇辨,但是如果沒有自定義錯誤信息,則只會顯示錯誤
In [10]: assert b not in a
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-10-57ad4b59ee88> in <module>
----> 1 assert b not in a
AssertionError:
In [11]:
斷言語句類似于:
if not expression:
raise AssertionError
常用的斷言方法:
assertEqual 如果兩個值相等能耻,則pass
assertNotEqual 如果兩個值不相等助泽,則pass
assertTrue 判斷bool值為True,則pass
assertFalse 判斷bool值為False嚎京,則pass
assertIsNone 不存在,則pass
assertIsNotNone 存在隐解,則pass
如何測試鞍帝?
寫一個斐波那契數(shù)列 Fibonacci 來進行測試,驗證以下的數(shù)字是否符合斐波那契數(shù)列煞茫。
可以測試的數(shù)字:
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233帕涌,377,610续徽,987蚓曼,1597,2584钦扭,4181纫版,6765,
# 斐波那契數(shù)列 Fibonacci
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
測試執(zhí)行判斷斷言如下:
單元測試的基本寫法:
首先客情,定義一個類其弊,繼承自unittest.TestCase
import unittest
class TestClass(unitest.TestCase):
pass
其次癞己,在測試類中,定義兩個測試方法
import unittest
class TestClass(unittest.TestCase):
#該方法會首先執(zhí)行梭伐,方法名為固定寫法
def setUp(self):
pass
#該方法會在測試代碼執(zhí)行完后執(zhí)行痹雅,方法名為固定寫法
def tearDown(self):
pass
最后,在測試類中糊识,編寫測試代碼
import unittest
class TestClass(unittest.TestCase):
#該方法會首先執(zhí)行绩社,相當于做測試前的準備工作
def setUp(self):
pass
#該方法會在測試代碼執(zhí)行完后執(zhí)行,相當于做測試后的掃尾工作
def tearDown(self):
pass
#測試代碼
def test_app_exists(self):
pass
看清楚了上面關于unittest測試框架的基本寫法之后赂苗,下面來寫一個登錄的視圖函數(shù)愉耙,然后再寫一個視圖函數(shù)的單元測試。
登錄視圖函數(shù)的單元測試
1.編寫一個模擬登錄的視圖函數(shù) login.py
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/login", methods=["POST"])
def login():
"""登錄"""
name = request.form.get("name")
password = request.form.get("password")
# "" 0 [] () {} None 在邏輯判斷時都是假
if not all([name, password]):
# 表示name或password中有一個為空或者都為空
return jsonify(code=65535, message="參數(shù)不完整")
if name == "admin" and password =="123456":
return jsonify(code=0, message="OK")
else:
return jsonify(code=65535, message="用戶名或密碼錯誤")
if __name__ == '__main__':
app.run(debug=True)
2. 使用postman測試login登錄
首先輸入正確的用戶名和密碼測試哑梳,如下:
然后去除用戶名或者密碼劲阎,缺少參數(shù)進行請求,如下:
故意輸錯密碼進行請求鸠真,如下:
通過postman測試接口這三種情況是可以的悯仙,但是如果每次都要手動去進行這樣的單元測試,就會感覺很麻煩了吠卷。
那么下面可以將這三種情況寫成單元測試的代碼锡垄,來避免重復測試。
3.編寫單元測試代碼 test_login.py
import unittest
from login import app
import json
class TestLogin(unittest.TestCase):
"""定義測試案例"""
def setUp(self):
"""在執(zhí)行具體的測試方法前祭隔,先被調用"""
self.app = app
# 激活測試標志
app.config['TESTING'] = True
# 可以使用python的http標準客戶端進行測試
# urllib urllib2 requests
# 在這里,使用flask提供的測試客戶端進行測試
self.client = app.test_client()
def test_empty_name_password(self):
"""測試模擬場景货岭,用戶名或密碼不完整"""
# 使用客戶端向后端發(fā)送post請求, data指明發(fā)送的數(shù)據(jù),會返回一個響應對象
response = self.client.post("/login", data={})
# respoonse.data是響應體數(shù)據(jù)
resp_json = response.data
# 按照json解析
resp_dict = json.loads(resp_json)
# 使用斷言進行驗證:是否存在code字符串在字典中
self.assertIn("code", resp_dict)
# 獲取code的返回碼的值疾渴,驗證是否為錯誤碼 65535
code = resp_dict.get("code")
self.assertEqual(code, 65535)
# 測試只傳name
response = self.client.post("/login", data={"name": "admin"})
# respoonse.data是響應體數(shù)據(jù)
resp_json = response.data
# 按照json解析
resp_dict = json.loads(resp_json)
# 使用斷言進行驗證
self.assertIn("code", resp_dict)
# 驗證錯誤碼 65535
code = resp_dict.get("code")
self.assertEqual(code, 65535)
# 驗證返回信息
msg = resp_dict.get('message')
self.assertEqual(msg, "參數(shù)不完整")
def test_wrong_name_password(self):
"""測試用戶名或密碼錯誤"""
# 使用客戶端向后端發(fā)送post請求, data指明發(fā)送的數(shù)據(jù)千贯,會返回一個響應對象
response = self.client.post("/login", data={"name": "admin", "password": "123456789"})
# respoonse.data是響應體數(shù)據(jù)
resp_json = response.data
# 按照json解析
resp_dict = json.loads(resp_json)
# 使用斷言進行驗證
self.assertIn("code", resp_dict)
# 驗證錯誤碼
code = resp_dict.get("code")
self.assertEqual(code, 65535)
# 驗證返回信息
msg = resp_dict.get('message')
self.assertEqual(msg, "用戶名或密碼錯誤")
if __name__ == '__main__':
unittest.main()
執(zhí)行測試如下:
從上面可以看出,大部分的Flask框架的單元測試就是這樣的處理流程搞坝。下面再提供一個數(shù)據(jù)庫單元測試的示例搔谴。
數(shù)據(jù)庫單元測試:
數(shù)據(jù)單元測試的基本步驟方法如下:
1.替換使用一個創(chuàng)建的testdb測試庫,避免影響項目的實際數(shù)據(jù)庫
2.導入代碼中構建數(shù)據(jù)庫的模型類桩撮、app敦第、db等對象,創(chuàng)建數(shù)據(jù)庫以及創(chuàng)建數(shù)據(jù)
3.斷言查詢數(shù)據(jù)庫的數(shù)據(jù)店量,正確則單元測試成功
4.測試完畢之后芜果,刪除創(chuàng)建的數(shù)據(jù)表
下面來看看實際代碼,如下:
準備用來測試的項目代碼 db_database.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pymysql
pymysql.install_as_MySQLdb()
from flask_migrate import Migrate,MigrateCommand
from flask_script import Shell,Manager
app = Flask(__name__)
manager = Manager(app)
class Config(object):
"""配置參數(shù)"""
# 設置連接數(shù)據(jù)庫的URL
user = 'root'
password = '***************'
database = 'flask_ex'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://%s:%s@127.0.0.1:3306/%s' % (user,password,database)
# 設置sqlalchemy自動更跟蹤數(shù)據(jù)庫
SQLALCHEMY_TRACK_MODIFICATIONS = True
# 查詢時會顯示原始SQL語句
# app.config['SQLALCHEMY_ECHO'] = True
# 禁止自動提交數(shù)據(jù)處理
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = False
# 設置密鑰融师,用于csrf_token的加解密
app.config["SECRET_KEY"] = "xhosd6f982yfhowefy29f"
# 讀取配置
app.config.from_object(Config)
# 創(chuàng)建數(shù)據(jù)庫sqlalchemy工具對象
db = SQLAlchemy(app)
#第一個參數(shù)是Flask的實例右钾,第二個參數(shù)是Sqlalchemy數(shù)據(jù)庫實例
migrate = Migrate(app,db)
#manager是Flask-Script的實例,這條語句在flask-Script中添加一個db命令
manager.add_command('db',MigrateCommand)
#定義模型類-作者
class Author(db.Model):
__tablename__ = 'author'
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(32),unique=True)
email = db.Column(db.String(64))
au_book = db.relationship('Book',backref='author')
def __str__(self):
return 'Author:%s' %self.name
#定義模型類-書名
class Book(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer,primary_key=True)
info = db.Column(db.String(32),unique=True)
leader = db.Column(db.String(32))
au_book = db.Column(db.Integer,db.ForeignKey('author.id'))
def __str__(self):
return 'Book:%s,%s'%(self.info,self.leader)
if __name__ == '__main__':
# 通過管理對象來啟動flask
manager.run()
進行數(shù)據(jù)庫單元測試的代碼 test_db.py
import unittest
from db_database import app,db,Author,Book
import time
class TestLogin(unittest.TestCase):
"""定義測試案例"""
def setUp(self):
"""在執(zhí)行具體的測試方法前,先被調用"""
# 激活測試標志
app.config['TESTING'] = True
# 設置用來測試的數(shù)據(jù)庫霹粥,避免使用正式數(shù)據(jù)庫實例[覆蓋原來項目中的數(shù)據(jù)庫配置]
user = 'root'
password = '***********'
# 設置數(shù)據(jù)庫炬丸,測試之前需要創(chuàng)建好 create database testdb charset=utf8;
database = 'testdb'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://%s:%s@127.0.0.1:3306/%s' % (user, password, database)
self.app = app
# 創(chuàng)建數(shù)據(jù)庫的所有模型表:Author漂羊、Book模型表
db.create_all()
def tearDown(self):
# 測試結束操作立润,刪除數(shù)據(jù)庫
db.session.remove()
db.drop_all()
# 測試代碼
def test_append_data(self):
au = Author(name='quyuan')
bk = Book(info='python_book')
db.session.add_all([au, bk])
db.session.commit()
author = Author.query.filter_by(name='quyuan').first()
book = Book.query.filter_by(info='python_book').first()
# 斷言數(shù)據(jù)存在
self.assertIsNotNone(author)
self.assertIsNotNone(book)
# 休眠10秒幸冻,可以到數(shù)據(jù)庫中查詢表進行確認
time.sleep(10)
if __name__ == '__main__':
unittest.main()
測試執(zhí)行,執(zhí)行過程查看mysql的數(shù)據(jù)庫表浩淘,如下:
# 切換數(shù)據(jù)庫testdb
mysql> use testdb;
Database changed
mysql>
# 查看表為空
mysql> show tables;
Empty set (0.00 sec)
# 執(zhí)行過程捌朴,創(chuàng)建表成功
mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| author |
| books |
+------------------+
2 rows in set (0.00 sec)
mysql>
# 執(zhí)行完畢,表被全部刪除
mysql> show tables;
Empty set (0.00 sec)
mysql>
查看單元測試執(zhí)行成功张抄,如下: