[pre-commit.ci lite] apply automatic fixes
This commit is contained in:
parent
b3ae3117f9
commit
3d83d8138c
102 changed files with 26790 additions and 26749 deletions
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Keep Developing! — Flask Documentation (3.2.x)</title>
|
||||
<title>Test Coverage — Flask Documentation (3.2.x)</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=6625fa76" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/flask.css?v=b87c8d14" />
|
||||
<script src="../_static/documentation_options.js?v=56528222"></script>
|
||||
|
|
@ -15,8 +15,8 @@
|
|||
<link rel="icon" href="../_static/shortcut-icon.png"/>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Templates" href="../templating.html" />
|
||||
<link rel="prev" title="Deploy to Production" href="deploy.html" />
|
||||
<link rel="next" title="Deploy to Production" href="deploy.html" />
|
||||
<link rel="prev" title="Make the Project Installable" href="install.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
|
|
@ -28,52 +28,537 @@
|
|||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="../templating.html" title="Templates"
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
<a href="install.html" title="Make the Project Installable"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Keep Developing!</a></li>
|
||||
<li class="nav-item nav-item-this"><a href="">Test Coverage</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="keep-developing">
|
||||
<h1>Keep Developing!<a class="headerlink" href="#keep-developing" title="Link to this heading">¶</a></h1>
|
||||
<p>You’ve learned about quite a few Flask and Python concepts throughout
|
||||
the tutorial. Go back and review the tutorial and compare your code with
|
||||
the steps you took to get there. Compare your project to the
|
||||
<a class="reference external" href="https://github.com/pallets/flask/tree/main/examples/tutorial">example project</a>, which might look a bit
|
||||
different due to the step-by-step nature of the tutorial.</p>
|
||||
<p>There’s a lot more to Flask than what you’ve seen so far. Even so,
|
||||
you’re now equipped to start developing your own web applications. Check
|
||||
out the <a class="reference internal" href="../quickstart.html"><span class="doc">Quickstart</span></a> for an overview of what Flask can do, then
|
||||
dive into the docs to keep learning. Flask uses <a class="reference external" href="https://palletsprojects.com/p/jinja/">Jinja</a>, <a class="reference external" href="https://palletsprojects.com/p/click/">Click</a>,
|
||||
<a class="reference external" href="https://palletsprojects.com/p/werkzeug/">Werkzeug</a>, and <a class="reference external" href="https://palletsprojects.com/p/itsdangerous/">ItsDangerous</a> behind the scenes, and they all have
|
||||
their own documentation too. You’ll also be interested in
|
||||
<a class="reference internal" href="../extensions.html"><span class="doc">Extensions</span></a> which make tasks like working with the database or
|
||||
validating form data easier and more powerful.</p>
|
||||
<p>If you want to keep developing your Flaskr project, here are some ideas
|
||||
for what to try next:</p>
|
||||
<ul class="simple">
|
||||
<li><p>A detail view to show a single post. Click a post’s title to go to
|
||||
its page.</p></li>
|
||||
<li><p>Like / unlike a post.</p></li>
|
||||
<li><p>Comments.</p></li>
|
||||
<li><p>Tags. Clicking a tag shows all the posts with that tag.</p></li>
|
||||
<li><p>A search box that filters the index page by name.</p></li>
|
||||
<li><p>Paged display. Only show 5 posts per page.</p></li>
|
||||
<li><p>Upload an image to go along with a post.</p></li>
|
||||
<li><p>Format posts using Markdown.</p></li>
|
||||
<li><p>An RSS feed of new posts.</p></li>
|
||||
</ul>
|
||||
<p>Have fun and make awesome applications!</p>
|
||||
|
||||
<section id="test-coverage">
|
||||
<h1>Test Coverage<a class="headerlink" href="#test-coverage" title="Link to this heading">¶</a></h1>
|
||||
<p>Writing unit tests for your application lets you check that the code
|
||||
you wrote works the way you expect. Flask provides a test client that
|
||||
simulates requests to the application and returns the response data.</p>
|
||||
<p>You should test as much of your code as possible. Code in functions only
|
||||
runs when the function is called, and code in branches, such as <code class="docutils literal notranslate"><span class="pre">if</span></code>
|
||||
blocks, only runs when the condition is met. You want to make sure that
|
||||
each function is tested with data that covers each branch.</p>
|
||||
<p>The closer you get to 100% coverage, the more comfortable you can be
|
||||
that making a change won’t unexpectedly change other behavior. However,
|
||||
100% coverage doesn’t guarantee that your application doesn’t have bugs.
|
||||
In particular, it doesn’t test how the user interacts with the
|
||||
application in the browser. Despite this, test coverage is an important
|
||||
tool to use during development.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should test as you develop.</p>
|
||||
</div>
|
||||
<p>You’ll use <a class="reference external" href="https://pytest.readthedocs.io/">pytest</a> and <a class="reference external" href="https://coverage.readthedocs.io/">coverage</a> to test and measure your code.
|
||||
Install them both:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install pytest coverage
|
||||
</pre></div>
|
||||
</div>
|
||||
<section id="setup-and-fixtures">
|
||||
<h2>Setup and Fixtures<a class="headerlink" href="#setup-and-fixtures" title="Link to this heading">¶</a></h2>
|
||||
<p>The test code is located in the <code class="docutils literal notranslate"><span class="pre">tests</span></code> directory. This directory is
|
||||
<em>next to</em> the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package, not inside it. The
|
||||
<code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code> file contains setup functions called <em>fixtures</em>
|
||||
that each test will use. Tests are in Python modules that start with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>, and each test function in those modules also starts with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>.</p>
|
||||
<p>Each test will create a new temporary database file and populate some
|
||||
data that will be used in the tests. Write a SQL file to insert that
|
||||
data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/data.sql</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-sql notranslate"><div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'</span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'other'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'</span><span class="p">);</span>
|
||||
|
||||
<span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">body</span><span class="p">,</span><span class="w"> </span><span class="n">author_id</span><span class="p">,</span><span class="w"> </span><span class="n">created</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test title'</span><span class="p">,</span><span class="w"> </span><span class="s1">'test'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">x</span><span class="s1">'0a'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">'body'</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'2018-01-01 00:00:00'</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture will call the factory and pass <code class="docutils literal notranslate"><span class="pre">test_config</span></code> to
|
||||
configure the application and database for testing instead of using your
|
||||
local development configuration.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">tempfile</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span><span class="p">,</span> <span class="n">init_db</span>
|
||||
|
||||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span> <span class="s1">'data.sql'</span><span class="p">),</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="n">_data_sql</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">app</span><span class="p">():</span>
|
||||
<span class="n">db_fd</span><span class="p">,</span> <span class="n">db_path</span> <span class="o">=</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">mkstemp</span><span class="p">()</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">({</span>
|
||||
<span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="s1">'DATABASE'</span><span class="p">:</span> <span class="n">db_path</span><span class="p">,</span>
|
||||
<span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">init_db</span><span class="p">()</span>
|
||||
<span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">_data_sql</span><span class="p">)</span>
|
||||
|
||||
<span class="k">yield</span> <span class="n">app</span>
|
||||
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">db_fd</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">client</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_client</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">runner</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_cli_runner</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">tempfile.mkstemp()</span></code></a> creates and opens a temporary file, returning
|
||||
the file descriptor and the path to it. The <code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> path is
|
||||
overridden so it points to this temporary path instead of the instance
|
||||
folder. After setting the path, the database tables are created and the
|
||||
test data is inserted. After the test is over, the temporary file is
|
||||
closed and removed.</p>
|
||||
<p><a class="reference internal" href="../config.html#TESTING" title="TESTING"><code class="xref py py-data docutils literal notranslate"><span class="pre">TESTING</span></code></a> tells Flask that the app is in test mode. Flask changes
|
||||
some internal behavior so it’s easier to test, and other extensions can
|
||||
also use the flag to make testing them easier.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture calls
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_client" title="flask.Flask.test_client"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_client()</span></code></a> with the application
|
||||
object created by the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture. Tests will use the client to make
|
||||
requests to the application without running the server.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture is similar to <code class="docutils literal notranslate"><span class="pre">client</span></code>.
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_cli_runner" title="flask.Flask.test_cli_runner"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_cli_runner()</span></code></a> creates a runner
|
||||
that can call the Click commands registered with the application.</p>
|
||||
<p>Pytest uses fixtures by matching their function names with the names
|
||||
of arguments in the test functions. For example, the <code class="docutils literal notranslate"><span class="pre">test_hello</span></code>
|
||||
function you’ll write next takes a <code class="docutils literal notranslate"><span class="pre">client</span></code> argument. Pytest matches
|
||||
that with the <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture function, calls it, and passes the
|
||||
returned value to the test function.</p>
|
||||
</section>
|
||||
<section id="factory">
|
||||
<h2>Factory<a class="headerlink" href="#factory" title="Link to this heading">¶</a></h2>
|
||||
<p>There’s not much to test about the factory itself. Most of the code will
|
||||
be executed for each test already, so if something fails the other tests
|
||||
will notice.</p>
|
||||
<p>The only behavior that can change is passing test config. If config is
|
||||
not passed, there should be some default configuration, otherwise the
|
||||
configuration should be overridden.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_factory.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_config</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">create_app</span><span class="p">()</span><span class="o">.</span><span class="n">testing</span>
|
||||
<span class="k">assert</span> <span class="n">create_app</span><span class="p">({</span><span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span><span class="o">.</span><span class="n">testing</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'Hello, World!'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>You added the <code class="docutils literal notranslate"><span class="pre">hello</span></code> route as an example when writing the factory at
|
||||
the beginning of the tutorial. It returns “Hello, World!”, so the test
|
||||
checks that the response data matches.</p>
|
||||
</section>
|
||||
<section id="database">
|
||||
<h2>Database<a class="headerlink" href="#database" title="Link to this heading">¶</a></h2>
|
||||
<p>Within an application context, <code class="docutils literal notranslate"><span class="pre">get_db</span></code> should return the same
|
||||
connection each time it’s called. After the context, the connection
|
||||
should be closed.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">sqlite3</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_get_close_db</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">db</span> <span class="ow">is</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">ProgrammingError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT 1'</span><span class="p">)</span>
|
||||
|
||||
<span class="k">assert</span> <span class="s1">'closed'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">init-db</span></code> command should call the <code class="docutils literal notranslate"><span class="pre">init_db</span></code> function and output
|
||||
a message.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_init_db_command</span><span class="p">(</span><span class="n">runner</span><span class="p">,</span> <span class="n">monkeypatch</span><span class="p">):</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Recorder</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="n">called</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">fake_init_db</span><span class="p">():</span>
|
||||
<span class="n">Recorder</span><span class="o">.</span><span class="n">called</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="s1">'flaskr.db.init_db'</span><span class="p">,</span> <span class="n">fake_init_db</span><span class="p">)</span>
|
||||
<span class="n">result</span> <span class="o">=</span> <span class="n">runner</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s1">'init-db'</span><span class="p">])</span>
|
||||
<span class="k">assert</span> <span class="s1">'Initialized'</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">output</span>
|
||||
<span class="k">assert</span> <span class="n">Recorder</span><span class="o">.</span><span class="n">called</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This test uses Pytest’s <code class="docutils literal notranslate"><span class="pre">monkeypatch</span></code> fixture to replace the
|
||||
<code class="docutils literal notranslate"><span class="pre">init_db</span></code> function with one that records that it’s been called. The
|
||||
<code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture you wrote above is used to call the <code class="docutils literal notranslate"><span class="pre">init-db</span></code>
|
||||
command by name.</p>
|
||||
</section>
|
||||
<section id="authentication">
|
||||
<h2>Authentication<a class="headerlink" href="#authentication" title="Link to this heading">¶</a></h2>
|
||||
<p>For most of the views, a user needs to be logged in. The easiest way to
|
||||
do this in tests is to make a <code class="docutils literal notranslate"><span class="pre">POST</span></code> request to the <code class="docutils literal notranslate"><span class="pre">login</span></code> view
|
||||
with the client. Rather than writing that out every time, you can write
|
||||
a class with methods to do that, and use a fixture to pass it the client
|
||||
for each test.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">AuthActions</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_client</span> <span class="o">=</span> <span class="n">client</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="o">=</span><span class="s1">'test'</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'test'</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/login'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/logout'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">auth</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">AuthActions</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture, you can call <code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> in a test to
|
||||
log in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user, which was inserted as part of the test
|
||||
data in the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">register</span></code> view should render successfully on <code class="docutils literal notranslate"><span class="pre">GET</span></code>. On <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
with valid form data, it should redirect to the login URL and the user’s
|
||||
data should be in the database. Invalid data should display error
|
||||
messages.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">g</span><span class="p">,</span> <span class="n">session</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/register'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT * FROM user WHERE username = 'a'"</span><span class="p">,</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Username is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Password is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'already registered'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register_validate_input</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.get" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.get()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request
|
||||
and returns the <a class="reference internal" href="../api.html#flask.Response" title="flask.Response"><code class="xref py py-class docutils literal notranslate"><span class="pre">Response</span></code></a> object returned by Flask. Similarly,
|
||||
<a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.post" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.post()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
request, converting the <code class="docutils literal notranslate"><span class="pre">data</span></code> dict into form data.</p>
|
||||
<p>To test that the page renders successfully, a simple request is made and
|
||||
checked for a <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> <a class="reference internal" href="../api.html#flask.Response.status_code" title="flask.Response.status_code"><code class="xref py py-attr docutils literal notranslate"><span class="pre">status_code</span></code></a>. If
|
||||
rendering failed, Flask would return a <code class="docutils literal notranslate"><span class="pre">500</span> <span class="pre">Internal</span> <span class="pre">Server</span> <span class="pre">Error</span></code>
|
||||
code.</p>
|
||||
<p><code class="xref py py-attr docutils literal notranslate"><span class="pre">headers</span></code> will have a <code class="docutils literal notranslate"><span class="pre">Location</span></code> header with the login
|
||||
URL when the register view redirects to the login view.</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.Response.data" title="flask.Response.data"><code class="xref py py-attr docutils literal notranslate"><span class="pre">data</span></code></a> contains the body of the response as bytes. If
|
||||
you expect a certain value to render on the page, check that it’s in
|
||||
<code class="docutils literal notranslate"><span class="pre">data</span></code>. Bytes must be compared to bytes. If you want to compare text,
|
||||
use <a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/wrappers/#werkzeug.wrappers.Response.get_data" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">get_data(as_text=True)</span></code></a>
|
||||
instead.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">pytest.mark.parametrize</span></code> tells Pytest to run the same test function
|
||||
with different arguments. You use it here to test different invalid
|
||||
input and error messages without writing the same code three times.</p>
|
||||
<p>The tests for the <code class="docutils literal notranslate"><span class="pre">login</span></code> view are very similar to those for
|
||||
<code class="docutils literal notranslate"><span class="pre">register</span></code>. Rather than testing the data in the database,
|
||||
<a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should have <code class="docutils literal notranslate"><span class="pre">user_id</span></code> set after logging in.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id8">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id8" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_login</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/login'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span>
|
||||
<span class="k">assert</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'test'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect username.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect password.'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_validate_input</span><span class="p">(</span><span class="n">auth</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Using <code class="docutils literal notranslate"><span class="pre">client</span></code> in a <code class="docutils literal notranslate"><span class="pre">with</span></code> block allows accessing context variables
|
||||
such as <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> after the response is returned. Normally,
|
||||
accessing <code class="docutils literal notranslate"><span class="pre">session</span></code> outside of a request would raise an error.</p>
|
||||
<p>Testing <code class="docutils literal notranslate"><span class="pre">logout</span></code> is the opposite of <code class="docutils literal notranslate"><span class="pre">login</span></code>. <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should
|
||||
not contain <code class="docutils literal notranslate"><span class="pre">user_id</span></code> after logging out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id9">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id9" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_logout</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">logout</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="s1">'user_id'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">session</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="blog">
|
||||
<h2>Blog<a class="headerlink" href="#blog" title="Link to this heading">¶</a></h2>
|
||||
<p>All the blog views use the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture you wrote earlier. Call
|
||||
<code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> and subsequent requests from the client will be logged
|
||||
in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">index</span></code> view should display information about the post that was
|
||||
added with the test data. When logged in as the author, there should be
|
||||
a link to edit the post.</p>
|
||||
<p>You can also test some more authentication behavior while testing the
|
||||
<code class="docutils literal notranslate"><span class="pre">index</span></code> view. When not logged in, each page shows links to log in or
|
||||
register. When logged in, there’s a link to log out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id10">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id10" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_index</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Log In"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Register"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Log Out'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test title'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'by test on 2018-01-01'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test</span><span class="se">\n</span><span class="s1">body'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>A user must be logged in to access the <code class="docutils literal notranslate"><span class="pre">create</span></code>, <code class="docutils literal notranslate"><span class="pre">update</span></code>, and
|
||||
<code class="docutils literal notranslate"><span class="pre">delete</span></code> views. The logged in user must be the author of the post to
|
||||
access <code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code>, otherwise a <code class="docutils literal notranslate"><span class="pre">403</span> <span class="pre">Forbidden</span></code> status
|
||||
is returned. If a <code class="docutils literal notranslate"><span class="pre">post</span></code> with the given <code class="docutils literal notranslate"><span class="pre">id</span></code> doesn’t exist,
|
||||
<code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code> should return <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span> <span class="pre">Found</span></code>.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id11">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id11" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_author_required</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="c1"># change the post author to another user</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'UPDATE post SET author_id = 2 WHERE id = 1'</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="c1"># current user can't modify other user's post</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="c1"># current user doesn't see edit link</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/2/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/2/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_exists_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> and <code class="docutils literal notranslate"><span class="pre">update</span></code> views should render and return a
|
||||
<code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> status for a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request. When valid data is sent in a
|
||||
<code class="docutils literal notranslate"><span class="pre">POST</span></code> request, <code class="docutils literal notranslate"><span class="pre">create</span></code> should insert the new post data into the
|
||||
database, and <code class="docutils literal notranslate"><span class="pre">update</span></code> should modify the existing data. Both pages
|
||||
should show an error message on invalid data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id12">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id12" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_create</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'created'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT COUNT(id) FROM post'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">assert</span> <span class="n">count</span> <span class="o">==</span> <span class="mi">2</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_update</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'updated'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'updated'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_create_update_validate</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Title is required.'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">delete</span></code> view should redirect to the index URL and the post should
|
||||
no longer exist in the database.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id13">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id13" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_delete</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span> <span class="ow">is</span> <span class="kc">None</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="running-the-tests">
|
||||
<h2>Running the Tests<a class="headerlink" href="#running-the-tests" title="Link to this heading">¶</a></h2>
|
||||
<p>Some extra configuration, which is not required but makes running tests with coverage
|
||||
less verbose, can be added to the project’s <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id14">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id14" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[tool.pytest.ini_options]</span>
|
||||
<span class="n">testpaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"tests"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">[tool.coverage.run]</span>
|
||||
<span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flaskr"</span><span class="p">]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>To run the tests, use the <code class="docutils literal notranslate"><span class="pre">pytest</span></code> command. It will find and run all
|
||||
the test functions you’ve written.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pytest
|
||||
|
||||
========================= test session starts ==========================
|
||||
platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
|
||||
rootdir: /home/user/Projects/flask-tutorial
|
||||
collected 23 items
|
||||
|
||||
tests/test_auth.py ........ [ 34%]
|
||||
tests/test_blog.py ............ [ 86%]
|
||||
tests/test_db.py .. [ 95%]
|
||||
tests/test_factory.py .. [100%]
|
||||
|
||||
====================== 24 passed in 0.64 seconds =======================
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If any tests fail, pytest will show the error that was raised. You can
|
||||
run <code class="docutils literal notranslate"><span class="pre">pytest</span> <span class="pre">-v</span></code> to get a list of each test function rather than dots.</p>
|
||||
<p>To measure the code coverage of your tests, use the <code class="docutils literal notranslate"><span class="pre">coverage</span></code> command
|
||||
to run pytest instead of running it directly.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage run -m pytest
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can either view a simple coverage report in the terminal:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage report
|
||||
|
||||
Name Stmts Miss Branch BrPart Cover
|
||||
------------------------------------------------------
|
||||
flaskr/__init__.py 21 0 2 0 100%
|
||||
flaskr/auth.py 54 0 22 0 100%
|
||||
flaskr/blog.py 54 0 16 0 100%
|
||||
flaskr/db.py 24 0 4 0 100%
|
||||
------------------------------------------------------
|
||||
TOTAL 153 0 44 0 100%
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>An HTML report allows you to see which lines were covered in each file:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage html
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This generates files in the <code class="docutils literal notranslate"><span class="pre">htmlcov</span></code> directory. Open
|
||||
<code class="docutils literal notranslate"><span class="pre">htmlcov/index.html</span></code> in your browser to see the report.</p>
|
||||
<p>Continue to <a class="reference internal" href="deploy.html"><span class="doc">Deploy to Production</span></a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
|
@ -84,20 +569,33 @@ its page.</p></li>
|
|||
<span id="sidebar-top"></span>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
|
||||
|
||||
|
||||
|
||||
<p class="logo"><a href="../index.html">
|
||||
<img class="logo" src="../_static/flask-vertical.png" alt="Logo of Flask"/>
|
||||
</a></p>
|
||||
|
||||
|
||||
|
||||
<h3>Contents</h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Test Coverage</a><ul>
|
||||
<li><a class="reference internal" href="#setup-and-fixtures">Setup and Fixtures</a></li>
|
||||
<li><a class="reference internal" href="#factory">Factory</a></li>
|
||||
<li><a class="reference internal" href="#database">Database</a></li>
|
||||
<li><a class="reference internal" href="#authentication">Authentication</a></li>
|
||||
<li><a class="reference internal" href="#blog">Blog</a></li>
|
||||
<li><a class="reference internal" href="#running-the-tests">Running the Tests</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="deploy.html" title="previous chapter">Deploy to Production</a>
|
||||
<li>Next: <a href="../templating.html" title="next chapter">Templates</a></ul>
|
||||
<li>Previous: <a href="install.html" title="previous chapter">Make the Project Installable</a>
|
||||
<li>Next: <a href="deploy.html" title="next chapter">Deploy to Production</a></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
|
@ -121,4 +619,4 @@ its page.</p></li>
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue