<h1 id="test-driven-development-demo-with-pytest">Test Driven
Development Demo with PyTest</h1>
<h2 id="tdd">TDD</h2>
<ul>
<li>Discussed in hpr4075</li>
<li>Write a new test and run it. It should fail.</li>
<li>Write the minimal code that will pass the test</li>
<li>Optionally - refactor the code while ensure the tests continue to
pass</li>
</ul>
<h2 id="pytest">PyTest</h2>
<ul>
<li>Framework for writing software tests with python</li>
<li>Normally used to test python projects, but could test any software
that python can launch return input.</li>
<li>if you can write python, you can write tests in PyTest.</li>
<li>python assert - check that something is true</li>
</ul>
<h2 id="test-discovery">Test Discovery</h2>
<ul>
<li>Files named test*</li>
<li>Functions named test*</li>
</ul>
<h2 id="demo-project">Demo Project</h2>
<ul>
<li>Trivial app as a demo</li>
<li>Print a summary of the latest HPR Episode</li>
<li>Title, Host, Date, Audio File</li>
<li>How do we get the latest show data
<ul>
<li>RSS feed</li>
<li>Feed parser</li>
<li>Feed URL</li>
</ul></li>
</ul>
<h2 id="the-pytest-setup">The pytest setup</h2>
<ul>
<li>The python script we want to test will be named
<code>hpr_info.py</code></li>
<li>The test will be in a file will be named
<code>test_hpr_info.py</code></li>
</ul>
<p>test_hpr_info.py</p>
<pre class="py"><code>import hpr_info</code></pre>
<p>Run pytest</p>
<pre class="log"><code>ModuleNotFoundError: No module named &#39;hpr_info&#39;</code></pre>
<ul>
<li>We have written our first failing test.</li>
<li>The minimum code to get pytest to pass is to create an empty
file</li>
<li><code>touch hpr_info.py</code></li>
</ul>
<h3 id="run-pytest-again">Run pytest again</h3>
<pre class="txt"><code> pytest
============================= test session starts ==============================
platform linux -- Python 3.11.8, pytest-7.4.4, pluggy-1.4.0
rootdir: /tmp/Demo
collected 0 items</code></pre>
<h2 id="what-just-happened">What just happened</h2>
<ul>
<li>We created a file named <code>test_hpr_info.py</code> with a single
line to import hpr_info</li>
<li>We ran pytest and it failed because hpr_info.py did not exist</li>
<li>We created <code>hpr_info.py</code> and pytest ran without an
error.</li>
<li>This means we confirmed:
<ul>
<li>Pytest found the file named <code>test_hpr_info.py</code> and tried
to execute its tests</li>
<li>The import line is looking for a file named
<code>hpr_info.py</code></li>
</ul></li>
</ul>
<h2 id="python-assert">Python Assert</h2>
<ul>
<li>In python, <code>assert</code> tests if a statement is true</li>
<li>For example</li>
</ul>
<pre class="py"><code>asert 1==1</code></pre>
<p>In pytest, we can use assert to check a function returns a specific
value</p>
<pre class="py"><code>assert module.function() == &quot;Desired Output&quot;</code></pre>
<p>Without doing a comparison operator, we can also use assert to check
if something exists without specifying a specific value</p>
<pre class="py"><code>assert dictionary.key</code></pre>
<h2 id="adding-a-test">Adding a Test</h2>
<ul>
<li>Import hpr_info will allow us to test functions inside
<code>hpr_info.py</code></li>
<li>We can reference functions inside <code>hpr_info.py</code> by
prepending the name with <code>hpr_info.</code> for example</li>
</ul>
<pre class="py"><code>hpr_info.HPR_FEED</code></pre>
<ul>
<li>The first step in finding the latest HPR episode is fetching a copy
of the feed.</li>
<li>Lets add a test to make sure the HPR feed is defined</li>
</ul>
<pre class="py"><code>import hpr_info


def test_hpr_feed_url():
    assert hpr_info.HPR_FEED == &quot;https://hackerpublicradio.org/hpr_ogg_rss.php&quot;</code></pre>
<h2 id="pytest-again">pytest again</h2>
<ul>
<li>Lets run pytest again and we get the error
<code>AttributeError: module 'hpr_info' has no attribute 'HPR_FEED'</code></li>
<li>So lets add the just enough code <code>hpr_info.py</code> to get the
test to pass</li>
</ul>
<pre class="py"><code>HPR_FEED = &quot;https://hackerpublicradio.org/hpr_ogg_rss.php&quot;</code></pre>
<ul>
<li>Run pytest again and we get <code>1 passed</code> indicating the
pytest found 1 test which passed</li>
<li>Hooray, we are doing TDD</li>
</ul>
<h2 id="next-test---parsing-the-feed">Next Test - Parsing the feed</h2>
<ul>
<li>lets plan a function that pulls the HPR feed and returns the feed
data.</li>
<li>We can test that the result of fetching the feed is a HTTP 200</li>
</ul>
<pre class="py"><code>def test_get_show_data():
    show_data = hpr_info.get_show_data()
    assert show_data.status == 200</code></pre>
<ul>
<li>Now when we run pytest we get <code>1 failed, 1 passed</code> and we
can see the error
<code>AttributeError: module 'hpr_info' has no attribute 'get_show_data'</code></li>
<li>Lets write the code to get the new test to pass.</li>
<li>We will use the <code>feedparser</code> python module to make it
easier to parse the rss feed.</li>
<li>After we add the import and the new function,
<code>hpr_info.py</code> looks like this</li>
</ul>
<pre class="py"><code>import feedparser

HPR_FEED = &quot;https://hackerpublicradio.org/hpr_ogg_rss.php&quot;


def get_show_data():
    showdata = feedparser.parse(HPR_FEED)
    return showdata
</code></pre>
<hr />
<ul>
<li>Lets run pytest again. When I have more than one test, I like to add
the <code>-v</code> flag so I can see each test as it runs.</li>
</ul>
<pre><code>test_hpr_info.py::test_hpr_feed_url PASSED                                                                                               [ 50%]
test_hpr_info.py::test_get_show_data PASSED                                                                                              [100%]</code></pre>
<h2 id="next-test---get-the-most-recent-episode-from-the-feed">Next Test
- Get the most recent episode from the feed</h2>
<ul>
<li>Now that we have the feed, lets test getting the first episode.</li>
<li>feedparser entries are dictionaries.</li>
<li>Lets test what the function returns to make sure it looks like a rss
feed entry.</li>
</ul>
<pre class="py"><code>def test_get_latest_entry():
    latest_entry = hpr_info.get_latest_entry()
    assert latest_entry[&quot;title&quot;]
    assert latest_entry[&quot;published&quot;]</code></pre>
<hr />
<ul>
<li>After we verify the test fails, we can write the code to rerun the
newest entry data to <code>hpr_info.py</code> and <code>pytest -v</code>
will show 3 passing tests.</li>
</ul>
<pre class="py"><code>def get_latest_entry():
    showdata = get_show_data()
    return showdata[&quot;entries&quot;][0]</code></pre>
<h2 id="final-test">Final Test</h2>
<ul>
<li>Lets test a function to see if it returns the values we want to
print.</li>
<li>We don't test for specific values, just that the data exists.</li>
</ul>
<pre class="py"><code>def test_get_entry_data():
    entry_data = hpr_info.get_entry_data(hpr_info.get_latest_entry())
    assert entry_data[&quot;title&quot;]
    assert entry_data[&quot;host&quot;]
    assert entry_data[&quot;published&quot;]
    assert entry_data[&quot;file&quot;]</code></pre>
<hr />
<p>And then code to get the test to pass</p>
<pre class="py"><code>def get_entry_data(entry):
    for link in entry[&quot;links&quot;]:
        if link.get(&quot;rel&quot;) == &quot;enclosure&quot;:
            enclosure = link.get(&quot;href&quot;)

    return {
        &quot;title&quot;: entry[&quot;title&quot;],
        &quot;host&quot;: entry[&quot;authors&quot;][0][&quot;name&quot;],
        &quot;published&quot;: entry[&quot;published&quot;],
        &quot;file&quot;: enclosure,
    }</code></pre>
<h2 id="finish-the-hpr-info-script.">Finish the HPR info script.</h2>
<p>Now that we have tested that we can, get all the info we want from
the most recent episode lets add the last bit of code to
<code>hpr_info.py</code> to print the episode info</p>
<pre class="py"><code>if __name__ == &quot;__main__&quot;:
    most_recent_show = get_entry_data(get_latest_entry())
    print()
    print(f&quot;Most Recent HPR Episode&quot;)
    for x in most_recent_show:
        print(f&quot;{x}: {most_recent_show.get(x)}&quot;)</code></pre>
<p><code>if __name__ == "__main__":</code> ensures code inside this
block will only run when the script is called directly, and not when
imported by <code>test_hpr_info.py</code></p>
<h2 id="summary">Summary</h2>
<ul>
<li>TDD is a programming method where you write tests prior to writing
code.</li>
<li>TDD forces me to write smaller functions and more modular code.</li>
<li>Link to HPR info script and tests - TODO</li>
<li>Additional tests to add
<ul>
<li>Check date is the most recent weekday</li>
<li>Check this the host is listed on corespondents page</li>
<li>Check others.</li>
</ul></li>
<li>Project Files - https://gitlab.com/norrist/hpr-pytest-demo</li>
</ul>
