Testing Flask Applications(Flaskアプリケーションのテスト)

6495 ワード

公式文書の7章を起点とする.
# 7.2 The Testing Skeleton.
import os
import flaskr
import unittest
import tempfile

class FlaskTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
        flaskr.app.config['TESTING'] = True
        self.app = flaskr.app.test_client()
        with flaskr.app.app_context():
            flaskr.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(flaskr.app.config['DATABASE'])

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

次にselfを見てみましょうapp = flaskr.app.test_Client()このコードはいったい何をしているのか.
    # app.py
    def test_client(self, use_cookies=True, **kwargs):
        cls = self.test_client_class
        if cls is None:
            from flask.testing import FlaskClient as cls
        return cls(self, self.response_class, use_cookies=use_cookies, **kwargs)

デフォルトtest_client_classはNone.だからFlaskClient.FlaskClientはWerkzeugから継承されています.テストのClientですが、いくつかの方法が追加されています.with flaskr.app.app_context(): flaskr.init_db()のapp_context()メソッドは、AppContextにapp,url_が含まれていることを分析しました.adapter,g.作成したAppContextにg変数が含まれている以上flaskr.init_db()はスムーズに実行できます.
7.8節のKeeping the Context Aroundには、小さなセグメントコードがあります.
app = flask.Flask(__name__)

with app.test_client() as c:
    rv = c.get('/?tequila=42')
    assert request.args['tequila'] == '42'

flaskが「Working outside of request context.」ではなくrequestを正常に動作させる方法を見てみましょう.のエラーapp.test_request_context():...で、明らかなRequestContextがpushされています.でもapp.test_Client()は少し曲がって、そんなに明らかではありません.
    # testing.py
    def __enter__(self):
        if self.preserve_context:
            raise RuntimeError('Cannot nest client invocations')
        self.preserve_context = True
        return self

enter関数でself.preserve_ContextはTrueに設定.FlaskClientのインスタンスを返します.FlaskClientはWerkzeugのClientから継承する.
    # test.py in Werkzeug.
    def get(self, *args, **kw):
        """Like open but method is enforced to GET."""
        kw['method'] = 'GET'
        return self.open(*args, **kw)

c.get('/?tequila=42')を使用してgetメソッドを呼び出すと、実際にはreturn selfである.open(*args, **kw).
    def open(self, *args, **kwargs):
        kwargs.setdefault('environ_overrides', {}) \
            ['flask._preserve_context'] = self.preserve_context

        as_tuple = kwargs.pop('as_tuple', False)
        buffered = kwargs.pop('buffered', False)
        follow_redirects = kwargs.pop('follow_redirects', False)
        builder = make_test_environ_builder(self.application, *args, **kwargs)

        return Client.open(self, builder,
                           as_tuple=as_tuple,
                           buffered=buffered,
                           follow_redirects=follow_redirects)

FlaskClientはClientのopenメソッドを書き換えた.kwargsのflask._をpreserve_contextパラメータはselfに設定.preserve_つまりTrueです続いてkwargsがmake_に転送されますtest_environ_builder関数
def make_test_environ_builder(app, path='/', base_url=None, *args, **kwargs):
    http_host = app.config.get('SERVER_NAME')
    app_root = app.config.get('APPLICATION_ROOT')
    if base_url is None:
        url = url_parse(path)
        base_url = 'http://%s/' % (url.netloc or http_host or 'localhost')
        if app_root:
            base_url += app_root.lstrip('/')
        if url.netloc:
            path = url.path
            if url.query:
                path += '?' + url.query
    return EnvironBuilder(path, base_url, *args, **kwargs)

次にmakeを見てみましょうtest_environ_ビルダーのパラメータには、次のものがあります.
  • app=Flaskの例である.
  • path = '/?tequila=42'
  • base_url = None
  • args = ()
  • kwargs = {'method': 'GET', 'environ_overrides': {'flask._preserve_context': True}}

  • make_test_environ_ビルダーはEnvironBuilderに戻るEnvironBuilderに入力されたパラメータはbase_を除きます.urlが'に変わりますhttp://localhost他は変わっていない.EnvironBuilderを返します.例.次にClientを呼び出します.open(...),ClienでOpen()関数では、builderのkwargsの'flask._を主に見ます.preserve_context'はwsgiにどのように伝わりますか?appの中の.
        # test.py in Werkzeug.
        def open(self, *args, **kwargs):
            as_tuple = kwargs.pop('as_tuple', False)
            buffered = kwargs.pop('buffered', False)
            follow_redirects = kwargs.pop('follow_redirects', False)
            environ = None
            if not kwargs and len(args) == 1:
                if isinstance(args[0], EnvironBuilder):
                    #  builder environ.
                    environ = args[0].get_environ()  #

    self.run_wsgi_appでは、’flask.preserve_context'はenvironに含まれています.
    def run_wsgi_app(app, environ, buffered=False):
       
        environ = _get_environ(environ)
        response = []
        buffer = []
    
        ...
        app_rv = app(environ, start_response) # 

    今振り返ると、私たちはまたよく知っているFlaskに入りました.wsgi_app(...)の処理フローにおいて.
        def wsgi_app(self, environ, start_response):
      
            ctx = self.request_context(environ)
            ctx.push()
            ...
            finally:
                if self.should_ignore_error(error):
                    error = None
                ctx.auto_pop(error)  # 

    wsgi_appは最後に自動的にpop ctxする.次に、関連コードを詳しく分析します.
        def auto_pop(self, exc):
            if self.request.environ.get('flask._preserve_context') or \
               (exc is not None and self.app.preserve_context_on_exception):
                self.preserved = True
                self._preserved_exc = exc
            else:
                self.pop(exc)
    

    そこで真相が明らかになりました.原理auto_popの最初の行のコードはrequestを判断することである.Environに'flask._があるかどうかpreserve_context′変数は、他の2つの判断条件がデバッグに関連する可能性があり、一時的に無視する.それでpreserved = True. もちろんselfは実行する.pop(exc)です.実行しない結果、RequestContextはまだ存在します.request_ctx_stackの上には、もちろんrequestを正常に取得することができます.args['tequila']の値は、working outside of contextのエラーが発生しません.では、pop ctxはいつですか.答えはここにある.
        # testing.py
        def __exit__(self, exc_type, exc_value, tb):
            #  preserve-context False.
            self.preserve_context = False
    
            #  ctx,  ,  top.pop().
            top = _request_ctx_stack.top
            if top is not None and top.preserved:
                top.pop()
    

    これで、公式ドキュメント7.8節のコードの背後原理も明らかになった.