Test Driven Development With Python
Test Driven Development (TDD) With Python book by Harry Percival
I suspect, that other than clearly-confusing (hah) situations, I'm more of a Test Last person - add a test every time a human catches a bug.
- My thinking is documented at Agile Testing.
uses Python-v3 and Django and Selenium to build a To-Do List app
free online http://chimera.labs.oreilly.com/books/1234000000754
http://shop.oreilly.com/product/0636920029533.do
http://www.obeythetestinggoat.com/
== Trying to work through the book but using Python-2 and WebPy.... ==
(Feb'2014)
=== Chapter 1 ===
Go into the VirtualEnv I use for WebPy.
Do pip install --upgrade selenium
Do cd webpy
Do makedir tddwp
for directory for book
Create functional_tests.py
file, copy in code from book but change port to 8085. Run, it fails as expected.
There's no magic project-creator for WebPy, so (after opening another Terminal window, going into VirtualEnv, etc.) just mkdir superlists
, copy my code.py
from another WebPy project, tweak that file to refer to a superlists.db
SQLite file. Do python code.py 8085
to launch it - no error.
Run functional_tests.py
again - still get AssertionError
because page has other errors because of me just copying stuff over.
- also note that Selenium launched a whole new instance of FireFox, not just opening a new window or something.
Tweak my copied code.py
until can run test without AssertionError
from the page title.
Start Git {{{ git init git add .py git add superlists/.py echo "superlists/sessions/" >> .gitignore echo ".pyc" >> .gitignore git add .gitignore git commit }}}
=== Chapter 2 ===
Functional Test == Acceptance Test == End To End Test
First use of Unit Test:
TypeError: __init__() got an unexpected keyword argument 'warnings'
- try removing the keyword pass:
AttributeError: 'New Visitor Test' object has no attribute 'assertIn'
- argh, I'm actually running python-2.6.1 here. If I run functional_tests with
python2.7
then I getImportError: No module named selenium
- see VirtualEnv page for upgrading process
- still had to leave out the
warnings='ignore'
arg - but otherwise works fine
- realize I probably broke other WebPy bits with reinstall, so launch local Simplest Thing to test
=== intermission ===
Jan'2015 - obviously I just dropped this. Picking it back up.
- It looks like I hadn't even changed
functional_tests.py
to use the right port! (Or maybe something got over-written...) - And failing with
can't load the profile
- looks like I've updated FireFox which tends to break Selenium.- Currently running FireFox v34.0.5 - but downloading an update now... ok now v35.0
- It looks like the latest Selenium Web Driver is 2.44.0
- Do
pip install --upgrade selenium
- it upgrades to 2.44.0 (from existing 2.40.0) - Remind self: have 1 Terminal running to run the actual app/server, another for running the tests.
- Run
functional_tests.py
again - it works! (Fails, but works.) - The book has changed versions, too. So going back to review....
- Ch1: moved
functional_tests.py
intosuperlists
directory. So I do that. - Ch2: no other changes
- Ch1: moved
=== Chapter 3: Testing a Simple Home Page with Unit Tests ===
Suggests making a lists
Django app. I'm not in the mood for all that separation, so keeping all my WebPy stuff in one app.
Django gives you a tests.py
for each app, I'll just make my own equivalent following example from here.
- use his silly intended-to-fail test. Run with
python tests.py
. Fails in nice way.
Unit Test to check that root URL resolves to home_page
function. Relies on Django-specific testing stuff. Plus this seems awfully pointless, so not losing sleep over it. But change my route of /
to home_page
instead of index
.
- but I go ahead and make
test_home_page_returns_correct_html(self)
- this uses
response.data
instead of book'sresponse.content
- but runs/fails properly
- actually I'm not sure it's getting the response object...
- ah, it is, but it's getting a status=404 so
response.data='not found'
- for a bunch of other shim URLs I carried over from other projects, I get
405 Method Not Allowed
- so obviously my
from code import app
is hitting something weird. - ah, needed to just use
response = app.request('/')
- now getting proper result
- ah, it is, but it's getting a status=404 so
- move things forward until all the tests pass, no fails
- this uses
=== Chapter 4: What Are We Doing with All These Tests? ===
Pitching the philosophy of many small tests...
- not really buying it, though like the "ratchet" metaphor - prevent Back Sliding.
Back to functional tests
- adds a whole bunch of steps to the current test
- starts using a template - I had gone straight to that.
- this results in an extra newline at the end which breaks his earlier ends-with-html-close test - I had run into that same issue and figured out the strip solution. Yay.
- Rule Of Thumb: when refactoring, work on either code or tests, but not both at once
- sending
ENTER
didn't work, needed to addfrom selenium.webdriver.common.keys import Keys
- philosophy: functional vs unit tests - he points to Emily Bache post on "DoubleLoop" TDD.
=== Chapter 5: Saving User Input ===
finish form, make a POST
- WebPy unhappy, returns page with "None". Console shows 405-status because we have no POST for home_page.
- but in book there's other issue first - lack of CSRF. I don't have that turned on for my code, so I guess I will now.
- once I put in the CSRF decorator, I have to put a POST handler after it, so I do so with just a
pass
- now get the CSRF failure (not having the token in the form), so add it
- now get different error:
POST() takes exactly 2 arguments (1 given)
- book does not have this error, its next error is from the POST not actually changing the returned data to get found in the next test.
- ah, I had outsmarted myself by adding an arg to the POST function, when that's not what happens when not using the WebPy form handling stuff.
- change POST handler to render the template. Now get same test-fail as book.
- once I put in the CSRF decorator, I have to put a POST handler after it, so I do so with just a
Processing a POST Request on the Server
- separate Unit Test for this
- generate HttpPost like this
- CSRF again! Django must automatically ignore CSRF under certain conditions....
- I see some other frameworks have a way to pass an argument to disable CSRF, but don't see an easy way to do it in WebPy, and not going to add shmutz to my code for this.
- Try calling
app.csrf_token()
but that doesn't work either. - killing this path
- do the standard WebPy bits to handle the input
- Functional Test still fails
- stick in a print statement - hrm
web.input()
is catching the CSRF token, but not the form - ah, the book uses
id
for naming each form item, but WebPy only recognizesname
as handle. In fact now that I look at it, the book usesid
andname
- somehow I screwed that up. - now getting the same test-fail as in the book because of missing the initial number in the return.
- now book is putting obviously-stupid things (static
1:
in front of table entry) in template to make a test pass temporarily. Sorry, but I can't condone this insanity.
- refactoring functional test to make it callable-by-param...
- now I give up and put that static
1:
in to get to same place as book.
- now I give up and put that static
- persistence - ORM. Grrrr.
- so I'll just build a SQL db and my own interface code. Looks like table is named
item
and has 1 field namedtext
- I'm not writing a single Unit Test against silly CRUD stuff
- create SQLite db,
create table items(text varchar(64));
- create
model.py
for my db interface functions, write couple functions - pull out config bits to
config.py
which is what I do in my other WebPy apps - call
model
bits in code, referenceitems
bits in template - works - except still have that static
1:
in place...
- so I'll just build a SQL db and my own interface code. Looks like table is named
- caught up with book dealing with static
1:
- book just puts a counter in the template. WebPy uses$loop.index
for this.- but my entry numbers don't match book's
- bigger issue - every time I run the tests, the same entries get added to the db. The book has the same issue - hooray I didn't miss something earlier.
=== Chapter 6: Getting to the Minimum Viable Site ===
dealing with test-created data
- book uses a Django system that creates a new db every time; WebPy doesn't have that
- so I'll make a
model.delete_test_data()
function. And, unlike the book, I'll call it during the setUp rather than the tearDown because I often like to be able to browse the db after tests are done.
making app multi-list:
- some sort of craziness in the design approach here, I'm reading ahead then making my own process
- it appears each list will have its own slug/URL - later it turns out to just use the numeric
lists.id
for this - but you start creating a new list by entering a list item on the home_page? That's ridiculous.
- when do you name the list? Ah, you don't. Each list just has its ID.
- So I'm aiming for a simple HATEOAS-like walk-through.
- You get a list of lists (and since just having a numeric ID is useless, I'm going to immediately offer chance for optional name, though the ID will be used for the URL) (which a chance to create a new one).
- Then you see any existing entries in the list with a form to add another entry.
- Actually, I take it back - I'll give explicit options for each.)
- Also, while I'll put these get-started forms right on the home page, I'll have the POSTs go to use-specific pages, rather than washing back through the home page.
- it appears each list will have its own slug/URL - later it turns out to just use the numeric
Edited: | Tweet this! | Search Twitter for discussion