|
Cover |
1 |
|
Copyright |
4 |
|
Table of Contents |
5 |
|
Preface |
15 |
|
Why I Wrote a Book About Test-Driven Development |
15 |
|
Aims of This Book |
17 |
|
Outline |
17 |
|
Conventions Used in This Book |
18 |
|
Using Code Examples |
19 |
|
Safari® Books Online |
19 |
|
Contacting O’Reilly |
20 |
|
Prerequisites and Assumptions |
21 |
|
Python 3 and Programming |
21 |
|
How HTML Works |
22 |
|
JavaScript |
22 |
|
Required Software Installations |
22 |
|
Git’s Default Editor, and Other Basic Git Config |
25 |
|
Required Python Modules |
25 |
|
Acknowledgments |
27 |
|
Part I. The Basics of TDD and Django |
29 |
|
Chapter 1. Getting Django Set Up Using a Functional Test |
31 |
|
Obey the Testing Goat! Do Nothing Until You Have a Test |
31 |
|
Getting Django Up and Running |
34 |
|
Starting a Git Repository |
36 |
|
Chapter 2. Extending Our Functional Test Using the unittest Module |
41 |
|
Using a Functional Test to Scope Out a Minimum Viable App |
41 |
|
The Python Standard Library’s unittest Module |
44 |
|
Implicit waits |
46 |
|
Commit |
46 |
|
Chapter 3. Testing a Simple Home Page with Unit Tests |
49 |
|
Our First Django App, and Our First Unit Test |
50 |
|
Unit Tests, and How They Differ from Functional Tests |
50 |
|
Unit Testing in Django |
51 |
|
Django’s MVC, URLs, and View Functions |
52 |
|
At Last! We Actually Write Some Application Code! |
54 |
|
urls.py |
55 |
|
Unit Testing a View |
58 |
|
The Unit-Test/Code Cycle |
59 |
|
Chapter 4. What Are We Doing with All These Tests? |
63 |
|
Programming Is like Pulling a Bucket of Water up from a Well |
64 |
|
Using Selenium to Test User Interactions |
65 |
|
The “Don’t Test Constants” Rule, and Templates to the Rescue |
68 |
|
Refactoring to Use a Template |
68 |
|
On Refactoring |
72 |
|
A Little More of Our Front Page |
73 |
|
Recap: The TDD Process |
75 |
|
Chapter 5. Saving User Input |
79 |
|
Wiring Up Our Form to Send a POST Request |
79 |
|
Processing a POST Request on the Server |
82 |
|
Passing Python Variables to Be Rendered in the Template |
83 |
|
Three Strikes and Refactor |
87 |
|
The Django ORM and Our First Model |
88 |
|
Our First Database Migration |
90 |
|
The Test Gets Surprisingly Far |
91 |
|
A New Field Means a New Migration |
92 |
|
Saving the POST to the Database |
93 |
|
Redirect After a POST |
96 |
|
Better Unit Testing Practice: Each Test Should Test One Thing |
96 |
|
Rendering Items in the Template |
97 |
|
Creating Our Production Database with migrate |
99 |
|
Chapter 6. Getting to the Minimum Viable Site |
105 |
|
Ensuring Test Isolation in Functional Tests |
105 |
|
Running Just the Unit Tests |
108 |
|
Small Design When Necessary |
109 |
|
YAGNI! |
110 |
|
REST |
110 |
|
Implementing the New Design Using TDD |
111 |
|
Iterating Towards the New Design |
114 |
|
Testing Views, Templates, and URLs Together with the Django Test Client |
115 |
|
A New Test Class |
116 |
|
A New URL |
116 |
|
A New View Function |
117 |
|
A Separate Template for Viewing Lists |
118 |
|
Another URL and View for Adding List Items |
120 |
|
A Test Class for New List Creation |
121 |
|
A URL and View for New List Creation |
122 |
|
Removing Now-Redundant Code and Tests |
123 |
|
Pointing Our Forms at the New URL |
124 |
|
Adjusting Our Models |
125 |
|
A Foreign Key Relationship |
127 |
|
Adjusting the Rest of the World to Our New Models |
128 |
|
Each List Should Have Its Own URL |
130 |
|
Capturing Parameters from URLs |
131 |
|
Adjusting new_list to the New World |
132 |
|
One More View to Handle Adding Items to an Existing List |
133 |
|
Beware of Greedy Regular Expressions! |
134 |
|
The Last New URL |
134 |
|
The Last New View |
135 |
|
But How to Use That URL in the Form? |
136 |
|
A Final Refactor Using URL includes |
138 |
|
Part II. Web Development Sine Qua Nons |
141 |
|
Chapter 7. Prettification: Layout and Styling, and What to Test About It |
143 |
|
What to Functionally Test About Layout and Style |
143 |
|
Prettification: Using a CSS Framework |
146 |
|
Django Template Inheritance |
148 |
|
Integrating Bootstrap |
149 |
|
Rows and Columns |
150 |
|
Static Files in Django |
150 |
|
Switching to StaticLiveServerCase |
152 |
|
Using Bootstrap Components to Improve the Look of the Site |
153 |
|
Jumbotron! |
153 |
|
Large Inputs |
153 |
|
Table Styling |
154 |
|
Using Our Own CSS |
154 |
|
What We Glossed Over: collectstatic and Other Static Directories |
155 |
|
A Few Things That Didn’t Make It |
158 |
|
Chapter 8. Testing Deployment Using a Staging Site |
159 |
|
TDD and the Danger Areas of Deployment |
160 |
|
As Always, Start with a Test |
161 |
|
Getting a Domain Name |
163 |
|
Manually Provisioning a Server to Host Our Site |
164 |
|
Choosing Where to Host Our Site |
164 |
|
Spinning Up a Server |
165 |
|
User Accounts, SSH, and Privileges |
165 |
|
Installing Nginx |
166 |
|
Configuring Domains for Staging and Live |
167 |
|
Using the FT to Confirm the Domain Works and Nginx Is Running |
167 |
|
Deploying Our Code Manually |
168 |
|
Adjusting the Database Location |
169 |
|
Creating a Virtualenv |
170 |
|
Simple Nginx Configuration |
172 |
|
Creating the Database with migrate |
175 |
|
Getting to a Production-Ready Deployment |
176 |
|
Switching to Gunicorn |
176 |
|
Getting Nginx to Serve Static Files |
177 |
|
Switching to Using Unix Sockets |
178 |
|
Switching DEBUG to False and Setting ALLOWED_HOSTS |
179 |
|
Using Upstart to Make Sure Gunicorn Starts on Boot |
179 |
|
Saving Our Changes: Adding Gunicorn to Our requirements.txt |
180 |
|
Automating |
180 |
|
“Saving Your Progress” |
184 |
|
Chapter 9. Automating Deployment with Fabric |
185 |
|
Breakdown of a Fabric Script for Our Deployment |
186 |
|
Trying It Out |
190 |
|
Deploying to Live |
191 |
|
Nginx and Gunicorn Config Using sed |
193 |
|
Git Tag the Release |
194 |
|
Further Reading |
194 |
|
Chapter 10. Input Validation and Test Organisation |
197 |
|
Validation FT: Preventing Blank Items |
197 |
|
Skipping a Test |
198 |
|
Splitting Functional Tests out into Many Files |
199 |
|
Running a Single Test File |
202 |
|
Fleshing Out the FT |
202 |
|
Using Model-Layer Validation |
203 |
|
Refactoring Unit Tests into Several Files |
203 |
|
Unit Testing Model Validation and the self.assertRaises Context Manager |
205 |
|
A Django Quirk: Model Save Doesn’t Run Validation |
206 |
|
Surfacing Model Validation Errors in the View |
206 |
|
Checking Invalid Input Isn’t Saved to the Database |
209 |
|
Django Pattern: Processing POST Requests in the Same View as Renders the Form |
211 |
|
Refactor: Transferring the new_item Functionality into view_list |
212 |
|
Enforcing Model Validation in view_list |
214 |
|
Refactor: Removing Hardcoded URLs |
215 |
|
The {% url %} Template Tag |
216 |
|
Using get_absolute_url for Redirects |
216 |
|
Chapter 11. A Simple Form |
221 |
|
Moving Validation Logic into a Form |
221 |
|
Exploring the Forms API with a Unit Test |
222 |
|
Switching to a Django ModelForm |
223 |
|
Testing and Customising Form Validation |
224 |
|
Using the Form in Our Views |
226 |
|
Using the Form in a View with a GET Request |
226 |
|
A Big Find and Replace |
229 |
|
Using the Form in a View That Takes POST Requests |
231 |
|
Adapting the Unit Tests for the new_list View |
231 |
|
Using the Form in the View |
232 |
|
Using the Form to Display Errors in the Template |
233 |
|
Using the Form in the Other View |
233 |
|
A Helper Method for Several Short Tests |
234 |
|
Using the Form’s Own Save Method |
236 |
|
Chapter 12. More Advanced Forms |
239 |
|
Another FT for Duplicate Items |
239 |
|
Preventing Duplicates at the Model Layer |
240 |
|
A Little Digression on Queryset Ordering and String Representations |
242 |
|
Rewriting the Old Model Test |
244 |
|
Some Integrity Errors Do Show Up on Save |
245 |
|
Experimenting with Duplicate Item Validation at the Views Layer |
246 |
|
A More Complex Form to Handle Uniqueness Validation |
247 |
|
Using the Existing List Item Form in the List View |
249 |
|
Chapter 13. Dipping Our Toes, Very Tentatively, into JavaScript |
253 |
|
Starting with an FT |
253 |
|
Setting Up a Basic JavaScript Test Runner |
254 |
|
Using jQuery and the Fixtures Div |
257 |
|
Building a JavaScript Unit Test for Our Desired Functionality |
260 |
|
Javascript Testing in the TDD Cycle |
262 |
|
Columbo Says: Onload Boilerplate and Namespacing |
262 |
|
A Few Things That Didn’t Make It |
263 |
|
Chapter 14. Deploying Our New Code |
265 |
|
Staging Deploy |
265 |
|
Live Deploy |
265 |
|
What to Do If You See a Database Error |
266 |
|
Wrap-Up: git tag the New Release |
266 |
|
Part III. More Advanced Topics |
267 |
|
Chapter 15. User Authentication, Integrating Third-Party Plugins, and Mocking with JavaScript |
269 |
|
Mozilla Persona (BrowserID) |
270 |
|
Exploratory Coding, aka “Spiking” |
270 |
|
Starting a Branch for the Spike |
271 |
|
Frontend and JavaScript Code |
271 |
|
The Browser-ID Protocol |
272 |
|
The Server Side: Custom Authentication |
273 |
|
De-spiking |
279 |
|
A Common Selenium Technique: Explicit Waits |
281 |
|
Reverting Our Spiked Code |
283 |
|
JavaScript Unit Tests Involving External Components: Our First Mocks! |
284 |
|
Housekeeping: A Site-Wide Static Files Folder |
284 |
|
Mocking: Who, Why, What? |
285 |
|
Namespacing |
286 |
|
A Simple Mock to Unit Tests Our initialize Function |
286 |
|
More Advanced Mocking |
292 |
|
Checking Call Arguments |
295 |
|
QUnit setup and teardown, Testing Ajax |
296 |
|
More Nested Callbacks! Testing Asynchronous Code |
300 |
|
Chapter 16. Server-Side Authentication and Mocking in Python |
305 |
|
A Look at Our Spiked Login View |
305 |
|
Mocking in Python |
306 |
|
Testing Our View by Mocking Out authenticate |
306 |
|
Checking the View Actually Logs the User In |
309 |
|
De-spiking Our Custom Authentication Backend: Mocking Out an Internet Request |
313 |
|
1 if = 1 More Test |
314 |
|
Patching at the Class Level |
315 |
|
Beware of Mocks in Boolean Comparisons |
318 |
|
Creating a User if Necessary |
319 |
|
The get_user Method |
319 |
|
A Minimal Custom User Model |
321 |
|
A Slight Disappointment |
323 |
|
Tests as Documentation |
324 |
|
Users Are Authenticated |
325 |
|
The Moment of Truth: Will the FT Pass? |
326 |
|
Finishing Off Our FT, Testing Logout |
327 |
|
Chapter 17. Test Fixtures, Logging, and Server-Side Debugging |
331 |
|
Skipping the Login Process by Pre-creating a Session |
331 |
|
Checking It Works |
333 |
|
The Proof Is in the Pudding: Using Staging to Catch Final Bugs |
334 |
|
Setting Up Logging |
335 |
|
Fixing the Persona Bug |
337 |
|
Managing the Test Database on Staging |
339 |
|
A Django Management Command to Create Sessions |
339 |
|
Getting the FT to Run the Management Command on the Server |
340 |
|
An Additional Hop via subprocess |
342 |
|
Baking In Our Logging Code |
345 |
|
Using Hierarchical Logging Config |
346 |
|
Wrap-Up |
348 |
|
Chapter 18. Finishing “My Lists”: Outside-In TDD |
351 |
|
The Alternative: “Inside Out” |
351 |
|
Why Prefer “Outside-In”? |
351 |
|
The FT for “My Lists” |
352 |
|
The Outside Layer: Presentation and Templates |
353 |
|
Moving Down One Layer to View Functions (the Controller) |
354 |
|
Another Pass, Outside-In |
355 |
|
A Quick Restructure of the Template Inheritance Hierarchy |
355 |
|
Designing Our API Using the Template |
356 |
|
Moving Down to the Next Layer: What the View Passes to the Template |
357 |
|
The Next “Requirement” from the Views Layer: New Lists Should Record Owner |
358 |
|
A Decision Point: Whether to Proceed to the Next Layer with a Failing Test |
359 |
|
Moving Down to the Model Layer |
359 |
|
Final Step: Feeding Through the .name API from the Template |
361 |
|
Chapter 19. Test Isolation, and “Listening to Your Tests” |
365 |
|
Revisiting Our Decision Point: The Views Layer Depends on Unwritten Models Code |
365 |
|
A First Attempt at Using Mocks for Isolation |
366 |
|
Using Mock side_effects to Check the Sequence of Events |
367 |
|
Listen to Your Tests: Ugly Tests Signal a Need to Refactor |
369 |
|
Rewriting Our Tests for the View to Be Fully Isolated |
370 |
|
Keep the Old Integrated Test Suite Around as a Sanity Check |
370 |
|
A New Test Suite with Full Isolation |
371 |
|
Thinking in Terms of Collaborators |
371 |
|
Moving Down to the Forms Layer |
375 |
|
Keep Listening to Your Tests: Removing ORM Code from Our Application |
376 |
|
Finally, Moving Down to the Models Layer |
379 |
|
Back to Views |
381 |
|
The Moment of Truth (and the Risks of Mocking) |
382 |
|
Thinking of Interactions Between Layers as “Contracts” |
383 |
|
Identifying Implicit Contracts |
384 |
|
Fixing the Oversight |
385 |
|
One More Test |
386 |
|
Tidy Up: What to Keep from Our Integrated Test Suite |
387 |
|
Removing Redundant Code at the Forms Layer |
387 |
|
Removing the Old Implementation of the View |
388 |
|
Removing Redundant Code at the Forms Layer |
389 |
|
Conclusions: When to Write Isolated Versus Integrated Tests |
390 |
|
Let Complexity Be Your Guide |
391 |
|
Should You Do Both? |
391 |
|
Onwards! |
391 |
|
Chapter 20. Continuous Integration (CI) |
393 |
|
Installing Jenkins |
393 |
|
Configuring Jenkins Security |
395 |
|
Adding Required Plugins |
396 |
|
Setting Up Our Project |
397 |
|
First Build! |
399 |
|
Setting Up a Virtual Display so the FTs Can Run Headless |
400 |
|
Taking Screenshots |
402 |
|
A Common Selenium Problem: Race Conditions |
406 |
|
Running Our QUnit JavaScript Tests in Jenkins with PhantomJS |
409 |
|
Installing node |
410 |
|
Adding the Build Steps to Jenkins |
411 |
|
More Things to Do with a CI Server |
412 |
|
Chapter 21. The Token Social Bit, the Page Pattern, and an Exercise for the Reader |
415 |
|
An FT with Multiple Users, and addCleanup |
415 |
|
Implementing the Selenium Interact/Wait Pattern |
417 |
|
The Page Pattern |
418 |
|
Extend the FT to a Second User, and the “My Lists” Page |
421 |
|
An Exercise for the Reader |
423 |
|
Chapter 22. Fast Tests, Slow Tests, and Hot Lava |
425 |
|
Thesis: Unit Tests Are Superfast and Good Besides That |
426 |
|
Faster Tests Mean Faster Development |
426 |
|
The Holy Flow State |
427 |
|
Slow Tests Don’t Get Run as Often, Which Causes Bad Code |
427 |
|
We’re Fine Now, but Integrated Tests Get Slower Over Time |
427 |
|
Don’t Take It from Me |
427 |
|
And Unit Tests Drive Good Design |
428 |
|
The Problems with “Pure” Unit Tests |
428 |
|
Isolated Tests Can Be Harder to Read and Write |
428 |
|
Isolated Tests Don’t Automatically Test Integration |
428 |
|
Unit Tests Seldom Catch Unexpected Bugs |
428 |
|
Mocky Tests Can Become Closely Tied to Implementation |
428 |
|
But All These Problems Can Be Overcome |
429 |
|
Synthesis: What Do We Want from Our Tests, Anyway? |
429 |
|
Correctness |
429 |
|
Clean, Maintainable Code |
429 |
|
Productive Workflow |
430 |
|
Evaluate Your Tests Against the Benefits You Want from Them |
430 |
|
Architectural Solutions |
430 |
|
Ports and Adapters/Hexagonal/Clean Architecture |
431 |
|
Functional Core, Imperative Shell |
431 |
|
Conclusion |
432 |
|
Obey the Testing Goat! |
435 |
|
Appendix A. PythonAnywhere |
437 |
|
Running Firefox Selenium Sessions with Xvfb |
437 |
|
Setting Up Django as a PythonAnywhere Web App |
438 |
|
Cleaning Up /tmp |
439 |
|
Screenshots |
439 |
|
The Deployment Chapter |
439 |
|
Appendix B. Django Class-Based Views |
441 |
|
Class-Based Generic Views |
441 |
|
The Home Page as a FormView |
442 |
|
Using form_valid to Customise a CreateView |
443 |
|
A More Complex View to Handle Both Viewing and Adding to a List |
445 |
|
The Tests Guide Us, for a While |
445 |
|
Until We’re Left with Trial and Error |
446 |
|
Back on Track |
447 |
|
Is That Your Final Answer? |
448 |
|
Compare Old and New |
448 |
|
Best Practices for Unit Testing CBGVs? |
448 |
|
Take-Home: Having Multiple, Isolated View Tests with Single Assertions Helps |
449 |
|
Appendix C. Provisioning with Ansible |
451 |
|
Installing System Packages and Nginx |
451 |
|
Configuring Gunicorn, and Using Handlers to Restart Services |
453 |
|
What to Do Next |
454 |
|
Move Deployment out of Fabric and into Ansible |
454 |
|
Use Vagrant to Spin Up a Local VM |
454 |
|
Appendix D. Testing Database Migrations |
455 |
|
An Attempted Deploy to Staging |
455 |
|
Running a Test Migration Locally |
456 |
|
Entering Problematic Data |
456 |
|
Copying Test Data from the Live Site |
456 |
|
Confirming the Error |
457 |
|
Inserting a Data Migration |
457 |
|
Re-creating the Old Migration |
458 |
|
Testing the New Migrations Together |
458 |
|
Conclusions |
459 |
|
Appendix E. What to Do Next |
461 |
|
Notifications—Both on the Site and by Email |
461 |
|
Switch to Postgres |
461 |
|
Run Your Tests Against Different Browsers |
462 |
|
404 and 500 Tests |
462 |
|
The Django Admin Site |
462 |
|
Investigate a BDD Tool |
462 |
|
Write Some Security Tests |
463 |
|
Test for Graceful Degradation |
463 |
|
Caching and Performance Testing |
463 |
|
JavaScript MVC Frameworks |
463 |
|
Async and Websockets |
463 |
|
Switch to Using py.test |
464 |
|
Client-Side Encryption |
464 |
|
Your Suggestion Here |
464 |
|
Appendix F. Cheat Sheet |
465 |
|
Initial Project Setup |
465 |
|
The Basic TDD Workflow |
465 |
|
Moving Beyond dev-only Testing |
466 |
|
General Testing Best Practices |
467 |
|
Selenium/Functional Testing Best Practices |
467 |
|
Outside-In, Test Isolation Versus Integrated Tests, and Mocking |
468 |
|
Appendix G. Bibliography |
469 |
|
Index |
471 |
|
About the Author |
478 |


