Test-Driven Development with Python, 3rd Edition

Book description

The third edition of this trusted guide demonstrates the practical advantages of test-driven development (TDD) with Python and describes how to develop a real web application. You'll learn how to write and run tests before building each part of your app and then develop the minimum amount of code required to pass those tests. The result? Clean code that works.

In the process, author Harry Percival teaches software and web developers the basics of Django, Selenium, Git, JavaScript, and Mock libraries, along with current web development techniques. This book—updated for Python 3.11 and Django 4—clearly demonstrates how TDD encourages simple designs and inspires confidence.

Fully updated, this third edition addresses:

  • The TDD workflow, including the unit test/code cycle and refactoring
  • Unit tests for classes and functions and functional tests for user interactions within the browser
  • Mock objects and the pros and cons of isolated versus integrated tests
  • Testing and automation of deployments with a staging server
  • Tests applied to the third-party plug-ins you integrate into your site
  • Automatic tests using a continuous integration environment
  • Using TDD to build a REST API with a JavaScript frontend interface

Publisher resources

View/Submit Errata

Table of contents

  1. Brief Table of Contents (Not Yet Final)
  2. Preface
    1. Why I Wrote a Book About Test-Driven Development
    2. Aims of This Book
    3. Outline
    4. Conventions Used in This Book
    5. Submitting Errata
    6. Using Code Examples
    7. O’Reilly Online Learning
    8. How to Contact Us
    9. License for the free edition
  3. Prerequisites and Assumptions
    1. Python 3 and Programming
    2. How HTML Works
    3. Django
    4. JavaScript
    5. Required Software Installations
      1. Installing Firefox
    6. Setting Up Your Virtualenv
      1. Activating and Deactivating the Virtualenv
      2. Installing Django and Selenium
      3. Some Error Messages You’re Likely to See When You Inevitably Fail to Activate Your Virtualenv
  4. Companion Video
  5. I. The Basics of TDD and Django
  6. 1. Getting Django Set Up Using a Functional Test
    1. Obey the Testing Goat! Do Nothing Until You Have a Test
    2. Getting Django Up and Running
    3. Starting a Git Repository
  7. 2. Extending Our Functional Test Using the unittest Module
    1. Using a Functional Test to Scope Out a Minimum Viable App
    2. The Python Standard Library’s unittest Module
    3. Commit
  8. 3. Testing a Simple Home Page with Unit Tests
    1. Our First Django App, and Our First Unit Test
    2. Unit Tests, and How They Differ from Functional Tests
    3. Unit Testing in Django
    4. Django’s MVC, URLs, and View Functions
    5. Unit Testing a View
      1. At Last! We Actually Write Some Application Code!
      2. The Unit-Test/Code Cycle
    6. Our functional tests tell us we’re not quite done yet.
    7. Reading Tracebacks
    8. urls.py
  9. 4. What Are We Doing with All These Tests? (And, Refactoring)
    1. Programming Is Like Pulling a Bucket of Water Up from a Well
    2. Using Selenium to Test User Interactions
    3. The “Don’t Test Constants” Rule, and Templates to the Rescue
      1. Refactoring to Use a Template
      2. Checking template rendering
    4. On Refactoring
    5. A Little More of Our Front Page
    6. Recap: The TDD Process
      1. Double-loop TDD
  10. 5. Saving User Input: Testing the Database
    1. Wiring Up Our Form to Send a POST Request
    2. Debugging functional tests
    3. Processing a POST Request on the Server
    4. Passing Python Variables to Be Rendered in the Template
      1. An Unexpected Failure
    5. Three Strikes and Refactor
    6. The Django ORM and Our First Model
      1. Our First Database Migration
      2. The Test Gets Surprisingly Far
      3. A New Field Means a New Migration
    7. Saving the POST to the Database
    8. Redirect After a POST
    9. Better Unit Testing Practice: Each Test Should Test One Thing
    10. Rendering Items in the Template
    11. Creating Our Production Database with migrate
    12. Recap
  11. 6. Improving Functional Tests: Ensuring Isolation and Removing Voodoo Sleeps
    1. Ensuring Test Isolation in Functional Tests
      1. Running Just the Unit Tests
    2. Aside: Upgrading Selenium and Geckodriver
    3. On Implicit and Explicit Waits, and Voodoo time.sleeps
  12. 7. Working Incrementally
    1. Small Design When Necessary
      1. Not Big Design Up Front
      2. YAGNI!
      3. REST (ish)
    2. Implementing the New Design Incrementally Using TDD
    3. Ensuring We Have a Regression Test
    4. Iterating Towards the New Design
    5. Taking a First, Self-Contained Step: One New URL
      1. Separating out our home page and list view functionality
      2. The FTs detect a regression
      3. Getting Back to a Working State as Quickly as Possible
      4. Green? Refactor
    6. Another Small Step: A Separate Template for Viewing Lists
    7. A Third Small Step: A New URL for Adding List Items
      1. A Test Class for New List Creation
      2. A URL and View for New List Creation
      3. Removing Now-Redundant Code and Tests
      4. A Regression! Pointing Our Forms at the New URL
    8. Biting the Bullet: Adjusting Our Models
      1. A Foreign Key Relationship
      2. Adjusting the Rest of the World to Our New Models
    9. Each List Should Have Its Own URL
      1. Capturing Parameters from URLs
      2. Adjusting new_list to the New World
    10. The Functional Tests Detect Another Regression
    11. One More View to Handle Adding Items to an Existing List
      1. The Last New URL
      2. The Last New View
      3. Testing Template Context Directly
    12. A Final Refactor Using URL includes
  13. II. Web Development Sine Qua Nons
  14. 8. Prettification: Layout and Styling, and What to Test About It
    1. Testing Layout and Style
    2. Prettification: Using a CSS Framework
    3. Django Template Inheritance
    4. Integrating Bootstrap
      1. Rows and Columns
    5. Static Files in Django
      1. Switching to StaticLiveServerTestCase
    6. Using Bootstrap Components to Improve the Look of the Site
      1. Jumbotron!
      2. Large Inputs
      3. Table Styling
      4. Dark Modeeeeeee
      5. A semi-decent page
    7. What We Glossed Over: collectstatic and Other Static Directories
    8. A Few Things That Didn’t Make It
  15. 9. Containerization aka Docker
    1. TDD and the Danger Areas of Deployment
    2. Docker, Containers and Virtualization
      1. Why not just use a virtualenv?
      2. Docker and your CV
    3. Docker and the Danger Areas of Deployment
    4. An Overview of Our Deployment Procedure
    5. As Always, Start with a Test
      1. Making an src Folder
    6. Installing Docker
    7. Building a Docker Image and Running a Docker Container
      1. A First Cut of a Dockerfile
      2. Docker Build
      3. Docker Run
    8. Installing Django in a Virtualenv in our Container Image
      1. Successful Run
    9. Using the FT to Check That Our Container Works
    10. Debugging Container Networking Problems
      1. Debugging Web Server Connectivity With “curl”
    11. Running code “inside” the container with docker exec
      1. Docker Port Mapping
      2. Essential Googling the Error Message
    12. Database migrations
      1. Should we run “migrate” inside the Dockerfile? No.
    13. Mounting files inside the container.
  16. 10. Making Our App Production-Ready
    1. What We Need to Do
    2. Switching to Gunicorn
      1. The FTs catch a problem with static files
    3. Serving Static Files with Whitenoise
    4. Using requirements.txt
    5. Using Environment Variables to Adjust Settings for Production
      1. Setting DEBUG=True and SECRET_KEY
      2. Setting environment variables inside the Dockerfile
      3. Setting Environment Variables at the Docker Command Line
      4. ALLOWED_HOSTS is Required When Debug Mode is Turned Off
      5. Collectstatic is Required when Debug is Turned Off
    6. Switching to a nonroot user
    7. Configuring logging
      1. Provoking a deliberate error
  17. 11. Getting A Server Ready For Deployment
    1. Getting a Domain Name
    2. Manually Provisioning a Server to Host Our Site
      1. Choosing Where to Host Our Site
      2. Spinning Up Our Own Server
    3. Configuring DNS for Staging and Live Domains
    4. Ansible
      1. Ansible vs SSH: How we’ll Talk to our Server
    5. Start by Making Sure We Can SSH In
      1. Debugging Issues with SSH
      2. Installing Ansible
      3. Checking Ansible can Talk To Our Server
  18. 12. Infrastructure As Code: Automated Deployments With Ansible
    1. A First Cut of an Ansible Playbook for Deployment
    2. SSHing Into the Server and Viewing Container Logs
    3. Getting our image onto the server
    4. Using an env File to Store Our Environment Variables
      1. More debugging
    5. Mounting the database on the server and running migrations
    6. It workssss
    7. Further Reading
  19. 13. Splitting Our Tests into Multiple Files, and a Generic Wait Helper
    1. Start on a Validation FT: Preventing Blank Items
      1. Skipping a Test
    2. Splitting Functional Tests Out into Many Files
    3. Running a Single Test File
    4. A New Functional Test Tool: A Generic Explicit Wait Helper
    5. Finishing Off the FT
    6. Refactoring Unit Tests into Several Files
  20. 14. Validation at the Database Layer
    1. Model-Layer Validation
      1. The self.assertRaises Context Manager
      2. Django Model Constraints and Their Interaction With Databases
      3. Inspecting Our Constraints at the Database Level
      4. Testing Django Model Validation:
      5. A Django Quirk: Model Save Doesn’t Run Validation
    2. Surfacing Model Validation Errors in the View
      1. Checking That Invalid Input Isn’t Saved to the Database
      2. Adding an Early Return to our FT to Let us Refactor Against Green
    3. Django Pattern: Processing POST Requests in the Same View as Renders the Form
      1. Refactor: Transferring the new_item Functionality into view_list
      2. Enforcing Model Validation in view_list
    4. Refactor: Removing Hardcoded URLs
      1. The {% url %} Template Tag
      2. Using get_absolute_url for Redirects
  21. 15. A Simple Form
    1. Moving Validation Logic into a Form
      1. Exploring the Forms API with a Unit Test
      2. Switching to a Django ModelForm
      3. Testing and Customising Form Validation
    2. Using the Form in Our Views
      1. Using the Form in a View with a GET Request
      2. A Big Find and Replace
    3. Using the Form in a View That Takes POST Requests
      1. Using the Form to Display Errors in the Template
    4. Using the Form in the Other View
      1. A Helper Method for Several Short Tests
    5. An Unexpected Benefit: Free Client-Side Validation from HTML5
    6. A Pat on the Back
      1. But Have We Wasted a Lot of Time?
      2. Using the Form’s Own Save Method
  22. 16. More Advanced Forms
    1. Another FT for Duplicate Items
      1. Preventing Duplicates at the Model Layer
      2. Rewriting the Old Model Test
      3. Integrity Errors That Show Up on Save
    2. Experimenting with Duplicate Item Validation at the Views Layer
    3. A More Complex Form to Handle Uniqueness Validation
    4. Using the Existing List Item Form in the List View
      1. A Little Digression on Queryset Ordering and String Representations
    5. Wrapping Up: What We’ve Learned About Testing Django
  23. 17. A Gentle Excursion Into JavaScript
    1. Starting with an FT
    2. A Quick “Spike”
      1. A Simple Inline Script
      2. Using the Browser Devtools
    3. Setting Up a Basic JavaScript Test Runner
    4. Testing with some DOM content
    5. Building a JavaScript Unit Test for Our Desired Functionality
    6. Fixtures, Execution Order, and Global State: Key Challenges of JS Testing
      1. console.log for Debug Printing
    7. Using an Initialize Function for More Control Over Execution Time
    8. Deliberately Breaking Our Code to Force Ourselves To Write More Tests
    9. Red, Green, Refactor: Removing Hardcoded Selectors
    10. Does it work?
    11. Testing Integration with CSS and Bootstrap
    12. Columbo Says: wait for Onload
    13. JavaScript Testing in the TDD Cycle
  24. 18. Deploying Our New Code
    1. Staging Deploy
    2. Live Deploy
    3. What to Do If You See a Database Error
      1. How to Delete the Database on the Staging Server
    4. Wrap-Up: git tag the New Release
  25. About the Author

Product information

  • Title: Test-Driven Development with Python, 3rd Edition
  • Author(s): Harry Percival
  • Release date: July 2025
  • Publisher(s): O'Reilly Media, Inc.
  • ISBN: 9781098148713