This presentation walks the reader through implementing a simple web application and its tests using Python, Flask, and the Pytest testing framework. Emphasis is placed on following the process of test-driven development (TDD) in creating the application.
1. TDD in Python With
Pytest
Eddy Reyes
eddy@tasqr.io
http://www.tasqr.io
2. Overview
● High-level discussion of TDD
● TDD walk-through with pytest
● Mocking with pytest
Not looking to proselytize TDD in this
presentation
● I’m just presenting the concepts and method.
3. Further Reading
Uncle Bob Martin
● http://blog.8thlight.com/uncle-bob/archive.html
Kent Beck
● Extreme Programming Explained (book)
Is TDD Dead? video series
● https://www.youtube.com/watch?v=z9quxZsLcfo (part 1)
Code From This Presentation
● https://github.com/ereyes01/apug-pytest-prez
4. What Is TDD?
● Methodology of Implementing Software
● It is NOT a silver bullet!
● It is NOT the only way to write good
software!
○ But, if followed, will help you write solid software!
5. Effective TDD
● TDD Method- To change a Program:
○ Write a unit test, watch it fail
○ Change the code to make the test pass
○ Rinse, repeat...
● Unit tests are effective when they are self-contained
○ No external dependencies
● TDD is not for prototyping!
○ Use when you fully understand your design and how
to code your solution
6. Anatomy of a Test
● Given… precondition
● When… X happens
● Then… Y must be true
Tests == Formal Design Spec
● Make your tests as readable as you would a (formal)
specification document.
7. Python TDD Tools
● Standard library
○ unittest
○ unittest.mock
■ As of Python 3.3
● Nosetests
● pytest
○ http://www.pytest.org
8. Testing With Pytest
● No classes needed!
● Fully takes advantage of Python’s dynamism to help
you design beautiful tests.
● Use plain Python assert instead of Xunit jargon
● Fixtures
○ Makes it easy to define your test preconditions
○ Fixtures can be nested arbitrarily, allowing complex
dependency trees
○ Cleanup is handled gracefully
10. Easy Test Dependencies
● Fixtures allow arbitrarily nested test
dependencies, eliminate DRY in your tests!
○ Compare with unittest... fixtures look like:
class TestSomething(unittest.TestCase):
def setUp():
# fixture code here
def tearDown():
# cleanup fixture here
def testSomething():
# test case code here
11. Example: TDD and Flask Hello World
● Let’s walk through how we would implement
the Flask Hello World example using TDD.
○ http://flask.pocoo.org
● Requirements:
○ Need a Flask app
○ Must reply the text “hello world” to a GET of the
“/hello” route.
12. Need to Experiment?
● Not yet sure how to build this?
○ Stop your TDD!
○ Play, read docs learn, experiment…
○ Build a prototype if you like
…
● Do NOT commit that code!
○ TDD is not for learning… it’s for executing on
something you already know how to build.
13. Step 1: Start With A Test
test_hello.py:
def test_hello():
"""
GIVEN: A flask hello app
WHEN: I GET the hello/ route
THEN: The response should be "hello world"
"""
assert True
14. Step 2: Define Test Dependencies
test_hello.py
import pytest
import hello_app
@pytest.fixture
def app():
return hello_app.app
@pytest.fixture
def test_client(app):
return app.test_client()
def test_hello(test_client):
"""
GIVEN: A flask hello app
WHEN: I GET the hello/ route
THEN: The response should be "hello world"
"""
assert True
21. Real Life is Never That Simple!
● Of course it’s not
● Applications connect to the network,
● Use databases,
● Do I/O on enormous files,
● etc.
22. Mocking The Edges Of Your App
● Mocks are a testing technique to stub out the
“edges” of your application
○ “Edges” == external components
● You don’t want to test external components
out of your control
○ Network
○ Database
○ Large Files
23. Mocking with Pytest’s monkeypatch
● Pytest defines a special fixture called monkeypatch
● Allows arbitrary setattr on anything in your tested
code’s namespace
● Example:
def test_unknown_file(monkeypatch):
monkeypatch.setattr("os.path.islink", lambda x: False)
monkeypatch.setattr("os.path.isdir", lambda x: False)
mylib.check_file("/some/path" )
● Monkeypatched symbols are restored in test cleanup
24. Mocks as Your Own Fixtures
● monkeypatch can be nested within your own fixtures to
define high-level dependencies
● Helps you write clean test code with mocks that follows
the pattern of Given-When-Then
● Mocks help your application code remain separate from
your testing mechanisms.
25. Let’s Extend Our Flask Example
● We will add a new route:
○ /hacker_news_encoding
○ This route returns the “Content-Encoding” header
value returned by the Hacker News site
● We can’t directly test Hacker News
○ Site could change
○ Site could be down
○ Unreliable test results
26. Step 6: Add a Test For The Route
MOCK_ENCODING = “mock-encoding”
def test_encoding_header(test_client, mock_encoding_request ):
"""
GIVEN: A flask hello app
A mock request handler
WHEN: I GET the /hacker_news_encoding route
THEN: The response should be the expected Content-Encoding
"""
response = test_client.get("/hacker_news_encoding")
assert response.data.decode("utf-8") == MOCK_ENCODING