Flask Web 測試驅動開發最佳實踐之 Flask 實例


前言

前一篇講到了 TDD 測試驅動開發的相關概念和環境搭建,這篇就着手開始用TDD方式開發了。首先這篇需要編寫用戶相關的API接口,如用戶註冊、用戶登錄、用戶驗證等功能。

編寫測試

這裏使用 Python 自帶的測試框架 unittest 來編寫簡單測試,後續會使用更好用的 pytest 框架來完成整個網站的測試。
首先,最基本的測試是 Flask 應用實例是否存在,新建一個 test_basic.py 文件,當前 v2ex 項目結構如下:

├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
└── tests
    ├── test_basic.py
    └── test_user.py

編寫 test_user.py 代碼如下:

import unittest
from flask import current_app
from server import create_app


class UserTestCase(unittest.TestCase):
    def setUp(self):
        self.app = create_app('testing')
        self.app_context = self.app.app_context()
        self.app_context.push()

    def tearDown(self):
        self.app_context.pop()

    def test_app_is_exist():
        """測試 Flask 實例是否存在"""
        self.assertFalse(current_app is None)


if __name__ == '__main__':
    unittest.main()

這裏使用 unittest 編寫了一個測試類,setUp() 和 tearDown() 允許執行每個測試用例前分別初始化和清理測試環境,setUp 可用於創建應用實例,然後測試完成後就通過 tearDown 函數清理.

很明顯,這個測試肯定是失敗的,因爲很明顯

from server import create_app 

這一行是什麼東東啊,好像項目裏沒有存在啊,這就是 TDD 的一個概念了,先編寫一個預料之中的失敗,然後一步步的把失敗那部分改進到測試成功爲止。
TDD概念

TDD總體流程圖

編寫代碼跑通測試


新建一個 server 包,用於保存 v2ex 的業務邏輯代碼,下面是當前項目的結構:

v2ex
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
├── server
│   └── __init__.py
└── tests
    ├── test_basic.py
    └── test_user.py

爲了組織好模塊,會將多個模塊分爲包。簡單來說,包就是文件夾,但該文件夾下必須存在 __init__.py 文件, 然後可以在 __init__.py 文件中新增創建 Flask 實例的工廠函數,代碼如下所示:

from flask import Flask
from config import config


def create_app(config_name: str):
    """ 工廠函數,用於延遲創建 Flask 實例,可用於創建多個實例.

    :param config_name: 配置名稱,可根據開發環境、測試環境、生產環境區分
    :return: Flask 示例
    """
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    return app

然後運行 tests 文件夾下的 test_basic.py 文件,

$ python3 tests/test_basic.py 

Ran 1 test in 0.007s

FAILED (errors=1)

Error
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 59, in testPartExecutor
    yield
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 611, in run
    self.setUp()
  File "/Users/guoweikuang/project/github/v2ex/tests/test_basic.py", line 14, in setUp
    self.app = create_app('testing')
  File "/Users/guoweikuang/project/github/v2ex/server/__init__.py", line 17, in create_app
    app.config.from_object(config[config_name])
NameError: name 'config' is not defined

這還不能成功,因爲我們從最開始就說過,要使用最佳實踐方式來一步步實現網站,因此實例化 Flask 需要單獨存在一個配置模塊(config.py),目的就是把配置和其它功能區分開。
而且 TDD 的思想就是每次編寫最少量的代碼取得一些進展,再運行測試,如此不斷重複,直到測試成功爲止,最後可能還要重構代碼,測試能保證不破壞任何一個功能。

進一步完善

當前文件結構如下:

v2ex
├── LICENSE
├── Pipfile
├── Pipfile.lock
├── README.md
├── config.py
├── server
│   └── __init__.py
└── tests
    ├── test_basic.py
    └── test_user.py

前面一節已經知道測試失敗原因,這一節就編寫一個config.py 模塊使單元測試通過:

class Config(object):
    """ 配置基類,所有其它配置類都要繼承該類.
    """

    @staticmethod
    def init_app(app):
        pass


class DevelopmentConfig(Config):
    """ 開發環境配置 """
    DEBUG = True


class ProductionConfig(Config):
    """ 生產環境配置 """
    DEBUG = False


class TestingConfig(Config):
    """ 測試環境配置 """
    TESTING = True
    DEBUG = True


config = {
    'development': DevelopmentConfig,
    'product': ProductionConfig,
    'testing': TestingConfig,
    'default': DevelopmentConfig,
}

使用最少的代碼使測試成功,然後運行 test_basic.py 看看測試是否通過

$ python tests/test_basic.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

總結

從上面步驟可以總結到,先寫測試代碼,然後設法使測試一步步通過的 TDD 開發方式是十分有用的,保證了開發的功能符合預期的想法。當功能越來越複雜的時候,你可能修改了一些東西,導致另外一個功能的不可用,而單元測試也能保證了原有功能被破壞後能被檢測出來。這裏的代碼都可以到 v2ex
項目中查看,每一篇文章都對應着一個 tag, 想要查看每篇文章的修改可以切到對應標籤上查看。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章