在Python的Flask框架中實現單元測試的教程,屬於自動化部署的方面,可以給debug工作帶來諸多便利,需要的朋友可以參考下
概要
在前面的章節裡我們專注於在我們的小應用程序上一步步的添加功能上。到現在為止我們有了一個帶有數據庫的應用程序,可以注冊用戶,記錄用戶登陸退出日志以及查看修改配置文件。
在本節中,我們不為應用程序添加任何新功能,相反,我們要尋找一種方法來增加我們已寫代碼的穩定性,我們還將創建一個測試框架來幫助我們防止將來程序中出現的失敗和回滾。
讓我們來找bug
在上一章的結尾談到,我故意在應用程序中引入一個bug。接下來讓我描述一下它是什麼樣的bug,然後看看當我們的程序不按照我們意願執行的時候,它在其中又起了什麼樣的影響。
應用程序的問題在於,沒有保證用戶昵稱的唯一性。用戶昵稱是由應用程序自動初始化的。我們首先會考慮使用OpenID provider給出的用戶的昵稱,然後再考慮使用Email信息中的用戶名部分作為用戶的昵稱。但如果出現重復的昵稱,則後面的用戶將無法注冊成功。更糟糕的是,在修改用戶配置的表單中,我們允許用戶任意更改他們的昵稱,但我們仍然沒有對昵稱沖突進行檢查。
當我們分析完錯誤產生時應用程序的行為之後,我們將會定位這些問題。
Flask 的調試功能
那麼讓我們看看當bug被觸發時,會出現什麼現象。
讓我們從創建一個嶄新的數據庫,在linux下,執行:
?
1 2 rm app.db ./db_create.py在Windows下,執行:
?
1 2 del app.db flask/Scripts/python db_create.py我們需要兩個OpenID的賬號來重現這個bug。當然這兩個賬號最理想的狀態是來自來個不同的擁有者,那樣可以避免他們的cookie把情況搞的更復雜。通過如下步驟創建沖突的昵稱:
用第一個賬號登陸
進入用戶信息屬性編輯頁面,將昵稱改為“dup”
登出系統
用第二個賬號登陸
修改第二個賬號的用戶信息屬性,將昵稱改為“dup”
哎喲!sqlalchemy中拋出了一個異常,來看一下錯誤信息:
?
1 2 lalchemy.exc.IntegrityError IntegrityError: (IntegrityError) column nickname is not unique u'UPDATE user SET nickname=?, about_me=? WHERE user.id = ?' (u'dup', u'', 2)錯誤的後面是這個錯誤的堆棧信息,事實上,這是一個相當不錯的錯誤提示,你可以轉向任何框架檢查代碼或者在浏覽器裡執行正確的表達式。
這個錯誤信息相當明確,我們試圖在數據插入一個重復的昵稱,數據庫的昵稱字段是一個衛衣鍵,因此這樣的操作是無效的。
除了實際的錯誤,在我們手頭上還有一個次要的錯誤。如果一個用戶不注意在我們應用程序裡引起了一個錯誤(這一個錯誤或者任何其他原因引起的異常),應用程序將向他/她暴漏錯誤信息和堆棧信息,而不是暴露給我們。對於我們開發者來說這是個很好的特性,但是很多時候我們不想讓用戶看到這些信息。
這麼長時間以來,我們一直在debug模式下運行我們的應用程序,我們通過設置debug=True的參數來啟用應用程序的debug模式。這裡我們在運行腳本run.py裡配置。
當我們這樣開發應用是方便的,但是我們需要在生產環境上關閉debug模式。 讓我們創建另一個啟動腳本文件設置關閉dubug模式(filerunp.py):
?
1 2 3 #!flask/bin/python from app import app app.run(debug = False)現在重新啟動應用:
?
1 ./runp.py並且現在再嘗試重命名第二個賬號nickname成‘dup'
這次我們沒有獲取到一個錯誤信息,取而代之,我們得到了一個HTTP 500錯誤碼,這是個內部服務器錯誤。雖然這不容易定位錯誤,但至少沒有暴露我們應用程序的任何細節給陌生人。當調試關閉後出現一個異常時,Flask會產生一個500頁面。
雖然這樣好些了,但現在仍存在兩個問題。首先美化問題:默認的500頁面很丑陋。第二個問題更重要些,當用戶操作失敗時,我們無法獲取到錯誤信息了,因為錯誤在後台默默的處理了。幸運的是有個簡單方式來處理這兩個問題。
定制HTTP錯誤處理程序
Flask為應用程序提供了一個機制來安裝他們自己的錯誤頁面,作為例子,讓我們定義兩個最常見的HTTP 404和500錯誤的自定義頁面。定制其他錯誤頁面也是同樣的方式。
使用一個修飾來聲明一個定制的錯誤處理程序 (fileapp/views.py):
?
1 2 3 4 5 6 7 8 @app.errorhandler(404) def internal_error(error): return render_template('404.html'), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() return render_template('500.html'), 500這地方無需多言,因為他們都是不言而喻的。唯一有趣的地方時錯誤500處理中的rollack語句,這個地方是不可缺少的因為這個方法會被當做一個異常調用。如果因為數據庫錯誤導致一個異常,那麼數據庫的會話將變成一個無效狀態,因此我們需要回滾它,以防止一個會話轉向一個500錯誤的模板。
這是一個404錯誤在模版
?
1 2 3 4 5 6 7 <!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>File Not Found</h1> <p><a href="{{url_for('index')}}">Back</a></p> {% endblock %}這是一個500錯誤的模版
?
1 2 3 4 5 6 7 8 <!-- extend base layout --> {% extends "base.html" %} {% block content %} <h1>An unexpected error has occurred</h1> <p>The administrator has been notified. Sorry for the inconvenience!</p> <p><a href="{{url_for('index')}}">Back</a></p> {% endblock %}注意,我們會繼續使用我們base.html 布局, 這樣我們的錯誤頁看起來比較舒服
通過email發送錯誤日志
為了處理第二個問題我們需要配置應用的錯誤報告機制。
第一個是每當有錯誤發生時把錯誤日志通過郵件發送給我們。
首先,我們需要在我們的應用配置郵件服務器和管理員列表 (fileconfig.py):
?
1 2 3 4 5 6 7 8 # mail server settings MAIL_SERVER = 'localhost' MAIL_PORT = 25 MAIL_USERNAME = None MAIL_PASSWORD = None # administrator list ADMINS = ['[email protected]']當然,你要把上面的配置改成你自己的才有意義
Flask 使用通用的Python logging模塊, 所以設置發送錯誤日志郵件非常簡單. (fileapp/__init__.py):
?
1 2 3 4 5 6 7