├── !README_FOR_PRODUCTION.txt
├── .dockerignore
├── .git-blame-ignore-revs
├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .gitmodules
├── .python-version
├── CITATION.md
├── Dockerfile
├── ER_sampleTOC.html
├── LICENSE.md
├── Makefile
├── README.md
├── Vagrantfile
├── acknowledgments.asciidoc
├── analytics.html
├── appendix_CD.asciidoc
├── appendix_DjangoRestFramework.asciidoc
├── appendix_Django_Class-Based_Views.asciidoc
├── appendix_IV_testing_migrations.asciidoc
├── appendix_IX_cheat_sheet.asciidoc
├── appendix_X_what_to_do_next.asciidoc
├── appendix_bdd.asciidoc
├── appendix_fts_for_external_dependencies.asciidoc
├── appendix_github_links.asciidoc
├── appendix_logging.asciidoc
├── appendix_purist_unit_tests.asciidoc
├── appendix_rest_api.asciidoc
├── appendix_tradeoffs.asciidoc
├── asciidoc.conf
├── asciidoctor.css
├── atlas.json
├── author_bio.html
├── bibliography.asciidoc
├── book.asciidoc
├── buy_the_book_banner.html
├── callouts
├── 1.pdf
├── 1.png
├── 10.pdf
├── 10.png
├── 11.pdf
├── 11.png
├── 12.pdf
├── 12.png
├── 13.pdf
├── 13.png
├── 14.pdf
├── 14.png
├── 15.pdf
├── 15.png
├── 16.pdf
├── 16.png
├── 17.pdf
├── 17.png
├── 18.pdf
├── 18.png
├── 19.pdf
├── 19.png
├── 2.pdf
├── 2.png
├── 20.pdf
├── 20.png
├── 21.pdf
├── 21.png
├── 22.pdf
├── 22.png
├── 23.pdf
├── 23.png
├── 24.pdf
├── 24.png
├── 25.pdf
├── 25.png
├── 26.pdf
├── 26.png
├── 27.pdf
├── 27.png
├── 28.pdf
├── 28.png
├── 29.pdf
├── 29.png
├── 3.pdf
├── 3.png
├── 30.pdf
├── 30.png
├── 31.pdf
├── 31.png
├── 32.pdf
├── 32.png
├── 33.pdf
├── 33.png
├── 34.pdf
├── 34.png
├── 35.pdf
├── 35.png
├── 36.pdf
├── 36.png
├── 37.pdf
├── 37.png
├── 38.pdf
├── 38.png
├── 39.pdf
├── 39.png
├── 4.pdf
├── 4.png
├── 5.pdf
├── 5.png
├── 6.pdf
├── 6.png
├── 7.pdf
├── 7.png
├── 8.pdf
├── 8.png
├── 9.pdf
└── 9.png
├── chapter_01.asciidoc
├── chapter_02_unittest.asciidoc
├── chapter_03_unit_test_first_view.asciidoc
├── chapter_04_philosophy_and_refactoring.asciidoc
├── chapter_05_post_and_database.asciidoc
├── chapter_06_explicit_waits_1.asciidoc
├── chapter_07_working_incrementally.asciidoc
├── chapter_08_prettification.asciidoc
├── chapter_09_docker.asciidoc
├── chapter_10_production_readiness.asciidoc
├── chapter_11_server_prep.asciidoc
├── chapter_12_ansible.asciidoc
├── chapter_13_organising_test_files.asciidoc
├── chapter_14_database_layer_validation.asciidoc
├── chapter_15_simple_form.asciidoc
├── chapter_16_advanced_forms.asciidoc
├── chapter_17_javascript.asciidoc
├── chapter_18_second_deploy.asciidoc
├── chapter_19_spiking_custom_auth.asciidoc
├── chapter_20_mocking_1.asciidoc
├── chapter_21_mocking_2.asciidoc
├── chapter_22_fixtures_and_wait_decorator.asciidoc
├── chapter_23_debugging_prod.asciidoc
├── chapter_24_outside_in.asciidoc
├── chapter_25_CI.asciidoc
├── chapter_26_page_pattern.asciidoc
├── chapter_27_hot_lava.asciidoc
├── coderay-asciidoctor.css
├── colo.html
├── copy_html_to_site_and_print_toc.py
├── copyright.html
├── count-todos.py
├── cover.html
├── disqus_comments.html
├── docs
├── ORM_style_guide.htm
├── ORM_style_guide_files
│ └── main.css
├── asciidoc-cheatsheet.html
├── asciidoc-cheatsheet_files
│ ├── 640-screen2.gif
│ ├── Content.css
│ ├── asciidoc.asc
│ ├── asciidoc.css
│ ├── asciidoc.js
│ ├── caution.png
│ ├── home.png
│ ├── important.png
│ ├── jquery-1.js
│ ├── note.png
│ ├── pygments.css
│ ├── tip.png
│ └── warning.png
├── asciidoc-userguide.html
├── asciidoc-userguide_files
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── Content.css
│ ├── asciidoc.css
│ ├── asciidoc.js
│ ├── layout2.css
│ ├── note.png
│ ├── tip.png
│ ├── valid-xhtml11-blue.png
│ ├── vcss-blue.gif
│ └── warning.png
└── example_book.txt
├── downloads
└── bootstrap.zip
├── epilogue.asciidoc
├── images
├── ansible-and-ssh.png
├── bootsrap_css_404_devtools.png
├── car-factory-illustration.png
├── containers-diagram.png
├── cover.png
├── devtools-post-request.png
├── devtools_closeup_edit_html.png
├── devtools_error_div_hidden.png
├── django_400.png
├── django_operationalerror.png
├── double-loop-tdd-simpler.png
├── duplicate_item_error.png
├── editing-html-via-devtools.png
├── error-gone-but-input-still-red.png
├── firefox-connection-reset.png
├── firefox-unable-to-connect.png
├── firefox_upgrade_popup.png
├── gandi_add_dns_a_record.png
├── git_windows_installer_choose_editor.png
├── gitlab_files_ui.png
├── gitlab_first_build.png
├── gitlab_new_blank_project.png
├── gitlab_pipeline_js_success.png
├── gitlab_pipeline_overview_success.png
├── gitlab_pipeline_success.png
├── gitlab_ui_for_browse_artifacts.png
├── gitlab_ui_show_screenshot.png
├── google-results-with-stackoverflow.png
├── homepage_no_css_8888.png
├── html5-validation-popup.png
├── integrity_error_unique_constraint.png
├── jasmine-console-logs.png
├── jasmine-in-browser-green.png
├── jasmine-in-browser-red.png
├── jenkins-admin-user.png
├── jenkins-unlock.png
├── jenkins-welcome.png
├── js-errordiv-is-null-in-console.png
├── list_with_sharing_options.png
├── login-email-sent-page.png
├── login-link-in-email.png
├── magic-links-overview.png
├── multiple-lists-users-and-urls.png
├── new_jenkins_phantomjs_screenshot.png
├── orly-essential-googling-the-error-message.png
├── outside-in-layers.png
├── paperbottom.png
├── papermiddle.png
├── papertop.png
├── prettified-1.png
├── prettified-2.png
├── prettified-dark.png
├── prettified-final.png
├── python_install_add_to_path.png
├── red-green-refactor-excalidraw.png
├── search-results-400-bad-request.png
├── server_error_500.png
├── single-endpoint-for-forms.png
├── site-in-docker-is-up.png
├── spike-it-worked-windows.png
├── tdd-process-unit-tests-only-excalidraw.png
├── todo_list_table_disappeared.png
├── twp2_00in01.png
├── twp2_0101.png
├── twp2_0102.png
├── twp2_0103.png
├── twp2_0401.png
├── twp2_0402.png
├── twp2_0501.png
├── twp2_0503.png
├── twp2_0701.png
├── twp2_0901.png
├── twp2_0902.png
├── twp2_0902a.png
├── twp2_0904.png
├── twp2_1101.png
├── twp2_1601.png
├── twp2_1602.png
├── twp2_1603.png
├── twp2_1901.png
├── twp2_2001.png
├── twp2_2201.png
├── twp2_2404.png
├── twp2_2405.png
├── twp2_2406.png
├── twp2_2407.png
├── twp2_2408.png
├── twp2_2409.png
├── twp2_2410.png
├── twp2_2411.png
├── twp2_2412.png
├── twp2_2601.png
├── twp2_ad01.png
├── twp2_ae01.png
├── twp2_ag01.png
├── twp2_ah01.png
├── typeerror_in_devools.png
├── ugly-homepage.png
├── virtualenv-vs-vm.png
└── wrong_order_list.png
├── index.txt
├── ix.html
├── load_toc.js
├── misc
├── abandoned_roman_numerals_example
│ ├── rome.py
│ └── tests.py
├── chapters.rst
├── chapters_v2.rst
├── chimera_comments_scraper.py
├── curl
├── get_stats.py
├── get_stats.sh
├── isolation-talks
│ ├── djangoisland.md
│ ├── djangoisland.py
│ ├── extra_styling_for_djangoisland.css
│ ├── outline.txt
│ └── webcast-commits.hist
├── plot.py
├── reddit_post.md
├── redditnotesresponse.txt
├── tdd-flowchart.dot
└── tdd_diagram.odp
├── outline_and_future_chapters.asciidoc
├── part1.asciidoc
├── part2.asciidoc
├── part3.asciidoc
├── part4.asciidoc
├── pdf
└── drafts
│ ├── intake.pdf
│ └── intake_with_remarks.pdf
├── praise.forbook.asciidoc
├── praise.html
├── pre-requisite-installations.asciidoc
├── preface.asciidoc
├── pygments-default.css
├── pyproject.toml
├── rename-chapter.sh
├── research
├── js-testing.rst
└── literary_agencies.ods
├── run_test_tests.sh
├── server-quickstart.md
├── source
├── blackify-chap.sh
├── feed-thru-cherry-picks.sh
├── fix-commit-numbers.py
└── push-back.sh
├── tests
├── actual_manage_py_test.output
├── book_parser.py
├── book_tester.py
├── chapters.py
├── check_links.py
├── conftest.py
├── examples.py
├── my-phantomjs-qunit-runner.js
├── run-js-spec.py
├── slimerjs-0.9.0
│ ├── LICENSE
│ ├── README.md
│ ├── application.ini
│ ├── omni.ja
│ ├── slimerjs
│ ├── slimerjs.bat
│ └── slimerjs.py
├── source_updater.py
├── sourcetree.py
├── test_appendix_DjangoRestFramework.py
├── test_appendix_Django_Class-Based_Views.py
├── test_appendix_bdd.py
├── test_appendix_purist_unit_tests.py
├── test_appendix_rest_api.py
├── test_book_parser.py
├── test_book_tester.py
├── test_chapter_01.py
├── test_chapter_02_unittest.py
├── test_chapter_03_unit_test_first_view.py
├── test_chapter_04_philosophy_and_refactoring.py
├── test_chapter_05_post_and_database.py
├── test_chapter_06_explicit_waits_1.py
├── test_chapter_07_working_incrementally.py
├── test_chapter_08_prettification.py
├── test_chapter_09_docker.py
├── test_chapter_10_production_readiness.py
├── test_chapter_11_server_prep.py
├── test_chapter_12_ansible.py
├── test_chapter_13_organising_test_files.py
├── test_chapter_14_database_layer_validation.py
├── test_chapter_15_simple_form.py
├── test_chapter_16_advanced_forms.py
├── test_chapter_17_javascript.py
├── test_chapter_19_spiking_custom_auth.py
├── test_chapter_20_mocking_1.py
├── test_chapter_21_mocking_2.py
├── test_chapter_22_fixtures_and_wait_decorator.py
├── test_chapter_23_debugging_prod.py
├── test_chapter_24_outside_in.py
├── test_chapter_25_CI.py
├── test_chapter_26_page_pattern.py
├── test_source_updater.py
├── test_sourcetree.py
├── test_write_to_file.py
├── update_source_repo.py
└── write_to_file.py
├── theme
├── epub
│ ├── epub.css
│ ├── epub.xsl
│ └── layout.html
├── html
│ └── html.xsl
├── mobi
│ ├── layout.html
│ ├── mobi.css
│ └── mobi.xsl
└── pdf
│ ├── pdf.css
│ └── pdf.xsl
├── titlepage.html
├── toc.html
├── todos.txt
├── tools
├── figure_renaming_report.tsv
├── intakereport.txt
└── oneoffs
│ ├── oneoff.css
│ └── oneoff.xsl
├── video_plug.asciidoc
├── wordcount
└── workshops
├── images
├── empty_jasmine_specrunner.png
├── maths_broken.png
└── maths_works.png
├── intermediate_workshop_notes.md
├── js-testing-with-jasmine.asciidoc
├── pycon.uk.2015.dirigible-talk.md
├── pycon.uk.2015.tutorial-beginners.md
├── pycon.us.2015.study-group.md
├── study-group.md
├── working-incrementally-handout.md
├── working-incrementally-notes.md
└── workshop.asciidoc
/!README_FOR_PRODUCTION.txt:
--------------------------------------------------------------------------------
1 | Note for production editor about page-breaking: the author has requested a global solution to keep code blocks from breaking across pages in the PDF, so there is CSS in place to that effect.
2 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .venv
2 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # matt hacker bulk edit for prod
2 | 2cb0ed8c264cee682303288ba5a5cea80956fb8d
3 | 2735e383f1281c5f200e64cfb7cda0457cfe8d1e
4 | 6fce793a9a4d701313684de11ca2cd3f5e89a041
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | .cache
3 | .vagrant
4 | *-cloudimg-console.log
5 | .venv
6 | .pytest_cache
7 |
8 | /chapter_*.html
9 | /appendix_*.html
10 | /part*.html
11 | /outline_and_future*.html
12 | /pre-requisite-installations.html
13 | /preface.html
14 | /epilogue.html
15 | /bibliography.html
16 | /acknowledgments.html
17 | /video_plug.html
18 | /part*.forbook.asciidoc
19 | /praise.forbook.html
20 |
21 | /misc/abandoned_roman_numerals_example
22 | /docs/atlas_docs/
23 | /proposals
24 | /tags
25 | /pdf_drafts
26 | /pycon
27 | /source/*/static
28 | /source/*/database
29 | /wordcounts.*
30 | /feedback
31 | /downloads/*.js
32 | /downloads/mock*
33 | /misc/promo/
34 | /misc/Vagrantfile
35 | /misc/superlists-repo-django16-backup.zip
36 | /tdd-tutorial-materials
37 | /misc/Invoice-Percival-1
38 | /video
39 | /misc/*conference_report.md
40 | /tests/.cache/
41 | /workshops/js-testing-with-jasmine.html
42 | /tech review/
43 | .vagrant.d
44 | *.egg-info
45 | .tmpdir.*
46 | .env
47 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.13
2 |
--------------------------------------------------------------------------------
/CITATION.md:
--------------------------------------------------------------------------------
1 | Bibtex:
2 | ```TeX
3 | @BOOK{percival:tdd:python,
4 | AUTHOR = "{Harry J.W.} Percival",
5 | TITLE = "Test-Driven Development with Python",
6 | SUBTITLE = "Obey the Testing Goat!",
7 | DATE = "2014",
8 | PUBLISHER = "O'Reilly Media, Inc.",
9 | ISBN = "9781449365141"
10 | }
11 | ```
12 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:slim
2 |
3 | # -- WIP --
4 | # this dockerfile is a work in progress,
5 | # the vague intention is to use it for CI.
6 |
7 | # RUN add-apt-repository ppa:mozillateam/ppa && \
8 | RUN apt-get update -y
9 |
10 | RUN apt-get install -y \
11 | git \
12 | asciidoctor \
13 | # language-pack-en \
14 | ruby-pygments.rb \
15 | firefox-esr \
16 | tree \
17 | locales \
18 | vim
19 |
20 | RUN apt-get install -y \
21 | make \
22 | curl
23 |
24 | RUN locale-gen en_GB.UTF-8
25 | # RUN pip install uv
26 | ADD --chmod=755 https://astral.sh/uv/install.sh /install.sh
27 | RUN /install.sh && rm /install.sh
28 | RUN ln -s $HOME/.local/bin/uv /usr/bin/uv
29 |
30 | RUN git config --global user.email "elspeth@example.com" && \
31 | git config --global user.name "Elspeth See-Eye" && \
32 | git config --global init.defaultBranch main
33 |
34 | WORKDIR /app
35 | RUN uv venv .venv
36 |
37 | COPY pyproject.toml pyproject.toml
38 | RUN uv pip install .
39 | RUN uv pip install selenium
40 | ENV PATH=".venv/bin:$PATH"
41 |
42 |
43 | CMD bash
44 |
--------------------------------------------------------------------------------
/ER_sampleTOC.html:
--------------------------------------------------------------------------------
1 | Preface (AVAILABLE) Prerequisites and Assumptions (AVAILABLE) Companion Video (AVAILABLE) Acknowledgments (UNAVAILABLE) Part 1: The Basics of TDD and Django (AVAILABLE) Chapter 1: Getting Django Set Up Using a Functional Test (AVAILABLE) Chapter 2: Extending Our Functional Test Using the unittest Module (AVAILABLE) Chapter 3: Testing a Simple Home Page with Unit Tests (AVAILABLE) Chapter 4: What Are We Doing with All These Tests? (And, Refactoring) (AVAILABLE) Chapter 5: Saving User Input: Testing the Database (AVAILABLE) Chapter 6: Improving Functional Tests: Ensuring Isolation and Removing Voodoo Sleeps (AVAILABLE) Chapter 7: Working Incrementally (AVAILABLE) Part 2: Web Development Sine Qua Nons (AVAILABLE) Chapter 8: Prettification: Layout and Styling, and What to Test About It (AVAILABLE) Chapter 9: Containerization akaDocker (AVAILABLE) Chapter 10: Making our App Production-Ready (AVAILABLE) Chapter 11: Getting A Server Ready for Deployment (AVAILABLE) Chapter 12: Infrastructure As Code: Automated Deployments With Ansible (AVAILABLE) Chapter 13: Splitting Our Tests into Multiple Files, and a Generic
41 | Wait Helper (AVAILABLE) Chapter 14: Validation at the Database Layer (AVAILABLE) Chapter 15: A Simple Form (AVAILABLE) Chapter 16: More Advanced Forms (AVAILABLE) Chapter 17: A Gentle Excursion into JavaScript (AVAILABLE) Chapter 18: Deploying Our New Code (AVAILABLE) Part 3: More Advanced Topics in Testing (UNAVAILABLE) Chapter 19: User Authentication, Spiking, and De-Spiking (UNAVAILABLE) Chapter 20: Mocks and Mocking 1: Using Mocks to Test External
58 | Dependencies (UNAVAILABLE) Chapter 21: Mocks and Mocking 2: Using Mocks for Test Isolation (UNAVAILABLE) Chapter 22: Test Fixtures and a Decorator for Explicit Waits (UNAVAILABLE) Chapter 23: Server-Side Debugging (UNAVAILABLE) Chapter 24: Finishing “My Lists”: Outside-In TDD (UNAVAILABLE) Chapter 25: Continuous Integration (CI) (UNAVAILABLE) Chapter 26: The Token Social Bit, the Page Pattern, and an Exercise
71 | for the Reader (UNAVAILABLE) Chapter 27: Fast Tests, Slow Tests, and Hot Lava (UNAVAILABLE) Back Matter: Obey the Testing Goat! (UNAVAILABLE) App A: PythonAnywhere (UNAVAILABLE) App B: Django Class-Based Views (UNAVAILABLE) App C: Provisioning with Ansible (UNAVAILABLE) App D: Testing Database Migrations (UNAVAILABLE) App E: Behaviour-Driven Development (BDD) (UNAVAILABLE) App F: Building a REST API: JSON, Ajax, and Mocking with JavaScript (UNAVAILABLE) App G: Django-Rest-Framework (UNAVAILABLE) App H: Cheat Sheet (UNAVAILABLE) App I: What to Do Next (UNAVAILABLE) App J: Source Code Examples (UNAVAILABLE) Bibliography (UNAVAILABLE)Brief Table of Contents (Not Yet Final)
3 |
4 |
The animal on the cover of Test-Driven Development with Python is a cashmere goat. Though all goats can produce a cashmere undercoat, only those goats selectively bred to produce cashmere in commercially viable amounts are typically considered “cashmere goats.” Cashmere goats thus belong to the domestic goat species Capra hircus.
5 | 6 |The exceptionally fine, soft hair of the undercoat of a cashmere goat grows alongside an outer coat of coarser hair as part of the goat’s double fleece. The cashmere undercoat appears in winter to supplement the protection offered by the outer coat, called guard hair. The crimped quality of cashmere hair in the undercoat accounts for its lightweight yet effective insulation properties.
7 | 8 |The name “cashmere” is derived from the Kashmir Valley region on the Indian subcontinent where the textile has been manufactured for thousands of years. A diminishing population of cashmere goats in modern Kashmir has led to the cessation of exports of cashmere fiber from the area. Most cashmere wool now originates in Afghanistan, Iran, Outer Mongolia, India, and—predominantly—China.
9 | 10 |Cashmere goats grow hair of varying colors and color combinations. Both males and females have horns, which serve to keep the animals cool in summer and provide the goats’ owners with effective handles during farming activities.
11 | 12 |Many of the animals on O'Reilly covers are endangered; all of them are important to the world. To learn more about how you can help, go to animals.oreilly.com.
13 | 14 |The cover image is from Wood's Animate Creation. The cover fonts are URW Typewriter and Guardian Sans. The text font is Adobe Minion Pro; the heading font is Adobe Myriad Condensed; and the code font is Dalton Maag's Ubuntu Mono.
15 | 16 |4 |7 |In this book, Harry takes us on an adventure of discovery with Python and testing. It’s an excellent book, fun to read and full of vital information. It has my highest recommendations for anyone interested in testing with Python, learning Django, or wanting to use Selenium. Testing is essential for developer sanity and it's a notoriously difficult field, full of trade-offs. Harry does a fantastic job of holding our attention whilst exploring real-world testing practices.
5 |Michael Foord, Python Core Developer and Maintainer of unittest
6 |
8 |11 |This book is far more than an introduction to test-driven development—it’s a complete best-practices crash course, from start to finish, into modern web application development with Python. Every web developer needs this book.
9 |Kenneth Reitz, Fellow at Python Software Foundation
10 |
12 |15 |Harry’s book is what we wish existed when we were learning Django. At a pace that’s achievable and yet delightfully challenging, it provides excellent instruction for Django and various test practices. The material on Selenium alone makes the book worth purchasing, but there's so much more!
13 |Daniel and Audrey Roy Greenfeld, authors of Two Scoops of Django (Two Scoops Press)
14 |
13 |
13 |
Third Edition
5 | 6 |Obey the Testing Goat: Using Django, Selenium, and JavaScript
7 | 8 | 9 | 10 | 11 |
Comments
3 | 4 | 16 | 17 |