今天要开始写测试,这个部份我们不会特别认真写,重点是要把比较常用的函式秀出来。我们会用最原始的 unittest 套件来写,当然也可以自己改成 pytest 等等其他测试框架。
虽然说是前置作业,但这个步骤放到最後也没有关系。我们要来在 manage.py
再加入一个新指令。当然要记得 import unittest
。
@app.cli.command()
def test():
tests = unittest.TestLoader().discover("tests")
unittest.TextTestRunner().run(tests)
这是一段 unittest 很常见的程序码,基本上他会找到之前已经建好的 tests/
然後跑里面全部的测试。
这样一来,等等测试写完之後就可以使用 flask test
来跑测试了。
我们今天会写出 basic test 跟蓝图 main_bp
的测试,先从前者开始。以下程序码要放在 tests/test_basic.py
里面。
import unittest
from flask import url_for
from app import create_app
class BasicTest(unittest.TestCase):
def setUp(self) -> None:
self.app = create_app("testing")
self.client = self.app.test_client()
self.app_context = self.app.app_context()
self.app_context.push()
def tearDown(self) -> None:
if self.app_context:
self.app_context.pop()
def test_app_is_alive(self):
response = self.client.get(url_for("main.index_page"))
self.assertEqual(response.status_code, 200)
def test_blueprint(self):
self.assertNotEqual(self.app.blueprints.get("main", None), None)
self.assertNotEqual(self.app.blueprints.get("user", None), None)
self.assertNotEqual(self.app.blueprints.get("admin", None), None)
我们在 setUp
里面先用 create_app
建好一个 app
,接着使用了 test_client
,他是一个让我们可以模拟客户端的工具,可以使用他来对 app
发 request。接下来的两行跟 app_context
有关,基本上它就是把一个环境套用到里面,这样之後的程序码都会在这个环境之下。然後因为在 setUp
我们套用了 app_context
,那在结束 tearDown
的时候就要把他 pop
掉,这样才不会影响之後的测试。
如果觉得有点难懂的话,可以打开 python,然後直接呼叫 current_app.config
,他会告诉你 Working outside of application context.
,这时候如果你给他一个 app.app_context().push()
(当然 app
要自己宣告) 再呼叫一次,就可以看到他没有错误然後给你一个好好的设定档,就像我们之前看到的那样,而 pop
掉之後他又会变成跑出错误。在这里做的事就接近上述的行为,只是把在同一个 python interpreter 的环境变成在同一次测试的环境。
接下来我们继续看到後面,这里有两个测试,第一个是透过抓首页来确定 app
有没有好好活着;第二个是确定蓝图有没有被好好的载入。我们分开来说明。
test_app_is_alive
中,我们使用了刚刚宣告的 self.client
,并使用了他的 get
函式,这个跟 HTTP method 的那个 get 是同一个,所以可想而知,後面一定会有 post、put 等等类似的函式。而他会回传一个 response
,我们可以来检查他的回应有没有符合预期。此处我们去看他的 status_code
是不是 200。test_blueprint
里面,我们去检查 self.app.blueprints
里面有没有该有的蓝图,他是 dict 型别,所以可以这样去确认。因为测试很多函式的重复性很高,所以在开始写 main_bp
的测试前,我们先来写一下 helper.py
,一样要放在 tests/
里面。
import unittest
from flask import url_for
from app import create_app
from app.database import db, add_user
class TestModel(unittest.TestCase):
def setUp(self) -> None:
self.app = create_app("testing")
self.client = self.app.test_client()
self.app_context = self.app.app_context()
self.app_context.push()
self.user_data = {"username": "user", "password": "user"}
self.admin_data = {"username": "admin", "password": "admin"}
generate_test_data()
def tearDown(self) -> None:
db.session.remove()
db.drop_all()
if self.app_context is not None:
self.app_context.pop()
def user_login(self):
return self.client.post(url_for("user.login_page"), data=self.user_data)
def admin_login(self):
return self.client.post(url_for("user.login_page"), data=self.admin_data)
def login(self, login):
if login == "user":
self.user_login()
if login == "admin":
self.admin_login()
def get(self, login=False):
self.login(login)
res = self.client.get(self.route, follow_redirects=True)
return res
def post(self, login=False, data=None):
self.login(login)
res = self.client.post(self.route, data=data, follow_redirects=True)
return res
def generate_test_data():
db.create_all()
add_user("user", "user", "[email protected]")
add_user("admin", "admin", "[email protected]", is_admin=True)
在这里面我们定义了一个 TestModel
,我们之後的测试都会换成继承他,而非刚刚的 unittest.TestCase
,也因为如此,接下来定义的函式都不是测试,而是给测试用的工具。在 setUp
还有 tearDown
跟刚刚做的事差不多,有差别的部份是我们新增了 user_data
和 admin_data
,这在之後登入会用到。同时我们也用之前写好的 add_user
来加入两个测试用的使用者,如果有需要测试贴文、留言的话,也可以自己加入测试的资料。还有我们也在 tearDown
加入了清除资料库的动作。
接着我们定义了 user_login
和 admin_login
,这边就用到刚刚提到的 post
这个函式,然後他使用 data
参数把登入资料丢进去。再用 login
把上述两者包装起来给之後的函式用。
接下来我们自己定义了 get
和 post
两个函式,让它包含登入的功能,登入有很多种写法,但有时候并没有那麽直观,可能会遇到明明登入了但後面发请求的时候又变成没登入,这常常都是因为 context 不对。post
的部分跟刚刚一样都使用 data
参数来把资料传送给後端。我们还用到了一个叫做 follow_redirects
的参数,如果不让他 follow 的话,那 status_code
就会变成 302
,然後我们看到的资料也都是重新导向页面的资料,而这通常不是我们乐见的 (除非我们只想确定他有重新导向),因此在此处我们加上这个参数。
昨天已经认识分隔符号 DELIMITER和STORED PROCEDURE建立语法, 建立出BMI小...
前言 前面几天介绍了很多设计 SwiftUI 画面的元件, 那要怎麽知道元件的位置和尺寸大小呢? 这...
今年的疫情蛮严重的,希望大家都过得安好,希望疫情快点过去,能回到一些线下技术聚会的时光~ 今天目标:...
前端开发者在後端 api 尚在开发阶段,需要模拟 api 回传一些种子资料时,自行架设一个开发用的 ...
今天终於要进入到生命周期的最後一个阶段: Unmounting 了!在元件要被卸载的这个阶段会发生...