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 |