tdd in python with pytest

31

Click here to load reader

Upload: eddy-reyes

Post on 15-Jul-2015

650 views

Category:

Software


3 download

TRANSCRIPT

Page 2: TDD in Python With Pytest

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.

Page 3: TDD in Python With Pytest

Further ReadingUncle Bob Martin● http://blog.8thlight.com/uncle-bob/archive.htmlKent 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

Page 4: TDD in Python With Pytest

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!

Page 5: TDD in Python With Pytest

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

Page 6: TDD in Python With Pytest

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.

Page 7: TDD in Python With Pytest

Python TDD Tools● Standard library

○ unittest○ unittest.mock

■ As of Python 3.3● Nosetests● pytest

○ http://www.pytest.org

Page 8: TDD in Python With Pytest

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

Page 9: TDD in Python With Pytest

Pytest Fixture Examplefrom my_flask_app import create_app

@pytest.fixture

def app():

app = create_app("test")

return app

@pytest.fixture

def app_client(app):

client = app.test_client()

return client

# GIVEN: app_clientdef test_hello_route(app_client):

# WHEN:reply = app_client.get(“/hello”)

# THEN:assert reply.data == “Hello World”

Page 10: TDD in Python With Pytest

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

Page 11: TDD in Python With Pytest

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.

Page 12: TDD in Python With Pytest

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.

Page 13: TDD in Python With Pytest

Step 1: Start With A Testtest_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

Page 14: TDD in Python With Pytest

Step 2: Define Test Dependencies

test_hello.pyimport 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 appWHEN: I GET the hello/ routeTHEN: The response should be "hello world""""assert True

Page 15: TDD in Python With Pytest

Step 2 Cont’d

Page 16: TDD in Python With Pytest

Step 3: Add hello_app Module

hello_app.pyimport flask

app = flask.Flask(__name__)

Page 17: TDD in Python With Pytest

Step 4: Add Test For /hello Route

test_hello.pyimport 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 appWHEN: I GET the hello/ routeTHEN: The response should be "hello world""""response = test_client.get("/hello")assert response.data.decode("utf-8") == "hello world"

Page 18: TDD in Python With Pytest

Step 4 Cont’d

Page 19: TDD in Python With Pytest

Step 5: Add The /hello Route

hello_app.pyimport flask

app = flask.Flask(__name__)

@app.route("/hello")

def hello():

return "hello world"

Page 20: TDD in Python With Pytest

We’re Done!

Congratulations, you’ve just followed TDD to create a Flask hello world web application!

Page 21: TDD in Python With Pytest

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.

Page 22: TDD in Python With Pytest

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

Page 23: TDD in Python With Pytest

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

Page 24: TDD in Python With Pytest

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.

Page 25: TDD in Python With Pytest

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

Page 26: TDD in Python With Pytest

Step 6: Add a Test For The RouteMOCK_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

Page 27: TDD in Python With Pytest

Step 7: Add The Mock Fixtureclass MockEncodingResponse:

def __init__(self):

self.headers = {"Content-Encoding": MOCK_ENCODING}

def _mock_get(url):

assert url == "https://news.ycombinator.com"

return MockEncodingResponse()

@pytest.fixture

def mock_encoding_request(monkeypatch):

monkeypatch.setattr("requests.get", _mock_get)

Page 28: TDD in Python With Pytest

Step 7 Cont’d

Page 29: TDD in Python With Pytest

Step 8: Add The New Routehello_app.pyimport flask

import requests

app = flask.Flask(__name__)

@app.route("/hello")

def hello():

return "hello world"

@app.route("/hacker_news_encoding")

def hacker_news_encoding():

url = "https://news.ycombinator.com"

resp = requests.get(url)

return response.headers["Content-Encoding"]

Page 30: TDD in Python With Pytest

Step 8 Cont’d

Page 31: TDD in Python With Pytest

Want The Code?

Fork me on Github!

https://github.com/ereyes01/apug-pytest-prez