├── !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 |
2 |

Brief Table of Contents (Not Yet Final)

3 | 4 |

Preface (AVAILABLE)

5 | 6 |

Prerequisites and Assumptions (AVAILABLE)

7 | 8 |

Companion Video (AVAILABLE)

9 | 10 |

Acknowledgments (UNAVAILABLE)

11 | 12 |

Part 1: The Basics of TDD and Django (AVAILABLE)

13 | 14 |

Chapter 1: Getting Django Set Up Using a Functional Test (AVAILABLE)

15 | 16 |

Chapter 2: Extending Our Functional Test Using the unittest Module (AVAILABLE)

17 | 18 |

Chapter 3: Testing a Simple Home Page with Unit Tests (AVAILABLE)

19 | 20 |

Chapter 4: What Are We Doing with All These Tests? (And, Refactoring) (AVAILABLE)

21 | 22 |

Chapter 5: Saving User Input: Testing the Database (AVAILABLE)

23 | 24 |

Chapter 6: Improving Functional Tests: Ensuring Isolation and Removing Voodoo Sleeps (AVAILABLE)

25 | 26 |

Chapter 7: Working Incrementally (AVAILABLE)

27 | 28 |

Part 2: Web Development Sine Qua Nons (AVAILABLE)

29 | 30 |

Chapter 8: Prettification: Layout and Styling, and What to Test About It (AVAILABLE)

31 | 32 |

Chapter 9: Containerization akaDocker (AVAILABLE)

33 | 34 |

Chapter 10: Making our App Production-Ready (AVAILABLE)

35 | 36 |

Chapter 11: Getting A Server Ready for Deployment (AVAILABLE)

37 | 38 |

Chapter 12: Infrastructure As Code: Automated Deployments With Ansible (AVAILABLE)

39 | 40 |

Chapter 13: Splitting Our Tests into Multiple Files, and a Generic 41 | Wait Helper (AVAILABLE)

42 | 43 |

Chapter 14: Validation at the Database Layer (AVAILABLE)

44 | 45 |

Chapter 15: A Simple Form (AVAILABLE)

46 | 47 |

Chapter 16: More Advanced Forms (AVAILABLE)

48 | 49 |

Chapter 17: A Gentle Excursion into JavaScript (AVAILABLE)

50 | 51 |

Chapter 18: Deploying Our New Code (AVAILABLE)

52 | 53 |

Part 3: More Advanced Topics in Testing (UNAVAILABLE)

54 | 55 |

Chapter 19: User Authentication, Spiking, and De-Spiking (UNAVAILABLE)

56 | 57 |

Chapter 20: Mocks and Mocking 1: Using Mocks to Test External 58 | Dependencies (UNAVAILABLE)

59 | 60 |

Chapter 21: Mocks and Mocking 2: Using Mocks for Test Isolation (UNAVAILABLE)

61 | 62 |

Chapter 22: Test Fixtures and a Decorator for Explicit Waits (UNAVAILABLE)

63 | 64 |

Chapter 23: Server-Side Debugging (UNAVAILABLE)

65 | 66 |

Chapter 24: Finishing “My Lists”: Outside-In TDD (UNAVAILABLE)

67 | 68 |

Chapter 25: Continuous Integration (CI) (UNAVAILABLE)

69 | 70 |

Chapter 26: The Token Social Bit, the Page Pattern, and an Exercise 71 | for the Reader (UNAVAILABLE)

72 | 73 |

Chapter 27: Fast Tests, Slow Tests, and Hot Lava (UNAVAILABLE)

74 | 75 |

Back Matter: Obey the Testing Goat! (UNAVAILABLE)

76 | 77 |

App A: PythonAnywhere (UNAVAILABLE)

78 | 79 |

App B: Django Class-Based Views (UNAVAILABLE)

80 | 81 |

App C: Provisioning with Ansible (UNAVAILABLE)

82 | 83 |

App D: Testing Database Migrations (UNAVAILABLE)

84 | 85 |

App E: Behaviour-Driven Development (BDD) (UNAVAILABLE)

86 | 87 |

App F: Building a REST API: JSON, Ajax, and Mocking with JavaScript (UNAVAILABLE)

88 | 89 |

App G: Django-Rest-Framework (UNAVAILABLE)

90 | 91 |

App H: Cheat Sheet (UNAVAILABLE)

92 | 93 |

App I: What to Do Next (UNAVAILABLE)

94 | 95 |

App J: Source Code Examples (UNAVAILABLE)

96 | 97 |

Bibliography (UNAVAILABLE)

98 |
99 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This book, and all the associated source files, are being made available under 2 | the Creative Commons Attribution-NonCommercial-ShareAlike License (v3.0 United 3 | States) 4 | 5 | Full info at https://creativecommons.org/licenses/by-nc-sa/3.0/us/ 6 | 7 | -------------------------------------------------------------------------------- /analytics.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /appendix_tradeoffs.asciidoc: -------------------------------------------------------------------------------- 1 | [[appendix_tradeoffs]] 2 | [appendix] 3 | == Testing Tradeoffs: Choosing the Right Place to Test From 4 | 5 | pick up in chapter 16, refactor form, move html back to template 6 | 7 | tests were in the wrong place, better to have them in test_views.py 8 | 9 | mind you, it's also tested in the FT. 10 | 11 | -> 3 places we could/do test. 12 | 13 | discuss contract between FE and BE. it's the `name=` basically 14 | 15 | where do we want to keep the logic about bootstrap css? 16 | 17 | * show moving tests from test_forms.py to test_views.py 18 | * get rid of assertions about `isinstance(... Form)` 19 | 20 | 21 | lesson: tests at higher level enable more refactoring 22 | 23 | 24 | === Deleting some FTs 25 | 26 | validation test is doing quite a lot of work, tests integration w/ backend and bootstrap, good 27 | main todos test is the key smoke test 28 | test multiple lists? less wortwhile. 29 | test list sharing? maybe not worth it. 30 | test login? maybe not adding value 31 | 32 | 33 | === FT speedup techniques 34 | 35 | `with self.subTest`. 36 | 37 | 38 | === Testing at a Lower Level 39 | 40 | imagine a new feature, "strict todos": 41 | 42 | - rule about duplicate items is relaxed for non-strict todos 43 | - strict todos must start with capital letter and end with full stop 44 | - use linguistic analysis to check for imperative mood (?) 45 | 46 | => justitify some proper unit tests? 47 | 48 | at the very least, testing for the regex _could_ happen at a lower level 49 | 50 | -------------------------------------------------------------------------------- /asciidoc.conf: -------------------------------------------------------------------------------- 1 | [replacements] 2 | %1%=➊ 3 | %2%=➋ 4 | %3%=➌ 5 | %4%=➍ 6 | %5%=➎ 7 | %6%=➏ 8 | 9 | -------------------------------------------------------------------------------- /atlas.json: -------------------------------------------------------------------------------- 1 | { 2 | "branch": "main", 3 | "files": [ 4 | "cover.html", 5 | "praise.html", 6 | "titlepage.html", 7 | "copyright.html", 8 | "toc.html", 9 | "preface.asciidoc", 10 | "pre-requisite-installations.asciidoc", 11 | "video_plug.asciidoc", 12 | "acknowledgments.asciidoc", 13 | "part1.asciidoc", 14 | "chapter_01.asciidoc", 15 | "chapter_02_unittest.asciidoc", 16 | "chapter_03_unit_test_first_view.asciidoc", 17 | "chapter_04_philosophy_and_refactoring.asciidoc", 18 | "chapter_05_post_and_database.asciidoc", 19 | "chapter_06_explicit_waits_1.asciidoc", 20 | "chapter_07_working_incrementally.asciidoc", 21 | "chapter_08_prettification.asciidoc", 22 | "part2.asciidoc", 23 | "chapter_09_docker.asciidoc", 24 | "chapter_10_production_readiness.asciidoc", 25 | "chapter_11_server_prep.asciidoc", 26 | "chapter_12_ansible.asciidoc", 27 | "part3.asciidoc", 28 | "chapter_13_organising_test_files.asciidoc", 29 | "chapter_14_database_layer_validation.asciidoc", 30 | "chapter_15_simple_form.asciidoc", 31 | "chapter_16_advanced_forms.asciidoc", 32 | "part4.asciidoc", 33 | "chapter_17_javascript.asciidoc", 34 | "chapter_18_second_deploy.asciidoc", 35 | "chapter_19_spiking_custom_auth.asciidoc", 36 | "chapter_20_mocking_1.asciidoc", 37 | "chapter_21_mocking_2.asciidoc", 38 | "chapter_22_fixtures_and_wait_decorator.asciidoc", 39 | "chapter_23_debugging_prod.asciidoc", 40 | "chapter_24_outside_in.asciidoc", 41 | "chapter_25_CI.asciidoc", 42 | "chapter_26_page_pattern.asciidoc", 43 | "chapter_27_hot_lava.asciidoc", 44 | "epilogue.asciidoc", 45 | "appendix_rest_api.asciidoc", 46 | "appendix_IX_cheat_sheet.asciidoc", 47 | "appendix_X_what_to_do_next.asciidoc", 48 | "appendix_github_links.asciidoc", 49 | "bibliography.asciidoc", 50 | "ix.html", 51 | "author_bio.html", 52 | "colo.html" 53 | ], 54 | "formats": { 55 | "pdf": { 56 | "version": "web", 57 | "toc": true, 58 | "index": false, 59 | "antennahouse_version": "AHFormatterV62_64-MR4", 60 | "syntaxhighlighting": true, 61 | "show_comments": false, 62 | "color_count": "1", 63 | "trim_size": "7inx9.1875in" 64 | }, 65 | "epub": { 66 | "index": false, 67 | "toc": true, 68 | "epubcheck": true, 69 | "syntaxhighlighting": true, 70 | "show_comments": false, 71 | "downsample_images": false 72 | }, 73 | "mobi": { 74 | "index": true, 75 | "toc": true, 76 | "syntaxhighlighting": true, 77 | "show_comments": false, 78 | "downsample_images": false 79 | }, 80 | "html": { 81 | "index": true, 82 | "toc": true, 83 | "consolidated": false, 84 | "syntaxhighlighting": true, 85 | "show_comments": false 86 | } 87 | }, 88 | "theme": "oreillymedia/animal_theme_sass", 89 | "title": "Test-Driven Development with Python", 90 | "print_isbn13": "9781449364823", 91 | "templating": false, 92 | "lang": "en", 93 | "accent_color": "" 94 | } 95 | -------------------------------------------------------------------------------- /author_bio.html: -------------------------------------------------------------------------------- 1 |
2 |

About the Author

3 |

After an idyllic childhood spent playing with BASIC on French 8-bit computers like the Thomson T-07 whose keys go "boop" when you press them, Harry spent a few years being deeply unhappy with economics and management consultancy. Soon he rediscovered his true geek nature, and was lucky enough to fall in with a bunch of XP fanatics, working on the pioneering but sadly defunct Resolver One spreadsheet. He now works at PythonAnywhere LLP, and spreads the gospel of TDD worldwide at talks, workshops, and conferences, with all the passion and enthusiasm of a recent convert.

4 |
5 | -------------------------------------------------------------------------------- /bibliography.asciidoc: -------------------------------------------------------------------------------- 1 | [role="bibliography":"] 2 | [appendix] 3 | 4 | Bibliography 5 | ------------ 6 | 7 | TODO remove this chapter entirely, 8 | or remove links from text (they dont work), 9 | and strip down to just the most important books. 10 | 11 | - [[[tddbe]]] Kent Beck, 'Test Driven Development: By Example', Addison-Wesley 12 | - [[[refactoring]]] Martin Fowler, 'Refactoring', Addison-Wesley 13 | - [[[seceng]]] Ross Anderson, 'Security Engineering, Second Edition', 14 | Addison-Wesley: http://www.cl.cam.ac.uk/~rja14/book.html 15 | - [[[jsgoodparts]]] Douglas Crockford, 16 | http://oreil.ly/SuXjXq['JavaScript: The Good Parts'], O'Reilly 17 | - [[[twoscoops]]] Daniel Greenfeld and Audrey Roy, 'Two Scoops of Django', http://twoscoopspress.com/products/two-scoops-of-django-1-6 18 | - [[[GOOSGBT]]] Steve Freeman and Nat Pryce, 'Growing 19 | Object-Oriented Software Guided by Tests', Addison-Wesley 20 | 21 | -------------------------------------------------------------------------------- /book.asciidoc: -------------------------------------------------------------------------------- 1 | :doctype: book 2 | :bookseries: pythonbook 3 | :source-highlighter: pygments 4 | :pygments-style: manni 5 | :icons: font 6 | 7 | = Test-Driven Development with Python 8 | :toc: 9 | 10 | 11 | :sectnums!: 12 | 13 | include::praise.forbook.asciidoc[] 14 | include::preface.asciidoc[] 15 | include::pre-requisite-installations.asciidoc[] 16 | include::video_plug.asciidoc[] 17 | include::acknowledgments.asciidoc[] 18 | 19 | include::part1.forbook.asciidoc[] 20 | 21 | :sectnums: 22 | 23 | include::chapter_01.asciidoc[] 24 | include::chapter_02_unittest.asciidoc[] 25 | include::chapter_03_unit_test_first_view.asciidoc[] 26 | include::chapter_04_philosophy_and_refactoring.asciidoc[] 27 | include::chapter_05_post_and_database.asciidoc[] 28 | include::chapter_06_explicit_waits_1.asciidoc[] 29 | include::chapter_07_working_incrementally.asciidoc[] 30 | include::chapter_08_prettification.asciidoc[] 31 | 32 | include::part2.forbook.asciidoc[] 33 | include::chapter_09_docker.asciidoc[] 34 | include::chapter_10_production_readiness.asciidoc[] 35 | include::chapter_11_server_prep.asciidoc[] 36 | include::chapter_12_ansible.asciidoc[] 37 | 38 | include::part3.forbook.asciidoc[] 39 | include::chapter_13_organising_test_files.asciidoc[] 40 | include::chapter_14_database_layer_validation.asciidoc[] 41 | include::chapter_15_simple_form.asciidoc[] 42 | include::chapter_16_advanced_forms.asciidoc[] 43 | 44 | include::part4.forbook.asciidoc[] 45 | include::chapter_17_javascript.asciidoc[] 46 | include::chapter_18_second_deploy.asciidoc[] 47 | include::chapter_19_spiking_custom_auth.asciidoc[] 48 | include::chapter_20_mocking_1.asciidoc[] 49 | include::chapter_21_mocking_2.asciidoc[] 50 | include::chapter_22_fixtures_and_wait_decorator.asciidoc[] 51 | include::chapter_23_debugging_prod.asciidoc[] 52 | include::chapter_24_outside_in.asciidoc[] 53 | include::chapter_25_CI.asciidoc[] 54 | include::chapter_26_page_pattern.asciidoc[] 55 | include::chapter_27_hot_lava.asciidoc[] 56 | 57 | include::epilogue.asciidoc[] 58 | 59 | include::appendix_fts_for_external_dependencies.asciidoc[] 60 | include::appendix_CD.asciidoc[] 61 | include::appendix_bdd.asciidoc[] 62 | include::appendix_rest_api.asciidoc[] 63 | include::appendix_IX_cheat_sheet.asciidoc[] 64 | include::appendix_X_what_to_do_next.asciidoc[] 65 | include::appendix_github_links.asciidoc[] 66 | 67 | include::bibliography.asciidoc[] 68 | 69 | -------------------------------------------------------------------------------- /buy_the_book_banner.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | buy the book ribbon 4 | 5 |
6 | -------------------------------------------------------------------------------- /callouts/1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/1.pdf -------------------------------------------------------------------------------- /callouts/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/1.png -------------------------------------------------------------------------------- /callouts/10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/10.pdf -------------------------------------------------------------------------------- /callouts/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/10.png -------------------------------------------------------------------------------- /callouts/11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/11.pdf -------------------------------------------------------------------------------- /callouts/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/11.png -------------------------------------------------------------------------------- /callouts/12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/12.pdf -------------------------------------------------------------------------------- /callouts/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/12.png -------------------------------------------------------------------------------- /callouts/13.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/13.pdf -------------------------------------------------------------------------------- /callouts/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/13.png -------------------------------------------------------------------------------- /callouts/14.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/14.pdf -------------------------------------------------------------------------------- /callouts/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/14.png -------------------------------------------------------------------------------- /callouts/15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/15.pdf -------------------------------------------------------------------------------- /callouts/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/15.png -------------------------------------------------------------------------------- /callouts/16.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/16.pdf -------------------------------------------------------------------------------- /callouts/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/16.png -------------------------------------------------------------------------------- /callouts/17.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/17.pdf -------------------------------------------------------------------------------- /callouts/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/17.png -------------------------------------------------------------------------------- /callouts/18.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/18.pdf -------------------------------------------------------------------------------- /callouts/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/18.png -------------------------------------------------------------------------------- /callouts/19.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/19.pdf -------------------------------------------------------------------------------- /callouts/19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/19.png -------------------------------------------------------------------------------- /callouts/2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/2.pdf -------------------------------------------------------------------------------- /callouts/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/2.png -------------------------------------------------------------------------------- /callouts/20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/20.pdf -------------------------------------------------------------------------------- /callouts/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/20.png -------------------------------------------------------------------------------- /callouts/21.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/21.pdf -------------------------------------------------------------------------------- /callouts/21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/21.png -------------------------------------------------------------------------------- /callouts/22.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/22.pdf -------------------------------------------------------------------------------- /callouts/22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/22.png -------------------------------------------------------------------------------- /callouts/23.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/23.pdf -------------------------------------------------------------------------------- /callouts/23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/23.png -------------------------------------------------------------------------------- /callouts/24.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/24.pdf -------------------------------------------------------------------------------- /callouts/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/24.png -------------------------------------------------------------------------------- /callouts/25.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/25.pdf -------------------------------------------------------------------------------- /callouts/25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/25.png -------------------------------------------------------------------------------- /callouts/26.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/26.pdf -------------------------------------------------------------------------------- /callouts/26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/26.png -------------------------------------------------------------------------------- /callouts/27.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/27.pdf -------------------------------------------------------------------------------- /callouts/27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/27.png -------------------------------------------------------------------------------- /callouts/28.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/28.pdf -------------------------------------------------------------------------------- /callouts/28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/28.png -------------------------------------------------------------------------------- /callouts/29.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/29.pdf -------------------------------------------------------------------------------- /callouts/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/29.png -------------------------------------------------------------------------------- /callouts/3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/3.pdf -------------------------------------------------------------------------------- /callouts/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/3.png -------------------------------------------------------------------------------- /callouts/30.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/30.pdf -------------------------------------------------------------------------------- /callouts/30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/30.png -------------------------------------------------------------------------------- /callouts/31.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/31.pdf -------------------------------------------------------------------------------- /callouts/31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/31.png -------------------------------------------------------------------------------- /callouts/32.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/32.pdf -------------------------------------------------------------------------------- /callouts/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/32.png -------------------------------------------------------------------------------- /callouts/33.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/33.pdf -------------------------------------------------------------------------------- /callouts/33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/33.png -------------------------------------------------------------------------------- /callouts/34.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/34.pdf -------------------------------------------------------------------------------- /callouts/34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/34.png -------------------------------------------------------------------------------- /callouts/35.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/35.pdf -------------------------------------------------------------------------------- /callouts/35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/35.png -------------------------------------------------------------------------------- /callouts/36.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/36.pdf -------------------------------------------------------------------------------- /callouts/36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/36.png -------------------------------------------------------------------------------- /callouts/37.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/37.pdf -------------------------------------------------------------------------------- /callouts/37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/37.png -------------------------------------------------------------------------------- /callouts/38.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/38.pdf -------------------------------------------------------------------------------- /callouts/38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/38.png -------------------------------------------------------------------------------- /callouts/39.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/39.pdf -------------------------------------------------------------------------------- /callouts/39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/39.png -------------------------------------------------------------------------------- /callouts/4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/4.pdf -------------------------------------------------------------------------------- /callouts/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/4.png -------------------------------------------------------------------------------- /callouts/5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/5.pdf -------------------------------------------------------------------------------- /callouts/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/5.png -------------------------------------------------------------------------------- /callouts/6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/6.pdf -------------------------------------------------------------------------------- /callouts/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/6.png -------------------------------------------------------------------------------- /callouts/7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/7.pdf -------------------------------------------------------------------------------- /callouts/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/7.png -------------------------------------------------------------------------------- /callouts/8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/8.pdf -------------------------------------------------------------------------------- /callouts/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/8.png -------------------------------------------------------------------------------- /callouts/9.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/9.pdf -------------------------------------------------------------------------------- /callouts/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/callouts/9.png -------------------------------------------------------------------------------- /coderay-asciidoctor.css: -------------------------------------------------------------------------------- 1 | /*! Stylesheet for CodeRay to loosely match GitHub themes | MIT License */ 2 | pre.CodeRay{background:#f7f7f8} 3 | .CodeRay .line-numbers{border-right:1px solid;opacity:.35;padding:0 .5em 0 0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} 4 | .CodeRay span.line-numbers{display:inline-block;margin-right:.75em} 5 | .CodeRay .line-numbers strong{color:#000} 6 | table.CodeRay{border-collapse:separate;border:0;margin-bottom:0;background:none} 7 | table.CodeRay td{vertical-align:top;line-height:inherit} 8 | table.CodeRay td.line-numbers{text-align:right} 9 | table.CodeRay td.code{padding:0 0 0 .75em} 10 | .CodeRay .debug{color:#fff!important;background:navy!important} 11 | .CodeRay .annotation{color:#007} 12 | .CodeRay .attribute-name{color:navy} 13 | .CodeRay .attribute-value{color:#700} 14 | .CodeRay .binary{color:#509} 15 | .CodeRay .comment{color:#998;font-style:italic} 16 | .CodeRay .char{color:#04d} 17 | .CodeRay .char .content{color:#04d} 18 | .CodeRay .char .delimiter{color:#039} 19 | .CodeRay .class{color:#458;font-weight:bold} 20 | .CodeRay .complex{color:#a08} 21 | .CodeRay .constant,.CodeRay .predefined-constant{color:teal} 22 | .CodeRay .color{color:#099} 23 | .CodeRay .class-variable{color:#369} 24 | .CodeRay .decorator{color:#b0b} 25 | .CodeRay .definition{color:#099} 26 | .CodeRay .delimiter{color:#000} 27 | .CodeRay .doc{color:#970} 28 | .CodeRay .doctype{color:#34b} 29 | .CodeRay .doc-string{color:#d42} 30 | .CodeRay .escape{color:#666} 31 | .CodeRay .entity{color:#800} 32 | .CodeRay .error{color:#808} 33 | .CodeRay .exception{color:inherit} 34 | .CodeRay .filename{color:#099} 35 | .CodeRay .function{color:#900;font-weight:bold} 36 | .CodeRay .global-variable{color:teal} 37 | .CodeRay .hex{color:#058} 38 | .CodeRay .integer,.CodeRay .float{color:#099} 39 | .CodeRay .include{color:#555} 40 | .CodeRay .inline{color:#000} 41 | .CodeRay .inline .inline{background:#ccc} 42 | .CodeRay .inline .inline .inline{background:#bbb} 43 | .CodeRay .inline .inline-delimiter{color:#d14} 44 | .CodeRay .inline-delimiter{color:#d14} 45 | .CodeRay .important{color:#555;font-weight:bold} 46 | .CodeRay .interpreted{color:#b2b} 47 | .CodeRay .instance-variable{color:teal} 48 | .CodeRay .label{color:#970} 49 | .CodeRay .local-variable{color:#963} 50 | .CodeRay .octal{color:#40e} 51 | .CodeRay .predefined{color:#369} 52 | .CodeRay .preprocessor{color:#579} 53 | .CodeRay .pseudo-class{color:#555} 54 | .CodeRay .directive{font-weight:bold} 55 | .CodeRay .type{font-weight:bold} 56 | .CodeRay .predefined-type{color:inherit} 57 | .CodeRay .reserved,.CodeRay .keyword{color:#000;font-weight:bold} 58 | .CodeRay .key{color:#808} 59 | .CodeRay .key .delimiter{color:#606} 60 | .CodeRay .key .char{color:#80f} 61 | .CodeRay .value{color:#088} 62 | .CodeRay .regexp .delimiter{color:#808} 63 | .CodeRay .regexp .content{color:#808} 64 | .CodeRay .regexp .modifier{color:#808} 65 | .CodeRay .regexp .char{color:#d14} 66 | .CodeRay .regexp .function{color:#404;font-weight:bold} 67 | .CodeRay .string{color:#d20} 68 | .CodeRay .string .string .string{background:#ffd0d0} 69 | .CodeRay .string .content{color:#d14} 70 | .CodeRay .string .char{color:#d14} 71 | .CodeRay .string .delimiter{color:#d14} 72 | .CodeRay .shell{color:#d14} 73 | .CodeRay .shell .delimiter{color:#d14} 74 | .CodeRay .symbol{color:#990073} 75 | .CodeRay .symbol .content{color:#a60} 76 | .CodeRay .symbol .delimiter{color:#630} 77 | .CodeRay .tag{color:teal} 78 | .CodeRay .tag-special{color:#d70} 79 | .CodeRay .variable{color:#036} 80 | .CodeRay .insert{background:#afa} 81 | .CodeRay .delete{background:#faa} 82 | .CodeRay .change{color:#aaf;background:#007} 83 | .CodeRay .head{color:#f8f;background:#505} 84 | .CodeRay .insert .insert{color:#080} 85 | .CodeRay .delete .delete{color:#800} 86 | .CodeRay .change .change{color:#66f} 87 | .CodeRay .head .head{color:#f4f} -------------------------------------------------------------------------------- /colo.html: -------------------------------------------------------------------------------- 1 |
2 |

Colophon

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 |
17 | -------------------------------------------------------------------------------- /count-todos.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import datetime 3 | import re 4 | import sys 5 | from pathlib import Path 6 | 7 | MARKERS = ["TODO", "RITA", "DAVID", "SEBASTIAN", "JAN", "CSANAD"] 8 | 9 | out = csv.writer(sys.stdout) 10 | out.writerow(["Date", "Chapter"] + MARKERS) 11 | today = datetime.date.today() 12 | for path in sorted( 13 | list(Path(".").rglob("chapter*.asciidoc")) 14 | + list(Path(".").rglob("appendix*.asciidoc")) 15 | ): 16 | chapter_name = str(path).replace(".asciidoc", "") 17 | contents = path.read_text() 18 | todos = [len(re.findall(rf"\b{thing}\b", contents)) for thing in MARKERS] 19 | out.writerow([today.isoformat(), chapter_name] + todos) 20 | -------------------------------------------------------------------------------- /cover.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /disqus_comments.html: -------------------------------------------------------------------------------- 1 |
2 |

Comments

3 |
4 | 16 | 17 |
18 | 19 | -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/640-screen2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/640-screen2.gif -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/Content.css: -------------------------------------------------------------------------------- 1 | /* 2 | ShareMeNot is licensed under the MIT license: 3 | http://www.opensource.org/licenses/mit-license.php 4 | 5 | 6 | Copyright (c) 2012 University of Washington 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | /* 29 | * Every property is !important to prevent any styles declared on the web page 30 | * from overriding ours. 31 | */ 32 | 33 | .sharemenotReplacementButton { 34 | border: none !important; 35 | cursor: pointer !important; 36 | height: auto !important; 37 | width: auto !important; 38 | } 39 | 40 | .sharemenotOriginalButton { 41 | border: none !important; 42 | height: 1.5em !important; 43 | } -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/asciidoc.asc: -------------------------------------------------------------------------------- 1 | Not found: /doc/javascripts/867/asciidoc.js 2 | -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/caution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/caution.png -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/home.png -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/important.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/important.png -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/note.png -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/tip.png -------------------------------------------------------------------------------- /docs/asciidoc-cheatsheet_files/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-cheatsheet_files/warning.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/1.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/2.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/3.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/Content.css: -------------------------------------------------------------------------------- 1 | /* 2 | ShareMeNot is licensed under the MIT license: 3 | http://www.opensource.org/licenses/mit-license.php 4 | 5 | 6 | Copyright (c) 2012 University of Washington 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | /* 29 | * Every property is !important to prevent any styles declared on the web page 30 | * from overriding ours. 31 | */ 32 | 33 | .sharemenotReplacementButton { 34 | border: none !important; 35 | cursor: pointer !important; 36 | height: auto !important; 37 | width: auto !important; 38 | } 39 | 40 | .sharemenotOriginalButton { 41 | border: none !important; 42 | height: 1.5em !important; 43 | } -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/layout2.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | #layout-menu-box { 6 | position: fixed; 7 | left: 0px; 8 | top: 0px; 9 | width: 160px; 10 | height: 100%; 11 | z-index: 1; 12 | background-color: #f4f4f4; 13 | } 14 | 15 | #layout-content-box { 16 | position: relative; 17 | margin-left: 160px; 18 | background-color: white; 19 | } 20 | 21 | h1 { 22 | margin-top: 0.5em; 23 | } 24 | 25 | #layout-banner { 26 | color: white; 27 | background-color: #73a0c5; 28 | font-family: Arial,Helvetica,sans-serif; 29 | text-align: left; 30 | padding: 0.8em 20px; 31 | } 32 | 33 | #layout-title { 34 | font-family: "Courier New", Courier, monospace; 35 | font-size: 3.5em; 36 | font-weight: bold; 37 | letter-spacing: 0.2em; 38 | margin: 0; 39 | } 40 | 41 | #layout-description { 42 | font-size: 1.2em; 43 | letter-spacing: 0.1em; 44 | } 45 | 46 | #layout-menu { 47 | height: 100%; 48 | border-right: 3px solid #eeeeee; 49 | padding-top: 0.8em; 50 | padding-left: 15px; 51 | padding-right: 0.8em; 52 | font-size: 1.0em; 53 | font-family: Arial,Helvetica,sans-serif; 54 | font-weight: bold; 55 | } 56 | #layout-menu a { 57 | line-height: 2em; 58 | margin-left: 0.5em; 59 | } 60 | #layout-menu a:link, #layout-menu a:visited, #layout-menu a:hover { 61 | color: #527bbd; 62 | text-decoration: none; 63 | } 64 | #layout-menu a:hover { 65 | color: navy; 66 | text-decoration: none; 67 | } 68 | #layout-menu #page-source { 69 | border-top: 2px solid silver; 70 | margin-top: 0.2em; 71 | } 72 | 73 | #layout-content { 74 | padding-top: 0.2em; 75 | padding-left: 1.0em; 76 | padding-right: 0.4em; 77 | } 78 | 79 | @media print { 80 | #layout-banner-box { display: none; } 81 | #layout-menu-box { display: none; } 82 | #layout-content-box { margin-top: 0; margin-left: 0; } 83 | } 84 | -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/note.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/tip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/tip.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/valid-xhtml11-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/valid-xhtml11-blue.png -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/vcss-blue.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/vcss-blue.gif -------------------------------------------------------------------------------- /docs/asciidoc-userguide_files/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/docs/asciidoc-userguide_files/warning.png -------------------------------------------------------------------------------- /downloads/bootstrap.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/downloads/bootstrap.zip -------------------------------------------------------------------------------- /epilogue.asciidoc: -------------------------------------------------------------------------------- 1 | [appendix] 2 | [role="afterword"] 3 | Obey the Testing Goat! 4 | ---------------------- 5 | 6 | Back to the Testing Goat. 7 | 8 | 'Groan', I hear you say, 'Harry, the Testing Goat stopped being funny about 9 | 17 chapters ago'. Bear with me, I'm going to use it to make a serious point. 10 | 11 | Testing Is Hard 12 | ~~~~~~~~~~~~~~~ 13 | 14 | 15 | 16 | ((("Testing Goat", "philosophy of")))I 17 | think the reason the phrase "Obey the Testing Goat" first grabbed me when I 18 | saw it was that it really spoke to the fact that testing is hard--not hard to 19 | do in and of itself, but hard to 'stick to', and hard to keep doing. 20 | 21 | It always feels easier to cut corners and skip a few tests. And it's doubly 22 | hard psychologically because the payoff is so disconnected from the point at 23 | which you put in the effort. A test you spend time writing now doesn't reward 24 | you immediately, it only helps much later--perhaps months later when it saves 25 | you from introducing a bug while refactoring, or catches a regression when you 26 | upgrade a dependency. Or, perhaps it pays you back in a way that's hard to 27 | measure, by encouraging you to write better designed code, but you convince 28 | yourself you could have written it just as elegantly without tests. 29 | 30 | I myself started slipping when I was writing the 31 | https://github.com/hjwp/Book-TDD-Web-Dev-Python/tree/master/tests[test 32 | framework for this book]. Being a quite complex beast, it has tests of its 33 | own, but I cut several corners, coverage isn't perfect, and I now regret it 34 | because it's turned out quite unwieldy and ugly (go on, I've open sourced it 35 | now, so you can all point and laugh). 36 | 37 | 38 | Keep Your CI Builds Green 39 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 40 | 41 | 42 | ((("Continuous Integration (CI)", "tips")))Another 43 | area that takes real hard work is continuous integration. You saw in 44 | <> that strange and unpredictable bugs sometimes occur on CI. 45 | When you're looking at these and thinking "it works fine on my machine", 46 | there's a strong temptation to just ignore them...but, if you're not careful, 47 | you start to tolerate a failing test suite in CI, and pretty soon your CI build 48 | is actually useless, and it feels like too much work to get it going again. 49 | Don't fall into that trap. Persist, and you'll find the reason that your test 50 | is failing, and you'll find a way to lock it down and make it deterministic, 51 | and green, again. 52 | 53 | 54 | Take Pride in Your Tests, as You Do in Your Code 55 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 56 | 57 | One of the things that helps is to stop thinking of your tests as being an 58 | incidental add-on to the "real" code, and to start thinking of them as being 59 | a part of the finished product that you're building--a part that should be 60 | just as finely polished, just as aesthetically pleasing, and a part you can 61 | be justly proud of delivering... 62 | 63 | 64 | So do it because the Testing Goat says so. Do it because you know the payoff 65 | will be worth it, even if it's not immediate. Do it out of a sense of duty, 66 | or professionalism, or OCD, or sheer bloody-mindedness. Do it because it's 67 | a good thing to practice. And, eventually, do it because it makes software 68 | development more fun. 69 | 70 | //something about pairing? 71 | 72 | 73 | Remember to Tip the Bar Staff 74 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 75 | 76 | This book wouldn't have been possible without the backing of my publisher, 77 | the wonderful O'Reilly Media. If you're reading the free edition online, 78 | I hope you'll consider 79 | https://shop.oreilly.com/product/0636920051091.do[buying a real copy]...if you 80 | don't need one for yourself, then maybe as a gift for a friend? 81 | 82 | 83 | Don't Be a Stranger! 84 | ~~~~~~~~~~~~~~~~~~~~ 85 | 86 | I hope you enjoyed the book. Do get in touch and tell me what you thought! 87 | 88 | Harry. 89 | 90 | * https://fosstodon.org/@hjwp 91 | * obeythetestinggoat@gmail.com 92 | 93 | -------------------------------------------------------------------------------- /images/ansible-and-ssh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/ansible-and-ssh.png -------------------------------------------------------------------------------- /images/bootsrap_css_404_devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/bootsrap_css_404_devtools.png -------------------------------------------------------------------------------- /images/car-factory-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/car-factory-illustration.png -------------------------------------------------------------------------------- /images/containers-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/containers-diagram.png -------------------------------------------------------------------------------- /images/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/cover.png -------------------------------------------------------------------------------- /images/devtools-post-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/devtools-post-request.png -------------------------------------------------------------------------------- /images/devtools_closeup_edit_html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/devtools_closeup_edit_html.png -------------------------------------------------------------------------------- /images/devtools_error_div_hidden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/devtools_error_div_hidden.png -------------------------------------------------------------------------------- /images/django_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/django_400.png -------------------------------------------------------------------------------- /images/django_operationalerror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/django_operationalerror.png -------------------------------------------------------------------------------- /images/double-loop-tdd-simpler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/double-loop-tdd-simpler.png -------------------------------------------------------------------------------- /images/duplicate_item_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/duplicate_item_error.png -------------------------------------------------------------------------------- /images/editing-html-via-devtools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/editing-html-via-devtools.png -------------------------------------------------------------------------------- /images/error-gone-but-input-still-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/error-gone-but-input-still-red.png -------------------------------------------------------------------------------- /images/firefox-connection-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/firefox-connection-reset.png -------------------------------------------------------------------------------- /images/firefox-unable-to-connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/firefox-unable-to-connect.png -------------------------------------------------------------------------------- /images/firefox_upgrade_popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/firefox_upgrade_popup.png -------------------------------------------------------------------------------- /images/gandi_add_dns_a_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gandi_add_dns_a_record.png -------------------------------------------------------------------------------- /images/git_windows_installer_choose_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/git_windows_installer_choose_editor.png -------------------------------------------------------------------------------- /images/gitlab_files_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_files_ui.png -------------------------------------------------------------------------------- /images/gitlab_first_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_first_build.png -------------------------------------------------------------------------------- /images/gitlab_new_blank_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_new_blank_project.png -------------------------------------------------------------------------------- /images/gitlab_pipeline_js_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_pipeline_js_success.png -------------------------------------------------------------------------------- /images/gitlab_pipeline_overview_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_pipeline_overview_success.png -------------------------------------------------------------------------------- /images/gitlab_pipeline_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_pipeline_success.png -------------------------------------------------------------------------------- /images/gitlab_ui_for_browse_artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_ui_for_browse_artifacts.png -------------------------------------------------------------------------------- /images/gitlab_ui_show_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/gitlab_ui_show_screenshot.png -------------------------------------------------------------------------------- /images/google-results-with-stackoverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/google-results-with-stackoverflow.png -------------------------------------------------------------------------------- /images/homepage_no_css_8888.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/homepage_no_css_8888.png -------------------------------------------------------------------------------- /images/html5-validation-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/html5-validation-popup.png -------------------------------------------------------------------------------- /images/integrity_error_unique_constraint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/integrity_error_unique_constraint.png -------------------------------------------------------------------------------- /images/jasmine-console-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jasmine-console-logs.png -------------------------------------------------------------------------------- /images/jasmine-in-browser-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jasmine-in-browser-green.png -------------------------------------------------------------------------------- /images/jasmine-in-browser-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jasmine-in-browser-red.png -------------------------------------------------------------------------------- /images/jenkins-admin-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jenkins-admin-user.png -------------------------------------------------------------------------------- /images/jenkins-unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jenkins-unlock.png -------------------------------------------------------------------------------- /images/jenkins-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/jenkins-welcome.png -------------------------------------------------------------------------------- /images/js-errordiv-is-null-in-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/js-errordiv-is-null-in-console.png -------------------------------------------------------------------------------- /images/list_with_sharing_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/list_with_sharing_options.png -------------------------------------------------------------------------------- /images/login-email-sent-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/login-email-sent-page.png -------------------------------------------------------------------------------- /images/login-link-in-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/login-link-in-email.png -------------------------------------------------------------------------------- /images/magic-links-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/magic-links-overview.png -------------------------------------------------------------------------------- /images/multiple-lists-users-and-urls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/multiple-lists-users-and-urls.png -------------------------------------------------------------------------------- /images/new_jenkins_phantomjs_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/new_jenkins_phantomjs_screenshot.png -------------------------------------------------------------------------------- /images/orly-essential-googling-the-error-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/orly-essential-googling-the-error-message.png -------------------------------------------------------------------------------- /images/outside-in-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/outside-in-layers.png -------------------------------------------------------------------------------- /images/paperbottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/paperbottom.png -------------------------------------------------------------------------------- /images/papermiddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/papermiddle.png -------------------------------------------------------------------------------- /images/papertop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/papertop.png -------------------------------------------------------------------------------- /images/prettified-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/prettified-1.png -------------------------------------------------------------------------------- /images/prettified-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/prettified-2.png -------------------------------------------------------------------------------- /images/prettified-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/prettified-dark.png -------------------------------------------------------------------------------- /images/prettified-final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/prettified-final.png -------------------------------------------------------------------------------- /images/python_install_add_to_path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/python_install_add_to_path.png -------------------------------------------------------------------------------- /images/red-green-refactor-excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/red-green-refactor-excalidraw.png -------------------------------------------------------------------------------- /images/search-results-400-bad-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/search-results-400-bad-request.png -------------------------------------------------------------------------------- /images/server_error_500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/server_error_500.png -------------------------------------------------------------------------------- /images/single-endpoint-for-forms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/single-endpoint-for-forms.png -------------------------------------------------------------------------------- /images/site-in-docker-is-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/site-in-docker-is-up.png -------------------------------------------------------------------------------- /images/spike-it-worked-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/spike-it-worked-windows.png -------------------------------------------------------------------------------- /images/tdd-process-unit-tests-only-excalidraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/tdd-process-unit-tests-only-excalidraw.png -------------------------------------------------------------------------------- /images/todo_list_table_disappeared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/todo_list_table_disappeared.png -------------------------------------------------------------------------------- /images/twp2_00in01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_00in01.png -------------------------------------------------------------------------------- /images/twp2_0101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0101.png -------------------------------------------------------------------------------- /images/twp2_0102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0102.png -------------------------------------------------------------------------------- /images/twp2_0103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0103.png -------------------------------------------------------------------------------- /images/twp2_0401.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0401.png -------------------------------------------------------------------------------- /images/twp2_0402.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0402.png -------------------------------------------------------------------------------- /images/twp2_0501.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0501.png -------------------------------------------------------------------------------- /images/twp2_0503.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0503.png -------------------------------------------------------------------------------- /images/twp2_0701.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0701.png -------------------------------------------------------------------------------- /images/twp2_0901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0901.png -------------------------------------------------------------------------------- /images/twp2_0902.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0902.png -------------------------------------------------------------------------------- /images/twp2_0902a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0902a.png -------------------------------------------------------------------------------- /images/twp2_0904.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_0904.png -------------------------------------------------------------------------------- /images/twp2_1101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_1101.png -------------------------------------------------------------------------------- /images/twp2_1601.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_1601.png -------------------------------------------------------------------------------- /images/twp2_1602.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_1602.png -------------------------------------------------------------------------------- /images/twp2_1603.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_1603.png -------------------------------------------------------------------------------- /images/twp2_1901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_1901.png -------------------------------------------------------------------------------- /images/twp2_2001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2001.png -------------------------------------------------------------------------------- /images/twp2_2201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2201.png -------------------------------------------------------------------------------- /images/twp2_2404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2404.png -------------------------------------------------------------------------------- /images/twp2_2405.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2405.png -------------------------------------------------------------------------------- /images/twp2_2406.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2406.png -------------------------------------------------------------------------------- /images/twp2_2407.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2407.png -------------------------------------------------------------------------------- /images/twp2_2408.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2408.png -------------------------------------------------------------------------------- /images/twp2_2409.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2409.png -------------------------------------------------------------------------------- /images/twp2_2410.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2410.png -------------------------------------------------------------------------------- /images/twp2_2411.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2411.png -------------------------------------------------------------------------------- /images/twp2_2412.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2412.png -------------------------------------------------------------------------------- /images/twp2_2601.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_2601.png -------------------------------------------------------------------------------- /images/twp2_ad01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_ad01.png -------------------------------------------------------------------------------- /images/twp2_ae01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_ae01.png -------------------------------------------------------------------------------- /images/twp2_ag01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_ag01.png -------------------------------------------------------------------------------- /images/twp2_ah01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/twp2_ah01.png -------------------------------------------------------------------------------- /images/typeerror_in_devools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/typeerror_in_devools.png -------------------------------------------------------------------------------- /images/ugly-homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/ugly-homepage.png -------------------------------------------------------------------------------- /images/virtualenv-vs-vm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/virtualenv-vs-vm.png -------------------------------------------------------------------------------- /images/wrong_order_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/images/wrong_order_list.png -------------------------------------------------------------------------------- /ix.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /load_toc.js: -------------------------------------------------------------------------------- 1 | var httpRequest = new XMLHttpRequest(); 2 | httpRequest.onreadystatechange = function() { 3 | if (httpRequest.readyState === XMLHttpRequest.DONE) { 4 | if (httpRequest.status === 200) { 5 | document.getElementById('header').innerHTML += httpRequest.responseText; 6 | var subheaders = document.getElementsByClassName('sectlevel2'); 7 | var section; 8 | for (var i=0; i 4: 13 | break 14 | for field in reader.fieldnames: 15 | if 'words' in field: 16 | val = row[field] 17 | if val: 18 | fixed_row[field] = val 19 | else: 20 | fixed_row[field] = 0 21 | date = datetime(int(row['date.year']), int(row['date.month']), int(row['date.day']), int(row['date.hour']),) 22 | fixed_row['date'] = date 23 | data.append(fixed_row) 24 | return data 25 | 26 | 27 | # print(len(data)) 28 | data = {} 29 | data['date'] = [1, 4, 3, 2] 30 | data['words1'] = [0, 3, 3, 5] 31 | data['words2'] = [4, 6, 0, 2] 32 | array = [data['date'], data['words1'], data['words2']] 33 | numpy.sort(array, 0) 34 | 35 | data = get_data_from_csv() 36 | data.sort(key=lambda d: d['date']) 37 | x = [d['date'] for d in data] 38 | y = [ 39 | [d[key] for d in data] 40 | for key in data[0].keys() if 'words' in key 41 | ] 42 | pyplot.stackplot(x, y) 43 | 44 | # pyplot.stackplot(data['date'], [values for (field, values) in data.items() if 'words' in field]) 45 | # for (field, values) in data.items(): 46 | # if 'words' in field: 47 | # pyplot.plot(data['date'], values) 48 | 49 | pyplot.show() 50 | -------------------------------------------------------------------------------- /misc/reddit_post.md: -------------------------------------------------------------------------------- 1 | Hi Python-Redditors!, 2 | 3 | I'm somehow writing a book on TDD and Python for O'Reilly, despite feeling massively underqualified. Please help me to instill the book with more wisdom than I can muster on my own! 4 | 5 | Here's a link where you can take a look at what I have so far (8 chapters): 6 | 7 | http://www.obeythetestinggoat.com 8 | 9 | **My background:** 10 | 11 | I've only been a professional programmer for about 4 years, but I was lucky enough to fall in with a bunch of XP fanatics at Resolver Systems, now [PythonAnywhere](http://www.pythonanywhere.com). Over the past few years I've learned an awful lot, and I'm trying to share what I've learned with other people who are starting out. Someone described learning TDD as being a step on the journey from amateur coder to professional, maybe that's a good way of putting it. 12 | 13 | For the past couple of years I've been running beginners' TDD / Django workshops at EuroPython and Pycons, and they've been well-received. They're probably what must have fooled O'Reilly into thinking I could write a book! 14 | 15 | **The book** 16 | 17 | The concept is to take the user through building a web app from scratch, but using full TDD all along the way. That involves: 18 | 19 | * Functional tests using Selenium 20 | * "unit" tests using the Django test runner 21 | * All Django goodness including views, models, templates, forms, admin etc 22 | * Unit testing javascript 23 | * Tips on deployment, and how to test against a staging site 24 | 25 | ...and lots more. You'll find a [proposed chapter outline](http://chimera.labs.oreilly.com/books/1234000000754/ch09.html) at the end of the book. 26 | 27 | I've made a good start, 8 chapters, taking us all the way to deploying a minimum viable app, but I really need some more feedback. 28 | 29 | * Am I covering the right stuff? Would you add / remove any topics? 30 | * Am I teaching what you consider to be best practice? Some people, for example, think that touching the database in a thing you call a unit test is an absolute no-no (cf [past discussion on reddit](http://www.reddit.com/r/django/comments/1c67rl/is_tddjangotutorial_truly_a_good_resource_i_want/)). Do you think that's important? Should I acknowledge the controversy, and maybe refer people to an appendix which discusses how to avoid hitting the db in unit tests? Or should I throw out the Django test runner? 31 | * I'm also telling people to use a very belt & braces technique, where we write functional tests *and* unit tests for pretty much every single line of code. Is that overkill? Or do you think that it's a good thing to learn, even if you later relax the rules when you get a bit more experience (I make this argument in chapter 4) 32 | * My latest chapter is [all about deployment](http://chimera.labs.oreilly.com/books/1234000000754/ch08.html) -- lots of people do different things in this area, we had a good discussion of it on the Python-UK mailing list. What do you think of what I have so far? 33 | 34 | 35 | **Why help?** 36 | 37 | Perhaps you're thinking "Why should I help this guy to write a better book and make more money, despite the blatant massive lacunae in his knowledge?". 38 | 39 | Well, one thing that I hope will sway your cold, hard heart is that I am pledging to make the book available for free under a CC license, alongside the official paid-for printed and ebook versions. I insisted on that as part of my contract, and O'Reilly were totally happy with it (props to them for being forward-thinking). I really hope that this book can distill something of the sum total of all the knowledge in the Python testing community, over and above my own, and be a useful resource for that whole community, and not just those that can afford to pay for a book... 40 | 41 | If that's not enough, how about I promise I'll buy you a beer one day? 42 | 43 | 44 | Thanks in advance, 45 | Harry 46 | 47 | -------------------------------------------------------------------------------- /misc/tdd-flowchart.dot: -------------------------------------------------------------------------------- 1 | digraph g { 2 | 3 | write_test [label="Write a test" shape=box] 4 | if_passes [label="Run the test.\nDoes it pass?" shape=diamond] 5 | code [label="Write minimal code" shape=box] 6 | refactor [label="Refactor (optional)" shape=box] 7 | 8 | subgraph along { 9 | rank = same; 10 | write_test -> if_passes ; 11 | if_passes -> refactor [label="Yes" color = green] ; 12 | refactor -> write_test; 13 | } 14 | subgraph down { 15 | rankdir = UD; 16 | if_passes -> code:w [label="No" color = red] ; 17 | } 18 | 19 | code:e -> if_passes 20 | 21 | } 22 | -------------------------------------------------------------------------------- /misc/tdd_diagram.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/misc/tdd_diagram.odp -------------------------------------------------------------------------------- /part1.asciidoc: -------------------------------------------------------------------------------- 1 | [[part1]] 2 | [part] 3 | [role="pagenumrestart"] 4 | == The Basics of TDD and Django 5 | 6 | [partintro] 7 | -- 8 | In this first part, I'm going to introduce the basics of 'Test-Driven 9 | Development' (TDD). We'll build a real web application from scratch, writing tests first at every stage. 10 | 11 | We'll cover functional testing with Selenium, as well as unit testing, 12 | and see the difference between the two. 13 | I'll introduce the TDD workflow, Red/Green/Refactor. 14 | 15 | I'll also be using a version control system (Git). 16 | We'll discuss how and when to do commits and integrate them with the TDD and web development workflow. 17 | 18 | We'll be using Django, the Python world's most popular web framework (probably). 19 | I've tried to introduce the Django concepts slowly and one at a time, 20 | and provide lots of links to further reading. 21 | If you're a total beginner to Django, I thoroughly recommend taking the time to read them. 22 | If you find yourself feeling a bit lost, 23 | take a couple of hours to go through the https://docs.djangoproject.com/en/5.2/intro/[official Django tutorial] 24 | and then come back to the book. 25 | 26 | In Part 1 you'll also get to meet the Testing Goat... 27 | 28 | .Be Careful with Copy and Paste 29 | [TIP] 30 | ==== 31 | If you're working from a digital version of the book, 32 | it's natural to want to copy and paste code listings from the book as you're working through it. 33 | It's much better if you don't: typing things in by hand gets them into your muscle memory, 34 | and just feels much more real. 35 | You also inevitably make the occasional typo, and debugging them is an important thing to learn. 36 | 37 | Quite apart from that, you'll find that the quirks of the PDF format 38 | mean that weird stuff often happens when you try to copy/paste from it... 39 | ==== 40 | 41 | -- 42 | -------------------------------------------------------------------------------- /part3.asciidoc: -------------------------------------------------------------------------------- 1 | [[part3]] 2 | [part] 3 | == Forms and Validation 4 | 5 | [partintro] 6 | -- 7 | Now that we've got things into production, 8 | we'll spend a bit of time on validation, 9 | a core topic in web development. 10 | 11 | There's quite a lot of Django-specific content in this part, 12 | so if you weren't familiar with Django before starting on the book, 13 | you may find that taking a little time to run through the 14 | https://docs.djangoproject.com/en/1.11/intro/tutorial01/#creating-models[official Django tutorial] 15 | will complement the next few chapters nicely. 16 | 17 | With that said, there are lots of good lessons about TDD in general in here too! 18 | So, alternatively, if you're not that interested in Django itself, 19 | don't worry too much about the details, 20 | but instead, look out for the more general principles of testing. 21 | 22 | Here's a little preview of what we'll cover: 23 | 24 | * Splitting tests out across multiple files. 25 | 26 | * Using a decorator for Selenium waits/polling. 27 | 28 | * Database-layer validation and constraints. 29 | 30 | * HTML5 form validation in the frontend. 31 | 32 | * The Django Forms framework. 33 | 34 | * The tradeoffs of frameworks in general, and when to stop using them. 35 | 36 | * How far to go when testing for possible coding errors. 37 | 38 | * An overview of all the typical tests for Django views. 39 | 40 | -- 41 | -------------------------------------------------------------------------------- /part4.asciidoc: -------------------------------------------------------------------------------- 1 | [[part4]] 2 | [part] 3 | == More Advanced Topics in Testing 4 | 5 | [partintro] 6 | -- 7 | 8 | "Oh my gosh, what? Another section? Harry, I'm exhausted, it's already 9 | been three hundred pages, I don't think I can handle a whole ’nother section 10 | of the book. Particularly not if it's called 'Advanced’...maybe I can 11 | get away with just skipping it?" 12 | 13 | Oh no, you can't! This may be called the advanced section, 14 | but it's full of really important topics for TDD and web development. 15 | No way can you skip it. 16 | If anything, it's 'even more important' than the first two sections. 17 | 18 | First off, we'll get into that _sine qua non_ of web development, JavaScript. 19 | Seeing how TDD works in another language can give you a whole new perspective. 20 | 21 | We'll be talking about a key technique, "spiking", 22 | which is where you relax the strict rules of TDD, 23 | and allow yourself a bit of exploratory hacking. 24 | 25 | TIP: A common objection to TDD is "how can I write tests if I don't even know what I'm doing?" 26 | Spiking is the bit where you get to play around and figure things out, 27 | so you can come back and do it test-first later. 28 | 29 | We'll be talking about how to integrate third-party systems, 30 | and how to test them. 31 | We'll cover mocking, which is a hard to avoid.footnote:[ 32 | Although not impossible! Check out the book www.cosmicpython.com[Cosmic Python], 33 | which has tips on testing without mocks. 34 | I happen to know that at least one of the two authors is incredibly wise.] 35 | in the world of Python testing. 36 | 37 | We'll talk about server-side debugging, and test fixtures, 38 | and how to set up a Continuous Integration environment. 39 | None of these things are take-it-or-leave-it optional luxury extras for your project--they're all 40 | vital! 41 | 42 | 43 | Inevitably, the learning curve does get a little steeper in this section. 44 | You may find yourself having to read things a couple of times before they sink in, 45 | or you may find that things don't work on the first go, 46 | and that you need to do a bit of debugging on your own. 47 | 48 | But I encourage you to persist with it! 49 | The harder it is, the more rewarding it is, right? 50 | And, remember, I'm always happy to help if you're stuck; 51 | just drop me an email at obeythetestinggoat@gmail.com. 52 | 53 | Come on; I promise the best is yet to come! 54 | -- 55 | -------------------------------------------------------------------------------- /pdf/drafts/intake.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/pdf/drafts/intake.pdf -------------------------------------------------------------------------------- /pdf/drafts/intake_with_remarks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/pdf/drafts/intake_with_remarks.pdf -------------------------------------------------------------------------------- /praise.forbook.asciidoc: -------------------------------------------------------------------------------- 1 | ["dedication", role="praise"] 2 | == Praise for 'Test-Driven Development with Python' 3 | 4 | [quote, Michael Foord, Python Core Developer and Maintainer of unittest] 5 | ____ 6 | “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.” 7 | ____ 8 | 9 | [quote, Kenneth Reitz, Fellow at Python Software Foundation] 10 | ____ 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.” 12 | ____ 13 | 14 | [quote, Daniel and Audrey Roy Greenfeld, authors of "Two Scoops of Django" (Two Scoops Press)] 15 | ____ 16 | “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!” 17 | ____ 18 | -------------------------------------------------------------------------------- /praise.html: -------------------------------------------------------------------------------- 1 |
2 |

Praise for Test-Driven Development with Python

3 |
4 |

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 |
7 |
8 |

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 |
11 |
12 |

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 |
15 |
16 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "goat-book" 7 | version = "0" 8 | 9 | # most requts are deliberately unpinned so we stay up to date with deps, 10 | # and CI will warn when things change. 11 | dependencies = [ 12 | "requests", 13 | "lxml", 14 | "lxml-stubs", 15 | "cssselect", 16 | "django<6", 17 | "django-types", 18 | "pygments", 19 | "docopt", 20 | "requests", 21 | "selenium<5", 22 | "pytest", 23 | # "pytest-xdist", 24 | "ruff", 25 | "black", # needed as a marker to tell django to use black 26 | "whitenoise", # from chap 10 on 27 | ] 28 | 29 | [tool.setuptools] 30 | packages = [] 31 | 32 | [tool.ruff.lint] 33 | select = [ 34 | # pycodestyle 35 | "E", 36 | # Pyflakes 37 | "F", 38 | # pyupgrade 39 | "UP", 40 | # flake8-bugbear 41 | "B", 42 | # flake8-simplify 43 | "SIM", 44 | # isort 45 | "I", 46 | # pylint 47 | "PL", 48 | ] 49 | ignore = [ 50 | "E741", # single-letter variable 51 | "E731", # allow lambdas 52 | "PLR2004", # magic values 53 | ] 54 | 55 | [tool.pyright] 56 | # these help pyright in neovide to find its way around 57 | venvPath = "." 58 | venv = ".venv" 59 | # most of the source for the book itself is untyped 60 | typeCheckingMode = "standard" 61 | 62 | [tool.pytest.ini_options] 63 | # -r N disables the post-test summary 64 | addopts = ["--tb=short", "-r N", "--color=yes"] 65 | 66 | -------------------------------------------------------------------------------- /rename-chapter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eux 3 | set -o pipefail 4 | 5 | OLD_CHAPTER=$1 6 | NEW_NAME=$2 7 | 8 | git mv "$OLD_CHAPTER.asciidoc" "$NEW_NAME.asciidoc" 9 | mv "$OLD_CHAPTER.html" "$NEW_NAME.html" || touch "$NEW_NAME.html" 10 | 11 | if [ -e "tests/test_$OLD_CHAPTER.py" ]; then 12 | git mv "tests/test_$OLD_CHAPTER.py" "tests/test_$NEW_NAME.py" 13 | fi 14 | 15 | git mv "source/$OLD_CHAPTER" "source/$NEW_NAME" 16 | 17 | cd "source/$NEW_NAME/superlists" 18 | git switch "$OLD_CHAPTER" 19 | git switch -c "$NEW_NAME" 20 | git push -u local 21 | git push -u origin 22 | cd ../../.. 23 | 24 | git grep -l "$OLD_CHAPTER" | xargs sed -i '' "s/$OLD_CHAPTER/$NEW_NAME/g" 25 | 26 | # make "test_$NEW_NAME" || echo -e "\a" 27 | 28 | echo git commit -am \'rename "$OLD_CHAPTER" to "$NEW_NAME".\' 29 | -------------------------------------------------------------------------------- /research/js-testing.rst: -------------------------------------------------------------------------------- 1 | Options: 2 | 3 | Qunit -- weird syntax, but seems popular 4 | YUI -- familiar 5 | jasmine - seems sane 6 | js-test-driver: by google, but seems to be server-only 7 | 8 | Sinon - for mocking 9 | 10 | http://www.letscodejavascript.com/ 11 | -------------------------------------------------------------------------------- /research/literary_agencies.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/research/literary_agencies.ods -------------------------------------------------------------------------------- /run_test_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PYTHONHASHSEED=0 pytest \ 3 | --failed-first \ 4 | --tb=short \ 5 | -k 'not test_listings_and_commands_and_output' \ 6 | "$@" \ 7 | tests/ 8 | # py.test --tb=short `ls tests/test_* | grep -v test_chapter | grep -v test_server` 9 | -------------------------------------------------------------------------------- /source/blackify-chap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PREV=$1 5 | CHAP=$2 6 | 7 | # assumes a git remote local pointing at a local bare repo... 8 | REPO=local 9 | 10 | cd $CHAP/superlists 11 | 12 | git fetch $REPO 13 | 14 | STARTCOMMIT="$(git rev-parse $PREV)" 15 | ENDCOMMIT="$(git rev-parse $CHAP)" 16 | 17 | git switch $CHAP 18 | git reset --hard $REPO/$PREV 19 | ruff format . 20 | git commit -am"initial black commit" --allow-empty 21 | 22 | git rev-list $STARTCOMMIT^..$ENDCOMMIT| tail -r | xargs -n1 sh -c 'git co $0 -- . && ruff format . && git add . && git stwdiff && git commit -am "$(git show -s --format=%B $0)"' 23 | 24 | git diff -w $REPO/$CHAP 25 | 26 | 27 | cd ../.. 28 | -------------------------------------------------------------------------------- /source/feed-thru-cherry-picks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # replay all the commits for a given chapter ($CHAP) 3 | # onto the latest version of the previous chapter ($PREV) 4 | set -e 5 | 6 | PREV=$1 7 | CHAP=$2 8 | 9 | # assumes a git remote called "local" (eg pointing at a local bare repo...) 10 | # rather than using origin/github, 11 | # this allows us to feed through changes without pushing to github 12 | REPO=local 13 | 14 | cd "$CHAP/superlists" 15 | 16 | # determine the commit we want to start from, 17 | # which is the last commit of the $PREV branch as it was *before* our new version. 18 | # We assume that the repo version in $CHAP/superlists has *not* got this latest version yet. 19 | # (so that's why we don't do the git fetch until after this step) 20 | START_COMMIT="$(git rev-list -n 1 "$REPO/$PREV")" 21 | CHAP_COMMIT_LIST="$START_COMMIT..$REPO/$CHAP" 22 | 23 | # check START_COMMIT exists in $CHAP branch's history 24 | # https://stackoverflow.com/a/4129070/366221 25 | if [ "$(git merge-base "$START_COMMIT" "$CHAP")" != "$START_COMMIT" ]; then 26 | echo "Error: $START_COMMIT is not in the history of $CHAP" 27 | exit 1 28 | fi 29 | 30 | # now we pull down the latest version of $PREV 31 | git fetch "$REPO" 32 | 33 | # reset our chapter to the new version of the end of $PREV 34 | git switch "$CHAP" 35 | git reset --hard "$REPO/$PREV" 36 | 37 | # now cherry pick all the old commits from $CHAP onto this new base. 38 | # (we can't just use rebase because it does the wrong thing with trying to find common history) 39 | git cherry-pick -Xrename-threshold=20% "$CHAP_COMMIT_LIST" 40 | 41 | 42 | # display a little diff to sanity-check what we've done. 43 | git diff -w "$REPO/$CHAP" 44 | 45 | cd ../.. 46 | -------------------------------------------------------------------------------- /source/fix-commit-numbers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # use with 4 | # FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --msg-filter $PWD/../../fix-commit-numbers.py 3fc31f1b.. 5 | 6 | import re 7 | import sys 8 | 9 | while incoming := sys.stdin.readline(): 10 | # if m := re.match(r"(.+) --ch(\d+)l(\d+)(-?)(\d?)--", incoming.rstrip()): 11 | if m := re.match(r"(.+) (\(|--)ch(\d+)l(\d+)(-?)(\d?)(\)|--)", incoming.rstrip()): 12 | prefix, sep1, chap_num, listing_num, extra_dash, suffix, sep2 = m.groups() 13 | chap_num = int(chap_num) 14 | listing_num = int(listing_num) 15 | suffix = int(suffix) if suffix else None 16 | if chap_num == 14: 17 | pass 18 | 19 | elif suffix and listing_num == 30: 20 | listing_num = listing_num - 13 + suffix 21 | 22 | if listing_num == 30: 23 | assert suffix is None 24 | listing_num = 21 25 | elif listing_num == 31: 26 | assert suffix is None 27 | listing_num = 22 28 | elif listing_num == 32 and suffix == "1": 29 | listing_num = 23 30 | elif listing_num == 32 and suffix == "2": 31 | listing_num = 24 32 | elif listing_num == 33 and suffix is None: 33 | listing_num = 25 34 | elif listing_num == 33 and suffix: 35 | listing_num = 26 36 | elif listing_num == 34: 37 | listing_num = 27 38 | elif listing_num == 35: 39 | listing_num = 28 40 | elif listing_num == 36: 41 | assert suffix 42 | listing_num = 28 + suffix 43 | 44 | print(f"{prefix} {sep1}ch{chap_num}l{listing_num:03d}{sep2}") 45 | else: 46 | print(incoming, end="") 47 | -------------------------------------------------------------------------------- /source/push-back.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | CHAP=$1 5 | 6 | cd "$CHAP/superlists" 7 | git push --force-with-lease local "$CHAP" 8 | git push --force-with-lease origin "$CHAP" 9 | 10 | cd ../.. 11 | -------------------------------------------------------------------------------- /tests/chapters.py: -------------------------------------------------------------------------------- 1 | CHAPTERS = [ 2 | # part 1 3 | "chapter_01", 4 | "chapter_02_unittest", 5 | "chapter_03_unit_test_first_view", 6 | "chapter_04_philosophy_and_refactoring", 7 | "chapter_05_post_and_database", 8 | "chapter_06_explicit_waits_1", 9 | "chapter_07_working_incrementally", 10 | 11 | # part 2: deploy 12 | "chapter_08_prettification", 13 | "chapter_09_docker", 14 | "chapter_10_production_readiness", 15 | "chapter_11_server_prep", 16 | "chapter_12_ansible", 17 | 18 | # part 3: validation 19 | "chapter_13_organising_test_files", 20 | "chapter_14_database_layer_validation", 21 | "chapter_15_simple_form", 22 | "chapter_16_advanced_forms", 23 | 24 | # part 4: spiking and mocking 25 | "chapter_17_javascript", 26 | "chapter_18_second_deploy", 27 | "chapter_19_spiking_custom_auth", 28 | "chapter_20_mocking_1", 29 | "chapter_21_mocking_2", 30 | "chapter_22_fixtures_and_wait_decorator", 31 | "chapter_23_debugging_prod", 32 | "chapter_24_outside_in", 33 | "chapter_25_CI", 34 | "chapter_26_page_pattern", 35 | 36 | "chapter_27_hot_lava", 37 | 38 | ] 39 | -------------------------------------------------------------------------------- /tests/check_links.py: -------------------------------------------------------------------------------- 1 | from lxml import html 2 | import requests 3 | 4 | with open('book.html') as f: 5 | node = html.fromstring(f.read()) 6 | 7 | all_hrefs = [e.get('href') for e in node.cssselect('a')] 8 | urls = [l for l in all_hrefs if l and l.startswith('h')] 9 | 10 | for l in urls: 11 | try: 12 | response = requests.get(l) 13 | if response.status_code != 200: 14 | print(l) 15 | else: 16 | print('.', end="", flush=True) 17 | except requests.RequestException: 18 | print(l) 19 | 20 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | pytest.register_assert_rewrite('sourcetree') 3 | -------------------------------------------------------------------------------- /tests/my-phantomjs-qunit-runner.js: -------------------------------------------------------------------------------- 1 | /*global require, phantom */ 2 | var system = require('system'); 3 | 4 | if (!system.args[1]){ 5 | console.log('Pass path to test file as second arg'); 6 | phantom.exit(); 7 | } 8 | 9 | var path = system.args[1]; 10 | if (path.indexOf('/') !== 0) { 11 | path = system.env.PWD + '/' + path; 12 | } 13 | 14 | var page = require('webpage').create(); 15 | var logs = ''; 16 | 17 | page.onConsoleMessage = function (msg) { 18 | logs += msg + '\n'; 19 | }; 20 | 21 | page.open('file://' + path, function () { 22 | setTimeout(function() { 23 | var output = page.evaluate( function () { 24 | var results = ''; 25 | 26 | var headline = $('#qunit-testresult').text().split('.')[1] + '.'; 27 | results += headline + '\n'; 28 | 29 | var testCounter = 0; 30 | $('#qunit-tests li').each(function() { 31 | var li = $(this); 32 | if (li.prop('id').indexOf('qunit-test-output') !== -1){ 33 | testCounter += 1; 34 | var resultLine = ''; 35 | resultLine += testCounter + '. '; 36 | if (li.find('.module-name').length > 0) { 37 | resultLine += li.find('.module-name').text() + ': '; 38 | } 39 | resultLine += li.find('.test-name').text(); 40 | resultLine += ' ' + li.find('.counts').text(); 41 | resultLine = resultLine.replace('Rerun', ''); 42 | var fails = li.find('.fail'); 43 | if (fails.text()) { 44 | li.find('.qunit-assert-list li').each(function (assertCounter) { 45 | var assert = $(this); 46 | resultLine += '\n'; 47 | resultLine += ' ' + (assertCounter + 1) + '. '; 48 | resultLine += assert.find('.test-message').text(); 49 | if (assert.find('.fail')) { 50 | assert.find('tr').each(function () { 51 | resultLine += '\n'; 52 | resultLine += ' ' + $(this).text(); 53 | }); 54 | } 55 | }); 56 | } 57 | results += resultLine + '\n'; 58 | } 59 | }); 60 | 61 | return results; 62 | }); 63 | console.log(output); 64 | console.log(logs); 65 | phantom.exit(); 66 | 67 | }, 100); 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /tests/run-js-spec.py: -------------------------------------------------------------------------------- 1 | #!python 2 | import re 3 | import sys 4 | import time 5 | from pathlib import Path 6 | 7 | from selenium import webdriver 8 | from selenium.webdriver.common.by import By 9 | from selenium.webdriver.remote.webelement import WebElement 10 | 11 | 12 | def sub_book_path(text: str) -> str: 13 | return re.sub( 14 | r"@file://(.+)/superlists/src/", 15 | ".../goat-book/src/", 16 | text, 17 | ) 18 | 19 | 20 | def run(path: Path): 21 | assert path.exists() 22 | 23 | options = webdriver.FirefoxOptions() 24 | options.add_argument("--headless") 25 | browser = webdriver.Firefox(options=options) 26 | # options = webdriver.ChromeOptions() 27 | # options.add_argument("--headless=new") 28 | # options.binary_location = "/Applications/Vivaldi.app/Contents/MacOS/Vivaldi" 29 | # browser = webdriver.Chrome(options=options) 30 | failed = False 31 | 32 | def _el_text(sel, node: webdriver.Remote | WebElement = browser): 33 | raw = "\n".join(el.text for el in node.find_elements(By.CSS_SELECTOR, sel)) 34 | return re.sub(r" 0\.\d+s", " 0.005s", raw) 35 | 36 | try: 37 | browser.get(f"file:///{path}?seed=12345") 38 | time.sleep(0.2) 39 | 40 | # for entry in browser.get_log('browser'): 41 | # print(entry) 42 | 43 | print( 44 | f"{_el_text('.jasmine-overall-result')} {_el_text('.jasmine-duration')}" 45 | ) 46 | 47 | print(_el_text(".jasmine-bar.jasmine-errored")) 48 | 49 | print(_el_text(".jasmine-menu.jasmine-failure-list")) 50 | for failures_el in browser.find_elements(By.CSS_SELECTOR, ".jasmine-failures"): 51 | for spec_failure in failures_el.find_elements( 52 | By.CSS_SELECTOR, ".jasmine-spec-detail.jasmine-failed" 53 | ): 54 | failed = True 55 | print() 56 | print(_el_text(".jasmine-description", spec_failure)) 57 | print(_el_text(".jasmine-messages", spec_failure)) 58 | 59 | for success_el in browser.find_elements(By.CSS_SELECTOR, ".jasmine-summary"): 60 | for suite_el in success_el.find_elements(By.CSS_SELECTOR, ".jasmine-suite"): 61 | if suite_el.is_displayed(): 62 | print(_el_text("li.jasmine-suite-detail", suite_el)) 63 | for spec_el in suite_el.find_elements( 64 | By.CSS_SELECTOR, "ul.jasmine-specs li.jasmine-passed" 65 | ): 66 | print(" * " + spec_el.text) 67 | 68 | finally: 69 | browser.quit() 70 | return failed 71 | 72 | 73 | if __name__ == "__main__": 74 | _, fn, *__ = sys.argv 75 | if fn.endswith("Spec.js"): 76 | fn = fn.replace("Spec.js", "SpecRunner.html") 77 | failed = run(Path(fn).resolve()) 78 | sys.exit(1 if failed is True else 0) 79 | -------------------------------------------------------------------------------- /tests/slimerjs-0.9.0/LICENSE: -------------------------------------------------------------------------------- 1 | The files SlimerJS are licensed under the MPL 2.0 (http://mozilla.org/MPL/2.0/), 2 | with the exception of the sources file listed below (zipped into the omni.ja file in the 3 | distributed version of SlimerJS), which are made available by 4 | their authors under the licenses listed alongside. 5 | 6 | * modules/addon-sdk/* 7 | are released under the Mozilla Public License, v. 2.0 8 | see http://mozilla.org/MPL/2.0/ 9 | These files comes from Firefox source code and Mozilla Addon-SDK source code 10 | http://mxr.mozilla.org/mozilla-central/source/toolkit/addon-sdk/ 11 | https://github.com/laurentj/addon-sdk/tree/master/lib/sdk/io 12 | 13 | * components/httpd.js 14 | * modules/httpUtils.jsm 15 | is released under the Mozilla Public License, v. 2.0 16 | see http://mozilla.org/MPL/2.0/ 17 | These files comes from Mozilla source code 18 | http://mxr.mozilla.org/mozilla-central/source/netwerk/test/httpserver/ 19 | 20 | * components/ConsoleAPI.js 21 | * components/nsPrompter.js 22 | is released under the Mozilla Public License, v. 2.0 23 | see http://mozilla.org/MPL/2.0/ 24 | This file comes originally from the source code of Firefox 25 | http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js 26 | http://mxr.mozilla.org/mozilla-central/source/toolkit/components/prompts/src/nsPrompter.js 27 | 28 | * modules/slimer-sdk/net-log.js 29 | is released under the Mozilla Public License, v. 2.0 30 | see http://mozilla.org/MPL/2.0/ 31 | The author is Olivier Meunier and Laurent Jouanneau 32 | and this file comes from https://github.com/olivier-m/jetpack-net-log/ 33 | 34 | * modules/slimer-sdk/webpage.js 35 | is released under the Mozilla Public License, v. 2.0 36 | see http://mozilla.org/MPL/2.0/ 37 | Some pieces of code come from webpage.js of the 38 | project https://github.com/olivier-m/jetpack-webpage/ 39 | The author of these pieces of code is Olivier Meunier 40 | 41 | * modules/slPhantomJSKeyCode.jsm 42 | 43 | content of this file is a part of webpage.js from the PhantomJS 44 | project from Ofi Labs. 45 | 46 | Copyright (C) 2011 Ariya Hidayat 47 | Copyright (C) 2011 Ivan De Marino 48 | Copyright (C) 2011 James Roe 49 | Copyright (C) 2011 execjosh, http://execjosh.blogspot.com 50 | Copyright (C) 2012 James M. Greene 51 | 52 | Redistribution and use in source and binary forms, with or without 53 | modification, are permitted provided that the following conditions are met: 54 | 55 | * Redistributions of source code must retain the above copyright 56 | notice, this list of conditions and the following disclaimer. 57 | * Redistributions in binary form must reproduce the above copyright 58 | notice, this list of conditions and the following disclaimer in the 59 | documentation and/or other materials provided with the distribution. 60 | * Neither the name of the nor the 61 | names of its contributors may be used to endorse or promote products 62 | derived from this software without specific prior written permission. 63 | 64 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 65 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 66 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 67 | ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 68 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 69 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 70 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 71 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 72 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 73 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | -------------------------------------------------------------------------------- /tests/slimerjs-0.9.0/README.md: -------------------------------------------------------------------------------- 1 | # SlimerJS 2 | 3 | SlimerJS is a scriptable browser. It allows you to manipulate a web page 4 | with a Javascript script: opening a webpage, clicking on links, modifying the content... 5 | It is useful to do functional tests, page automaton, network monitoring, screen capture etc. 6 | 7 | Go to [http://slimerjs.org] to know more and to access to the documentation 8 | 9 | 10 | # Install 11 | 12 | - Install [Firefox](http://getfirefox.com), 13 | or [XulRunner](http://ftp.mozilla.org/pub/mozilla.org/xulrunner/releases/19.0.2/runtimes/) (both version 18 or more) 14 | - [Download the latest package](http://download.slimerjs.org/slimerjs-0.5RC1.zip) or 15 | [the source code of SlimerJS](https://github.com/laurentj/slimerjs/archive/master.zip) if you didn't it yet 16 | - On windows, a .bat is provided, but you can also launch slimer from a "true" console. In this case, you should install 17 | [Cygwin](http://www.cygwin.com/) or any other unix environment to launch slimerjs. 18 | - SlimerJS needs to know where Firefox or XulRunner is stored. It tries to discover 19 | itself the path but can fail. You must then set the environment variable 20 | SLIMERJSLAUNCHER, which should contain the full path to the firefox binary: 21 | - On linux: ```export SLIMERJSLAUNCHER=/usr/bin/firefox``` 22 | - on Windows: ```SET SLIMERJSLAUNCHER="c:\Program Files\Mozilla Firefox\firefox.exe``` 23 | - On windows with cygwin : ```export SLIMERJSLAUNCHER="/cygdrive/c/program files/mozilla firefox/firefox.exe"``` 24 | - On MacOS: ```export SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox``` 25 | - You can of course set this variable in your .bashrc, .profile or in the computer 26 | properties on Windows. 27 | 28 | # Launching SlimerJS 29 | 30 | Open a terminal and go to the directory of SlimerJS (src/ if you downloaded the source code). Then launch: 31 | 32 | ``` 33 | ./slimerjs myscript.js 34 | ``` 35 | 36 | In the Windows commands console: 37 | 38 | ``` 39 | slimerjs.bat myscript.js 40 | ``` 41 | 42 | 43 | The given script myscripts.js is then executed in a window. If your script is 44 | short, you probably won't see this window. 45 | 46 | You can for example launch some tests if you execute SlimerJS from the source code: 47 | 48 | ``` 49 | ./slimerjs ../test/initial-tests.js 50 | ``` 51 | 52 | # Launching a headless SlimerJS 53 | 54 | There is a tool called xvfb, available on Linux and MacOS. It allows to launch 55 | any "graphical" programs without the need of X-Windows environment. Windows of 56 | the application won't be shown and will be drawn only in memory. 57 | 58 | Install it from your prefered repository (```sudo apt-get install xvfb``` 59 | with debian/ubuntu). 60 | 61 | Then launch SlimerJS like this: 62 | 63 | ``` 64 | xvfb-run ./slimerjs myscript.js 65 | ``` 66 | 67 | You won't see any windows. If you have any problems with xvfb, see its 68 | documentation. 69 | 70 | # Getting help 71 | 72 | - Ask your questions on the dedicated [mailing list](https://groups.google.com/forum/#!forum/slimerjs). 73 | - Discuss with us on IRC: channel #slimerjs on irc.mozilla.org. 74 | - Read the faq [on the website](http://slimerjs.org/faq.html). 75 | - Read [the documentation](http://docs.slimerjs.org/current/) 76 | -------------------------------------------------------------------------------- /tests/slimerjs-0.9.0/application.ini: -------------------------------------------------------------------------------- 1 | [App] 2 | Vendor=Innophi 3 | Name=SlimerJS 4 | Version=0.9.0 5 | BuildID=20131211 6 | ID=slimerjs@slimerjs.org 7 | Copyright=Copyright 2012-2013 Laurent Jouanneau & Innophi 8 | 9 | [Gecko] 10 | MinVersion=17.0.0 11 | MaxVersion=27.* 12 | -------------------------------------------------------------------------------- /tests/slimerjs-0.9.0/omni.ja: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjwp/Book-TDD-Web-Dev-Python/a0b11112a350236fa6f15a6ca9f6fdace8e4bbaf/tests/slimerjs-0.9.0/omni.ja -------------------------------------------------------------------------------- /tests/test_appendix_DjangoRestFramework.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class AppendixVIITest(ChapterTest): 8 | chapter_name = 'appendix_DjangoRestFramework' 9 | previous_chapter = 'appendix_rest_api' 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | self.prep_virtualenv() 15 | 16 | # sanity checks 17 | self.assertEqual(self.listings[0].type, 'other command') 18 | self.assertEqual(self.listings[1].type, 'code listing') 19 | 20 | # skips 21 | #self.skip_with_check(22, 'switch back to master') # comment 22 | 23 | # hack fast-forward 24 | skip = False 25 | if skip: 26 | self.pos = 40 27 | self.sourcetree.run_command('git switch {}'.format( 28 | self.sourcetree.get_commit_spec('ch36l027') 29 | )) 30 | 31 | while self.pos < len(self.listings): 32 | print(self.pos) 33 | self.recognise_listing_and_process_it() 34 | 35 | self.assert_all_listings_checked(self.listings) 36 | # TODO: 37 | # self.sourcetree.patch_from_commit('ch37l015') 38 | # self.sourcetree.patch_from_commit('ch37l017') 39 | # self.sourcetree.run_command( 40 | # 'git add . && git commit -m"final commit in rest api chapter"' 41 | #) 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /tests/test_appendix_Django_Class-Based_Views.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class AppendixIITest(ChapterTest): 8 | chapter_name = 'appendix_Django_Class-Based_Views' 9 | previous_chapter = 'chapter_16_advanced_forms' 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | #self.prep_virtualenv() 15 | 16 | # sanity checks 17 | self.assertEqual(self.listings[0].type, 'code listing currentcontents') 18 | self.assertEqual(self.listings[1].type, 'code listing with git ref') 19 | self.assertEqual(self.listings[2].type, 'code listing with git ref') 20 | 21 | # skips 22 | #self.skip_with_check(22, 'switch back to master') # comment 23 | 24 | # hack fast-forward 25 | skip = False 26 | if skip: 27 | self.pos = 27 28 | self.sourcetree.run_command('git switch {0}'.format( 29 | self.sourcetree.get_commit_spec('ch20l015') 30 | )) 31 | 32 | while self.pos < len(self.listings): 33 | print(self.pos) 34 | self.recognise_listing_and_process_it() 35 | 36 | self.assert_all_listings_checked(self.listings) 37 | self.check_final_diff(ignore=["moves"]) 38 | 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /tests/test_appendix_bdd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class AppendixVTest(ChapterTest): 8 | chapter_name = 'appendix_bdd' 9 | previous_chapter = 'chapter_23_debugging_prod' 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | #self.prep_virtualenv() 15 | 16 | # sanity checks 17 | self.assertEqual(self.listings[0].type, 'other command') 18 | self.assertEqual(self.listings[4].type, 'tree') 19 | self.assertEqual(self.listings[6].type, 'diff') 20 | self.assertEqual(self.listings[7].type, 'bdd test') 21 | 22 | # skips 23 | #self.skip_with_check(22, 'switch back to master') # comment 24 | 25 | # hack fast-forward 26 | skip = False 27 | if skip: 28 | self.pos = 27 29 | self.sourcetree.run_command('git switch {0}'.format( 30 | self.sourcetree.get_commit_spec('ch20l015') 31 | )) 32 | 33 | while self.pos < len(self.listings): 34 | print(self.pos) 35 | self.recognise_listing_and_process_it() 36 | 37 | self.assert_all_listings_checked(self.listings) 38 | self.sourcetree.run_command('git add . && git commit -m"final commit in bdd chapter"') 39 | self.check_final_diff() 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/test_appendix_purist_unit_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter20Test(ChapterTest): 8 | chapter_name = 'appendix_purist_unit_tests' 9 | previous_chapter = 'chapter_24_outside_in' 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, 'other command') 16 | self.assertEqual(self.listings[1].type, 'output') 17 | self.assertEqual(self.listings[4].type, 'code listing currentcontents') 18 | 19 | # skips 20 | self.skip_with_check(1, '# a branch') # comment 21 | self.skip_with_check(109, '# optional backup') # comment 22 | self.skip_with_check(112, '# reset master') # comment 23 | 24 | # prep 25 | self.start_with_checkout() 26 | 27 | # hack fast-forward 28 | skip = False 29 | if skip: 30 | self.pos = 75 31 | self.sourcetree.run_command('git switch {0}'.format( 32 | self.sourcetree.get_commit_spec('ch19l041') 33 | )) 34 | 35 | while self.pos < len(self.listings): 36 | print(self.pos, self.listings[self.pos].type) 37 | self.recognise_listing_and_process_it() 38 | 39 | self.assert_all_listings_checked(self.listings) 40 | self.check_final_diff(ignore=["moves"]) 41 | 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /tests/test_appendix_rest_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class AppendixVITest(ChapterTest): 8 | chapter_name = 'appendix_rest_api' 9 | previous_chapter = 'chapter_26_page_pattern' 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | # self.prep_virtualenv() 15 | 16 | # sanity checks 17 | self.assertEqual(self.listings[0].type, 'code listing') 18 | self.assertEqual(self.listings[1].type, 'code listing') 19 | 20 | # skips 21 | #self.skip_with_check(22, 'switch back to master') # comment 22 | 23 | # hack fast-forward 24 | skip = False 25 | if skip: 26 | self.pos = 40 27 | self.sourcetree.run_command('git switch {}'.format( 28 | self.sourcetree.get_commit_spec('ch36l027') 29 | )) 30 | 31 | while self.pos < len(self.listings): 32 | print(self.pos) 33 | self.recognise_listing_and_process_it() 34 | 35 | self.assert_all_listings_checked(self.listings) 36 | self.sourcetree.run_command( 37 | 'git add . && git commit -m"final commit in rest api chapter"' 38 | ) 39 | self.check_final_diff() 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/test_chapter_01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import os 4 | import unittest 5 | 6 | from book_parser import Output 7 | from book_tester import ChapterTest, CodeListing, write_to_file 8 | from update_source_repo import update_sources_for_chapter 9 | 10 | os.environ["LC_ALL"] = "en_GB.UTF-8" 11 | os.environ["LANG"] = "en_GB.UTF-8" 12 | os.environ["LANGUAGE"] = "en_GB.UTF-8" 13 | 14 | 15 | class Chapter1Test(ChapterTest): 16 | chapter_name = "chapter_01" 17 | 18 | def write_to_file(self, codelisting): 19 | # override write to file, in this chapter cwd is root tempdir 20 | print("writing to file", codelisting.filename) 21 | write_to_file(codelisting, os.path.join(self.tempdir)) 22 | print("wrote", open(os.path.join(self.tempdir, codelisting.filename)).read()) 23 | 24 | def test_listings_and_commands_and_output(self): 25 | update_sources_for_chapter(self.chapter_name, previous_chapter=None) 26 | self.parse_listings() 27 | # self.fail('\n'.join(f'{l.type}: {l}' for l in self.listings)) 28 | 29 | # sanity checks 30 | self.assertEqual(type(self.listings[0]), CodeListing) 31 | 32 | self.skip_with_check(6, "Performing system checks...") # after runserver 33 | self.listings[8] = Output(str(self.listings[8]).replace("$", "")) 34 | 35 | # prep folder as it would be 36 | self.sourcetree.run_command("mkdir -p .venv/bin") 37 | self.sourcetree.run_command("mkdir -p .venv/lib") 38 | 39 | self.unset_PYTHONDONTWRITEBYTECODE() 40 | while self.pos < len(self.listings): 41 | print(self.pos) 42 | self.recognise_listing_and_process_it() 43 | 44 | self.assert_all_listings_checked(self.listings) 45 | 46 | # manually add repo, we didn't do it at the beginning 47 | local_repo_path = os.path.abspath( 48 | os.path.join(os.path.dirname(__file__), "../source/chapter_01/superlists") 49 | ) 50 | self.sourcetree.run_command('git remote add repo "{}"'.format(local_repo_path)) 51 | self.sourcetree.run_command("git fetch repo") 52 | 53 | self.check_final_diff( 54 | ignore=[ 55 | "SECRET_KEY", 56 | "Generated by 'django-admin startproject' using Django 5.2.", 57 | ] 58 | ) 59 | 60 | 61 | if __name__ == "__main__": 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /tests/test_chapter_02_unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | 5 | from book_tester import ( 6 | ChapterTest, 7 | CodeListing, 8 | Command, 9 | ) 10 | 11 | class Chapter2Test(ChapterTest): 12 | chapter_name = 'chapter_02_unittest' 13 | previous_chapter = 'chapter_01' 14 | 15 | def test_listings_and_commands_and_output(self): 16 | self.parse_listings() 17 | 18 | # sanity checks 19 | self.assertEqual(type(self.listings[0]), CodeListing) 20 | self.assertEqual(type(self.listings[2]), Command) 21 | 22 | self.start_with_checkout() 23 | 24 | while self.pos < len(self.listings): 25 | print(self.pos) 26 | self.recognise_listing_and_process_it() 27 | 28 | self.assert_all_listings_checked(self.listings) 29 | self.check_final_diff() 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tests/test_chapter_03_unit_test_first_view.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | import time 5 | 6 | from book_tester import ( 7 | ChapterTest, 8 | CodeListing, 9 | Command, 10 | Output, 11 | ) 12 | 13 | class Chapter3Test(ChapterTest): 14 | chapter_name = 'chapter_03_unit_test_first_view' 15 | previous_chapter = 'chapter_02_unittest' 16 | 17 | def test_listings_and_commands_and_output(self): 18 | self.parse_listings() 19 | 20 | # sanity checks 21 | self.assertEqual(type(self.listings[0]), Command) 22 | self.assertEqual(type(self.listings[1]), Output) 23 | self.assertEqual(type(self.listings[2]), CodeListing) 24 | 25 | self.skip_with_check(10, 'will show you') 26 | final_ft = 43 27 | self.assertIn('Finish the test', self.listings[final_ft + 1]) 28 | 29 | self.start_with_checkout() 30 | self.start_dev_server() 31 | self.unset_PYTHONDONTWRITEBYTECODE() 32 | 33 | print(self.pos) 34 | assert 'manage.py startapp lists' in self.listings[self.pos] 35 | self.recognise_listing_and_process_it() 36 | time.sleep(1) # voodoo sleep, otherwise db.sqlite3 doesnt appear in CI sometimes 37 | 38 | while self.pos < final_ft: 39 | print(self.pos) 40 | self.recognise_listing_and_process_it() 41 | self.restart_dev_server() 42 | 43 | while self.pos < len(self.listings): 44 | print(self.pos) 45 | self.recognise_listing_and_process_it() 46 | 47 | self.assert_all_listings_checked(self.listings) 48 | self.check_final_diff() 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/test_chapter_04_philosophy_and_refactoring.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | import time 5 | 6 | from book_tester import ( 7 | ChapterTest, 8 | CodeListing, 9 | Command, 10 | Output, 11 | ) 12 | 13 | 14 | class Chapter4Test(ChapterTest): 15 | chapter_name = "chapter_04_philosophy_and_refactoring" 16 | previous_chapter = "chapter_03_unit_test_first_view" 17 | 18 | def test_listings_and_commands_and_output(self): 19 | self.parse_listings() 20 | 21 | # sanity checks 22 | self.assertEqual(type(self.listings[0]), Command) 23 | self.assertEqual(type(self.listings[1]), Output) 24 | self.assertEqual(type(self.listings[2]), CodeListing) 25 | 26 | self.start_with_checkout() 27 | self.start_dev_server() 28 | 29 | self.skip_with_check(36, "add the untracked templates folder") 30 | self.skip_with_check(38, "review the changes") 31 | 32 | while self.pos < len(self.listings): 33 | print(self.pos, self.listings[self.pos].type) 34 | time.sleep(0.5) # let runserver fs watcher catch up 35 | self.recognise_listing_and_process_it() 36 | 37 | self.assert_all_listings_checked(self.listings) 38 | self.check_final_diff() 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/test_chapter_05_post_and_database.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ( 5 | ChapterTest, 6 | CodeListing, 7 | Command, 8 | Output, 9 | ) 10 | 11 | 12 | class Chapter5Test(ChapterTest): 13 | chapter_name = "chapter_05_post_and_database" 14 | previous_chapter = "chapter_04_philosophy_and_refactoring" 15 | 16 | def test_listings_and_commands_and_output(self): 17 | self.parse_listings() 18 | 19 | # sanity checks 20 | self.assertEqual(type(self.listings[0]), Output) 21 | self.assertEqual(type(self.listings[1]), CodeListing) 22 | self.assertEqual(type(self.listings[3]), Command) 23 | 24 | views_pos = 21 25 | self.find_with_check(views_pos, "def home_page") 26 | 27 | nutemplate_pos = 95 28 | nl = self.find_with_check(nutemplate_pos, '{"items": items}') 29 | print(nl) 30 | 31 | migrate_pos = 99 32 | ml = self.find_with_check(migrate_pos, "migrate") 33 | assert ml.type == "interactive manage.py" 34 | 35 | self.start_with_checkout() 36 | self.start_dev_server() 37 | self.unset_PYTHONDONTWRITEBYTECODE() 38 | 39 | restarted_after_views = False 40 | restarted_after_migrate = False 41 | restarted_after_nutemplate = False 42 | while self.pos < len(self.listings): 43 | print(self.pos) 44 | if self.pos > views_pos and not restarted_after_views: 45 | self.restart_dev_server() 46 | restarted_after_views = True 47 | if self.pos > migrate_pos and not restarted_after_migrate: 48 | self.restart_dev_server() 49 | restarted_after_migrate = True 50 | if self.pos > nutemplate_pos and not restarted_after_nutemplate: 51 | self.restart_dev_server() 52 | restarted_after_nutemplate = True 53 | self.recognise_listing_and_process_it() 54 | 55 | self.assert_all_listings_checked(self.listings) 56 | self.check_final_diff( 57 | ignore=[ 58 | "moves", 59 | "Generated by Django 5.", 60 | ] 61 | ) 62 | 63 | 64 | if __name__ == "__main__": 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /tests/test_chapter_06_explicit_waits_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ( 5 | ChapterTest, 6 | Command, 7 | ) 8 | 9 | 10 | class Chapter6Test(ChapterTest): 11 | chapter_name = "chapter_06_explicit_waits_1" 12 | previous_chapter = "chapter_05_post_and_database" 13 | 14 | def test_listings_and_commands_and_output(self): 15 | self.parse_listings() 16 | 17 | # sanity checks 18 | self.assertEqual(type(self.listings[0]), Command) 19 | self.assertEqual(type(self.listings[1]), Command) 20 | self.assertEqual(type(self.listings[2]), Command) 21 | 22 | # skips 23 | self.skip_with_check(15, "msg eg") # git 24 | 25 | # other prep 26 | self.start_with_checkout() 27 | self.unset_PYTHONDONTWRITEBYTECODE() 28 | self.run_command(Command("python3 manage.py migrate --noinput")) 29 | 30 | # hack fast-forward 31 | self.skip_forward_if_skipto_set() 32 | 33 | while self.pos < len(self.listings): 34 | print(self.pos) 35 | self.recognise_listing_and_process_it() 36 | 37 | self.assert_all_listings_checked(self.listings) 38 | self.check_final_diff() 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main() 43 | -------------------------------------------------------------------------------- /tests/test_chapter_07_working_incrementally.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ( 5 | ChapterTest, 6 | Command, 7 | ) 8 | 9 | 10 | class Chapter7Test(ChapterTest): 11 | chapter_name = "chapter_07_working_incrementally" 12 | previous_chapter = "chapter_06_explicit_waits_1" 13 | 14 | def test_listings_and_commands_and_output(self): 15 | self.parse_listings() 16 | 17 | # sanity checks 18 | self.assertEqual(self.listings[0].type, "output") 19 | self.assertEqual(self.listings[1].type, "output") 20 | 21 | # skips 22 | self.skip_with_check(61, "should show 4 changed files") # git 23 | self.skip_with_check(66, "add a message summarising") # git 24 | self.skip_with_check(87, "5 changed files") # git 25 | self.skip_with_check(89, "forms x2") # git 26 | self.skip_with_check(116, "3 changed files") # git 27 | 28 | # other prep 29 | self.start_with_checkout() 30 | self.run_command(Command("python3 manage.py migrate --noinput")) 31 | 32 | # hack fast-forward 33 | self.skip_forward_if_skipto_set() 34 | 35 | while self.pos < len(self.listings): 36 | print(self.pos) 37 | self.recognise_listing_and_process_it() 38 | 39 | self.check_final_diff(ignore=["moves", "Generated by Django 5."]) 40 | self.assert_all_listings_checked(self.listings) 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/test_chapter_08_prettification.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_parser import Command, Output 5 | from book_tester import ChapterTest 6 | 7 | 8 | class Chapter8Test(ChapterTest): 9 | chapter_name = "chapter_08_prettification" 10 | previous_chapter = "chapter_07_working_incrementally" 11 | 12 | def test_listings_and_commands_and_output(self): 13 | self.parse_listings() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(type(self.listings[1]), Command) 18 | self.assertEqual(type(self.listings[2]), Output) 19 | 20 | self.start_with_checkout() 21 | # other prep 22 | self.sourcetree.run_command("python3 manage.py migrate --noinput") 23 | # self.unset_PYTHONDONTWRITEBYTECODE() 24 | self.prep_virtualenv() 25 | self.sourcetree.run_command("uv pip install pip") 26 | 27 | # skips 28 | self.skip_with_check(24, "the -w means ignore whitespace") 29 | self.skip_with_check(27, "leave static, for now") 30 | self.skip_with_check(52, "will now show all the bootstrap") 31 | 32 | # hack fast-forward 33 | self.skip_forward_if_skipto_set() 34 | 35 | while self.pos < len(self.listings): 36 | print(self.pos) 37 | self.recognise_listing_and_process_it() 38 | 39 | self.assert_all_listings_checked(self.listings) 40 | self.check_final_diff(ignore=["moves"]) 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/test_chapter_09_docker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import unittest 4 | 5 | from book_tester import ChapterTest 6 | 7 | 8 | class Chapter9Test(ChapterTest): 9 | chapter_name = "chapter_09_docker" 10 | previous_chapter = "chapter_08_prettification" 11 | 12 | def test_listings_and_commands_and_output(self): 13 | self.parse_listings() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "test") 18 | 19 | # skips: 20 | 21 | # docker build output, we want to run the 'docker build' 22 | # but not check output 23 | self.skip_with_check(29, "naming to docker.io/library/superlists") 24 | self.skip_with_check(36, "naming to docker.io/library/superlists") 25 | self.skip_with_check(36, "naming to docker.io/library/superlists") 26 | # normal git one 27 | self.skip_with_check(82, "add Dockerfile, .dockerignore, .gitignore") 28 | 29 | self.start_with_checkout() 30 | # simulate having a db.sqlite3 and a static folder from previous chaps 31 | self.sourcetree.run_command("./manage.py migrate --noinput") 32 | self.sourcetree.run_command("./manage.py collectstatic --noinput") 33 | 34 | # vm_restore = None # 'MANUAL_1' 35 | 36 | # hack fast-forward 37 | self.skip_forward_if_skipto_set() 38 | 39 | # if DO_SERVER_COMMANDS: 40 | # if vm_restore: 41 | # subprocess.check_call(["vagrant", "snapshot", "restore", vm_restore]) 42 | # else: 43 | # subprocess.check_call(["vagrant", "destroy", "-f"]) 44 | # subprocess.check_call(["vagrant", "up"]) 45 | 46 | while self.pos < len(self.listings): 47 | listing = self.listings[self.pos] 48 | print(self.pos, listing.type, repr(listing)) 49 | self.recognise_listing_and_process_it() 50 | 51 | self.assert_all_listings_checked(self.listings) 52 | self.check_final_diff() 53 | # if DO_SERVER_COMMANDS: 54 | # subprocess.run(["vagrant", "snapshot", "delete", "MANUAL_END"]) 55 | # subprocess.run(["vagrant", "snapshot", "save", "MANUAL_END"], check=True) 56 | 57 | 58 | if __name__ == "__main__": 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /tests/test_chapter_10_production_readiness.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import subprocess 4 | import unittest 5 | from pathlib import Path 6 | 7 | from book_tester import ChapterTest 8 | 9 | THIS_DIR = Path(__file__).parent 10 | 11 | 12 | class Chapter10Test(ChapterTest): 13 | chapter_name = "chapter_10_production_readiness" 14 | previous_chapter = "chapter_09_docker" 15 | 16 | def test_listings_and_commands_and_output(self): 17 | self.parse_listings() 18 | 19 | # sanity checks 20 | self.assertEqual(self.listings[0].type, "other command") 21 | self.assertEqual(self.listings[3].type, "docker run tty") 22 | 23 | self.start_with_checkout() 24 | self.prep_virtualenv() 25 | self.prep_database() 26 | 27 | # skips 28 | self.skip_with_check(47, "should show dockerfile") 29 | self.skip_with_check(50, "should now be clean") 30 | self.skip_with_check(55, "Change the owner") 31 | self.skip_with_check(57, "Change the file to be group-writeable as well") 32 | self.skip_with_check(61, "note container id") 33 | 34 | # hack fast-forward, nu way 35 | self.skip_forward_if_skipto_set() 36 | 37 | while self.pos < len(self.listings): 38 | listing = self.listings[self.pos] 39 | print(self.pos, listing.type, repr(listing)) 40 | 41 | self.recognise_listing_and_process_it() 42 | 43 | self.check_final_diff( 44 | ignore=[ 45 | "Django==5.2", 46 | "gunicorn==2", 47 | "whitenoise==6.", 48 | ] 49 | ) 50 | self.assert_all_listings_checked(self.listings) 51 | 52 | 53 | if __name__ == "__main__": 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /tests/test_chapter_11_server_prep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import unittest 4 | 5 | from book_tester import ChapterTest 6 | 7 | 8 | class Chapter11Test(ChapterTest): 9 | chapter_name = "chapter_11_server_prep" 10 | previous_chapter = "chapter_10_production_readiness" 11 | 12 | def test_listings_and_commands_and_output(self): 13 | self.parse_listings() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "server command") 17 | self.assertEqual(self.listings[1].type, "server command") 18 | self.assertEqual(self.listings[2].type, "output") 19 | 20 | self.start_with_checkout() 21 | self.prep_virtualenv() 22 | # self.sourcetree.run_command('mkdir -p static/stuff') 23 | 24 | # skips 25 | # self.skip_with_check(13, "we also need the Docker") 26 | 27 | # vm_restore = 'MANUAL_END' 28 | 29 | # hack fast-forward 30 | self.skip_forward_if_skipto_set() 31 | 32 | # if DO_SERVER_COMMANDS: 33 | # subprocess.check_call(['vagrant', 'snapshot', 'restore', vm_restore]) 34 | # 35 | # self.current_server_cd = '~/sites/$SITENAME' 36 | 37 | while self.pos < len(self.listings): 38 | listing = self.listings[self.pos] 39 | print(self.pos, listing.type, repr(listing)) 40 | self.recognise_listing_and_process_it() 41 | 42 | self.assert_all_listings_checked(self.listings) 43 | self.sourcetree.run_command("git add . && git commit -m ch11") 44 | self.check_final_diff(ignore=["gunicorn==19"]) 45 | # if DO_SERVER_COMMANDS: 46 | # subprocess.run(['vagrant', 'snapshot', 'delete', 'MAKING_END'], check=False) 47 | # subprocess.run(['vagrant', 'snapshot', 'save', 'MAKING_END'], check=True) 48 | # 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/test_chapter_12_ansible.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import unittest 4 | 5 | from book_tester import ChapterTest 6 | 7 | 8 | class Chapter12Test(ChapterTest): 9 | chapter_name = "chapter_12_ansible" 10 | previous_chapter = "chapter_11_server_prep" 11 | 12 | def test_listings_and_commands_and_output(self): 13 | self.parse_listings() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "against staging") 18 | 19 | self.start_with_checkout() 20 | self.prep_virtualenv() 21 | self.prep_database() 22 | # self.sourcetree.run_command('mkdir -p static/stuff') 23 | 24 | # skips 25 | self.skip_with_check(62, "git diff") 26 | self.skip_with_check(63, "should show our changes") 27 | 28 | # vm_restore = 'MANUAL_END' 29 | 30 | # hack fast-forward 31 | self.skip_forward_if_skipto_set() 32 | 33 | # if DO_SERVER_COMMANDS: 34 | # subprocess.check_call(['vagrant', 'snapshot', 'restore', vm_restore]) 35 | # 36 | # self.current_server_cd = '~/sites/$SITENAME' 37 | 38 | while self.pos < len(self.listings): 39 | listing = self.listings[self.pos] 40 | print(self.pos, listing.type, repr(listing)) 41 | self.recognise_listing_and_process_it() 42 | 43 | self.assert_all_listings_checked(self.listings) 44 | self.check_final_diff(ignore=["gunicorn==19"]) 45 | # if DO_SERVER_COMMANDS: 46 | # subprocess.run(['vagrant', 'snapshot', 'delete', 'MAKING_END'], check=False) 47 | # subprocess.run(['vagrant', 'snapshot', 'save', 'MAKING_END'], check=True) 48 | # 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/test_chapter_13_organising_test_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter13Test(ChapterTest): 8 | chapter_name = "chapter_13_organising_test_files" 9 | previous_chapter = "chapter_12_ansible" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, "code listing with git ref") 16 | self.assertEqual(self.listings[1].type, "code listing with git ref") 17 | self.assertEqual(self.listings[2].type, "test") 18 | 19 | # other prep 20 | self.start_with_checkout() 21 | self.prep_database() 22 | 23 | # hack fast-forward 24 | self.skip_forward_if_skipto_set() 25 | 26 | while self.pos < len(self.listings): 27 | print(self.pos, self.listings[self.pos].type) 28 | self.recognise_listing_and_process_it() 29 | 30 | self.assert_all_listings_checked(self.listings) 31 | self.check_final_diff( 32 | ignore=[ 33 | # "django==1.11" 34 | ] 35 | ) 36 | 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /tests/test_chapter_14_database_layer_validation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter13Test(ChapterTest): 8 | chapter_name = "chapter_14_database_layer_validation" 9 | previous_chapter = "chapter_13_organising_test_files" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, "test") 16 | self.assertEqual(self.listings[1].type, "output") 17 | self.assertEqual(self.listings[2].type, "code listing with git ref") 18 | 19 | # other prep 20 | self.start_with_checkout() 21 | self.prep_database() 22 | 23 | # self.skip_with_check(5, "equivalent to running sqlite3") 24 | 25 | # hack fast-forward 26 | self.skip_forward_if_skipto_set() 27 | 28 | while self.pos < len(self.listings): 29 | print(self.pos, self.listings[self.pos].type) 30 | self.recognise_listing_and_process_it() 31 | 32 | self.assert_all_listings_checked(self.listings) 33 | self.check_final_diff() 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /tests/test_chapter_15_simple_form.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter15Test(ChapterTest): 8 | chapter_name = "chapter_15_simple_form" 9 | previous_chapter = "chapter_14_database_layer_validation" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, "code listing with git ref") 16 | self.assertEqual(self.listings[1].type, "code listing with git ref") 17 | self.assertEqual(self.listings[2].type, "output") 18 | 19 | # skips 20 | # self.skip_with_check(31, "# review changes") # diff 21 | 22 | # prep 23 | self.start_with_checkout() 24 | self.prep_database() 25 | 26 | # hack fast-forward 27 | self.skip_forward_if_skipto_set() 28 | 29 | while self.pos < len(self.listings): 30 | print(self.pos) 31 | self.recognise_listing_and_process_it() 32 | 33 | self.assert_all_listings_checked(self.listings) 34 | self.check_final_diff(ignore=["moves"]) 35 | 36 | 37 | if __name__ == "__main__": 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /tests/test_chapter_16_advanced_forms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import unittest 4 | 5 | from book_tester import ChapterTest 6 | 7 | 8 | class Chapter16Test(ChapterTest): 9 | chapter_name = "chapter_16_advanced_forms" 10 | previous_chapter = "chapter_15_simple_form" 11 | 12 | def test_listings_and_commands_and_output(self): 13 | self.parse_listings() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "test") 18 | self.assertEqual(self.listings[2].type, "output") 19 | 20 | # prep 21 | self.start_with_checkout() 22 | self.prep_database() 23 | 24 | # skips 25 | self.skip_with_check(29, "# should show changes") # diff 26 | 27 | # hack fast-forward 28 | self.skip_forward_if_skipto_set() 29 | 30 | while self.pos < len(self.listings): 31 | print(self.pos, self.listings[self.pos].type) 32 | self.recognise_listing_and_process_it() 33 | 34 | self.assert_all_listings_checked(self.listings) 35 | self.check_final_diff(ignore=["Generated by Django 5."]) 36 | 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /tests/test_chapter_17_javascript.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter16Test(ChapterTest): 8 | chapter_name = "chapter_17_javascript" 9 | previous_chapter = "chapter_16_advanced_forms" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "test") 18 | self.assertEqual(self.listings[2].type, "output") 19 | 20 | # skip some inline bash comments 21 | self.skip_with_check(15, "if you're on Windows") 22 | self.skip_with_check(17, "delete all the other stuff") 23 | self.skip_with_check(74, "all our js") 24 | self.skip_with_check(76, "changes to the base template") 25 | 26 | # hack fast-forward 27 | self.skip_forward_if_skipto_set() 28 | 29 | while self.pos < len(self.listings): 30 | print(self.pos) 31 | self.recognise_listing_and_process_it() 32 | 33 | self.assert_all_listings_checked(self.listings) 34 | self.sourcetree.run_command('git add . && git commit -m"final commit"') 35 | self.check_final_diff() 36 | 37 | 38 | if __name__ == "__main__": 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /tests/test_chapter_19_spiking_custom_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter19Test(ChapterTest): 8 | chapter_name = "chapter_19_spiking_custom_auth" 9 | previous_chapter = "chapter_18_second_deploy" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | # self.assertEqual(self.listings[0].type, 'other command') 16 | self.assertEqual(self.listings[1].type, "code listing with git ref") 17 | self.assertEqual(self.listings[2].type, "other command") 18 | # self.assertTrue(self.listings[88].dofirst) 19 | 20 | # skips 21 | self.skip_with_check(33, "switch back to main") # comment 22 | self.skip_with_check(35, "remove any trace") # comment 23 | 24 | # prep 25 | self.start_with_checkout() 26 | self.prep_database() 27 | 28 | # hack fast-forward 29 | self.skip_forward_if_skipto_set() 30 | 31 | while self.pos < len(self.listings): 32 | print(self.pos) 33 | self.recognise_listing_and_process_it() 34 | 35 | self.assert_all_listings_checked(self.listings) 36 | 37 | # and do a final commit 38 | self.sourcetree.run_command('git add . && git commit -m"final commit"') 39 | self.check_final_diff(ignore=["Generated by Django 5."]) 40 | 41 | 42 | if __name__ == "__main__": 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/test_chapter_20_mocking_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter20Test(ChapterTest): 8 | chapter_name = "chapter_20_mocking_1" 9 | previous_chapter = "chapter_19_spiking_custom_auth" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "code listing with git ref") 18 | self.assertEqual(self.listings[2].type, "test") 19 | 20 | # skips 21 | # self.skip_with_check(22, 'switch back to master') # comment 22 | 23 | self.prep_database() 24 | # self.sourcetree.run_command("rm src/accounts/tests.py") 25 | self.sourcetree.run_command("mkdir -p src/static") 26 | 27 | # hack fast-forward 28 | self.skip_forward_if_skipto_set() 29 | 30 | while self.pos < len(self.listings): 31 | print(self.pos) 32 | self.recognise_listing_and_process_it() 33 | 34 | self.assert_all_listings_checked(self.listings) 35 | 36 | self.sourcetree.run_command( 37 | 'git add . && git commit -m"final commit in chap 19"' 38 | ) 39 | self.check_final_diff(ignore=["moves"]) 40 | 41 | 42 | if __name__ == "__main__": 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /tests/test_chapter_21_mocking_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter21Test(ChapterTest): 8 | chapter_name = "chapter_21_mocking_2" 9 | previous_chapter = "chapter_20_mocking_1" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "code listing") 18 | self.assertEqual(self.listings[1].skip, True) 19 | self.assertEqual(self.listings[2].type, "code listing with git ref") 20 | 21 | # skips 22 | # self.skip_with_check(22, 'switch back to master') # comment 23 | 24 | self.prep_database() 25 | # self.sourcetree.run_command("rm src/accounts/tests.py") 26 | self.sourcetree.run_command("mkdir -p src/static") 27 | 28 | # hack fast-forward 29 | self.skip_forward_if_skipto_set() 30 | 31 | while self.pos < len(self.listings): 32 | print(self.pos) 33 | self.recognise_listing_and_process_it() 34 | 35 | self.assert_all_listings_checked(self.listings) 36 | self.check_final_diff(ignore=["moves"]) 37 | 38 | 39 | if __name__ == "__main__": 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /tests/test_chapter_22_fixtures_and_wait_decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter22Test(ChapterTest): 8 | chapter_name = "chapter_22_fixtures_and_wait_decorator" 9 | previous_chapter = "chapter_21_mocking_2" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, "code listing with git ref") 16 | self.assertEqual(self.listings[1].type, "other command") 17 | self.assertEqual(self.listings[2].type, "output") 18 | 19 | # skips 20 | # self.skip_with_check(22, 'switch back to master') # comment 21 | 22 | # prep 23 | self.start_with_checkout() 24 | self.prep_database() 25 | 26 | # hack fast-forward 27 | self.skip_forward_if_skipto_set() 28 | 29 | while self.pos < len(self.listings): 30 | print(self.pos) 31 | self.recognise_listing_and_process_it() 32 | 33 | self.assert_all_listings_checked(self.listings) 34 | 35 | self.sourcetree.tidy_up_after_patches() 36 | self.sourcetree.run_command('git add . && git commit -m"final commit ch17"') 37 | self.check_final_diff(ignore=["moves"]) 38 | 39 | 40 | if __name__ == "__main__": 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /tests/test_chapter_23_debugging_prod.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.7 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter18Test(ChapterTest): 8 | chapter_name = "chapter_23_debugging_prod" 9 | previous_chapter = "chapter_22_fixtures_and_wait_decorator" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | 14 | # sanity checks 15 | self.assertEqual(self.listings[0].type, "docker run tty") 16 | self.assertEqual(self.listings[1].type, "output") 17 | 18 | # skips 19 | self.skip_with_check(1, "naming to docker") 20 | 21 | # self.replace_command_with_check( 22 | # 13, 23 | # "EMAIL_PASSWORD=yoursekritpasswordhere", 24 | # "EMAIL_PASSWORD=" + os.environ["EMAIL_PASSWORD"], 25 | # ) 26 | 27 | # deploy_pos = 49 28 | # assert "ansible-playbook" in self.listings[deploy_pos] 29 | 30 | # prep 31 | self.start_with_checkout() 32 | self.prep_database() 33 | self.sourcetree.run_command("touch container.db.sqlite3") 34 | self.sourcetree.run_command("sudo chown 1234 container.db.sqlite3") 35 | # for macos, see chap 10 36 | self.sourcetree.run_command("sudo chmod g+rw container.db.sqlite3") 37 | 38 | # vm_restore = "FABRIC_END" 39 | 40 | # hack fast-forward 41 | self.skip_forward_if_skipto_set() 42 | 43 | # if DO_SERVER_COMMANDS: 44 | # subprocess.check_call(["vagrant", "snapshot", "restore", vm_restore]) 45 | 46 | while self.pos < len(self.listings): 47 | print(self.pos) 48 | self.recognise_listing_and_process_it() 49 | 50 | self.assert_all_listings_checked(self.listings) 51 | 52 | self.sourcetree.tidy_up_after_patches() 53 | self.sourcetree.run_command('git add . && git commit -m"final commit ch17"') 54 | self.check_final_diff(ignore=["moves", "YAHOO_PASSWORD"]) 55 | # if DO_SERVER_COMMANDS: 56 | # subprocess.check_call(["vagrant", "snapshot", "save", "SERVER_DEBUGGED"]) 57 | 58 | 59 | if __name__ == "__main__": 60 | unittest.main() 61 | -------------------------------------------------------------------------------- /tests/test_chapter_24_outside_in.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter24Test(ChapterTest): 8 | chapter_name = "chapter_24_outside_in" 9 | previous_chapter = "chapter_23_debugging_prod" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | # self.prep_virtualenv() 14 | 15 | # sanity checks 16 | self.assertEqual(self.listings[0].type, "code listing with git ref") 17 | self.assertEqual(self.listings[1].type, "code listing with git ref") 18 | 19 | # skips 20 | self.skip_with_check(37, 'views.py, templates') 21 | 22 | self.start_with_checkout() 23 | self.prep_database() 24 | 25 | # hack fast-forward 26 | self.skip_forward_if_skipto_set() 27 | 28 | while self.pos < len(self.listings): 29 | print(self.pos, self.listings[self.pos].type) 30 | self.recognise_listing_and_process_it() 31 | 32 | self.assert_all_listings_checked(self.listings) 33 | self.check_final_diff(ignore=["moves", "Generated by Django 5"]) 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /tests/test_chapter_25_CI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter25Test(ChapterTest): 8 | chapter_name = "chapter_25_CI" 9 | previous_chapter = "chapter_24_outside_in" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | # self.prep_virtualenv() 15 | 16 | # sanity checks 17 | self.assertEqual(self.listings[0].skip, True) 18 | self.assertEqual(self.listings[1].skip, True) 19 | self.assertEqual(self.listings[9].type, "code listing with git ref") 20 | 21 | # skips 22 | # self.skip_with_check(22, 'switch back to master') # comment 23 | 24 | # hack fast-forward 25 | self.skip_forward_if_skipto_set() 26 | 27 | while self.pos < len(self.listings): 28 | print(self.pos) 29 | self.recognise_listing_and_process_it() 30 | 31 | self.assert_all_listings_checked(self.listings) 32 | self.sourcetree.run_command( 33 | "git add .gitlab-ci.yml", 34 | ) 35 | # TODO: test package.json 36 | # self.sourcetree.run_command( 37 | # "git add src/lists/static/package.json src/lists/static/tests" 38 | # ) 39 | self.sourcetree.run_command("git commit -m'final commit'") 40 | # self.check_final_diff(ignore=["moves"]) 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/test_chapter_26_page_pattern.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import unittest 3 | 4 | from book_tester import ChapterTest 5 | 6 | 7 | class Chapter26Test(ChapterTest): 8 | chapter_name = "chapter_26_page_pattern" 9 | previous_chapter = "chapter_25_CI" 10 | 11 | def test_listings_and_commands_and_output(self): 12 | self.parse_listings() 13 | self.start_with_checkout() 14 | # self.prep_virtualenv() 15 | 16 | # sanity checks 17 | # self.assertEqual(self.listings[0].type, 'code listing') 18 | self.assertEqual(self.listings[0].type, "code listing with git ref") 19 | self.assertEqual(self.listings[1].type, "test") 20 | self.assertEqual(self.listings[2].type, "output") 21 | 22 | # skips 23 | # self.skip_with_check(22, 'switch back to master') # comment 24 | 25 | # hack fast-forward 26 | self.skip_forward_if_skipto_set() 27 | 28 | while self.pos < len(self.listings): 29 | print(self.pos) 30 | self.recognise_listing_and_process_it() 31 | 32 | self.assert_all_listings_checked(self.listings) 33 | 34 | self.sourcetree.tidy_up_after_patches() 35 | # final branch includes a suggested implementation... 36 | # so just check diff up to the last listing 37 | commit = self.sourcetree.get_commit_spec("ch26l013") 38 | diff = self.sourcetree.run_command(f"git diff -b {commit}") 39 | self.check_final_diff(ignore=["moves"], diff=diff) 40 | 41 | 42 | if __name__ == "__main__": 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /theme/epub/epub.css: -------------------------------------------------------------------------------- 1 | /* Styling for custom captions on code blocks */ 2 | .sourcecode p { 3 | text-align: right; 4 | display: block; 5 | margin-bottom: -3pt; 6 | font-style: italic; 7 | hyphens: none; 8 | } 9 | 10 | div[data-type="example"].sourcecode pre { 11 | margin: 25px 0 25px 25px; 12 | } 13 | 14 | div[data-type="example"].sourcecode { 15 | margin-bottom: 0; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /theme/epub/epub.xsl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 |

15 |
16 | 17 | 18 | 19 |
SCRATCHPAD:
20 | 21 |
22 | 23 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /theme/epub/layout.html: -------------------------------------------------------------------------------- 1 | {{ doctype }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ title }} 11 | 12 | 13 | {{ content }} 14 | 15 | 16 | -------------------------------------------------------------------------------- /theme/html/html.xsl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /theme/mobi/layout.html: -------------------------------------------------------------------------------- 1 | {{ doctype }} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ title }} 11 | 12 | 13 | {{ content }} 14 | 15 | 16 | -------------------------------------------------------------------------------- /theme/mobi/mobi.css: -------------------------------------------------------------------------------- 1 | /* Styling for custom captions on code blocks */ 2 | .sourcecode p { 3 | text-align: right; 4 | display: block; 5 | margin-bottom: -3pt; 6 | font-style: italic; 7 | hyphens: none; 8 | } 9 | 10 | div[data-type="example"].sourcecode pre { 11 | margin: 25px 0 25px 25px; 12 | } 13 | 14 | div[data-type="example"].sourcecode { 15 | margin-bottom: 0; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /theme/mobi/mobi.xsl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 |

15 |
16 | 17 | 18 | 19 |
SCRATCHPAD:
20 | 21 |
22 | 23 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /theme/pdf/pdf.xsl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /titlepage.html: -------------------------------------------------------------------------------- 1 |
2 |

Test-Driven Development with Python

3 | 4 |

Third Edition

5 | 6 |

Obey the Testing Goat: Using Django, Selenium, and JavaScript

7 | 8 | 9 | 10 |

Harry J.W. Percival

11 |
12 | -------------------------------------------------------------------------------- /toc.html: -------------------------------------------------------------------------------- 1 |