Permalink
Browse files

Completed core of whichtests, models, and views.

  • Loading branch information...
carljm committed Mar 8, 2012
1 parent 193e791 commit 31adbd2ce062f904dd706a4daf26e164a7e463ba
View
@@ -18,5 +18,12 @@ $(function() {
}
}
});
+ $(".content").bind("showoff:prev", function(event) {
+ var slide = $(currentSlide).find(".content");
+ no.hide();
+ if(slide.hasClass("antipattern")) {
+ slide.data("overlaid", false);
+ }
+ });
});
});
View
@@ -3,31 +3,19 @@
## The database makes your tests slow. ##
* Try to write tests that don't hit it at all.
-* But you're still going to have a lot of tests that do.
-
-<!SLIDE>
-
-# `django/tests/testcases.py` #
-
- @@@ python
- def _fixture_teardown(self):
- # ...
- restore_transaction_methods()
-
- for db in databases:
- transaction.rollback(using=db)
- transaction.leave_transaction_management(
- using=db)
+* But you'll still have a lot of tests that do.
+.notes The DB is pretty core to most web apps; mocking it makes your tests fast, but I don't think it's worth it.
<!SLIDE incremental>
-# `TestCase` #
+# `django.test.TestCase` #
* Runs each test within a transaction.
* Rolls back the transaction at the end of the test.
* Monkeypatches transaction functions to be no-ops.
+.notes Django tries to make them fast...
<!SLIDE incremental>
@@ -36,3 +24,5 @@
* Lets you test transactions in your code.
* Has to flush every database table after every test.
* Makes your tests extra super bonus slow.
+
+.notes This part of the talk is boring because I have no complaints.
View
@@ -13,6 +13,8 @@
"is_staff": false,
"last_login": "2012-02-06 15:06:44",
+.notes Do you have these in your tests? BURN THEM!
+
<!SLIDE incremental>
# Just say no. #
@@ -39,25 +41,37 @@
return Profile.objects.create(
**defaults)
+.notes You can write simple factory functions like this (key benefit is prefilling FKs).
+
<!SLIDE>
-# Model factories! #
+# Using a factory #
@@@ python
def test_can_vote(self):
- """A user older than 18 can vote in the US."""
- profile = create_profile(age=19)
+ """A user age 18+ can vote in the US."""
+ profile = create_profile(age=18)
self.assertTrue(profile.can_vote)
+.notes BAD example. This test shouldn't touch the DB. So you want a smarter factory that can also build objects without saving.
<!SLIDE incremental>
-# It's been done. #
+# Or use `factory_boy`: #
+
+ @@@ python
+ class ProfileFactory(factory.Factory):
+ FACTORY_FOR = Profile
+
+ likes_cheese = True
+ age = 32
+ address = "3815 Brookside Dr"
+ user = factory.SubFactory(UserFactory)
+
+ profile = ProfileFactory.create(
+ age=18, user__username="carljm")
-* factory_boy
-* milkman
-* model-mommy
-* probably others
+.notes Also there's milkman, model_mommy. I don't like random data generation.
<!SLIDE incremental>
View
@@ -0,0 +1,3 @@
+<!SLIDE section>
+
+# Testing views #
View
@@ -0,0 +1,47 @@
+<!SLIDE incremental>
+
+# *Unit* testing views is hard. #
+
+* Views have many collaborators / dependencies.
+
+* Templates, database, middleware, url routing...
+
+* Write less view code!
+
+.notes Views have access to everything, so code in views is easy to write but
+hard to maintain and debug.
+
+<!SLIDE>
+
+# If you unit test views #
+
+* Use `RequestFactory`.
+
+* Call the view callable directly.
+
+* Set up dependencies explicitly (e.g. `request.user`, `request.session`).
+
+
+<!SLIDE>
+
+ @@@ python
+ def test_change_locale(self):
+ """POST sets 'locale' key in session."""
+ request = RequestFactory().post(
+ "/locale/", {"locale": "es-mx"})
+ request.session = {}
+
+ change_locale(request)
+
+ self.assertEqual(
+ request.session["locale"], "es-mx")
+
+<!SLIDE incremental>
+
+# Or don't. #
+
+* I rarely unit test views.
+
+* I write less view code, and cover it via functional tests.
+
+* Functional tests are slower, but catch more bugs.
View
@@ -0,0 +1,75 @@
+<!SLIDE>
+
+# Functional testing views #
+
+<!SLIDE antipattern>
+
+ @@@ python
+ url = "/case/edit/{0}".format(case.pk)
+ step = case.steps.get()
+ response = self.client.post(url, {
+ "product": case.product.id,
+ "name": case.name,
+ "description": case.description,
+ "steps-TOTAL_FORMS": 2,
+ "steps-INITIAL_FORMS": 1,
+ "steps-MAX_NUM_FORMS": 3,
+ "steps-0-step": step.step,
+ "steps-0-expected": step.expected,
+ "steps-1-step": "Click link.",
+ "steps-1-expected": "Account active.",
+ "status": case.status,
+ })
+
+
+<!SLIDE>
+
+# or... #
+
+<!SLIDE>
+
+# WebTest! #
+
+ @@@ python
+ url = "/case/edit/{0}".format(case.pk)
+ form = self.app.get(url).forms["case-form"]
+ form["steps-1-step"] = "Click link."
+ form["steps-1-expected"] = "Account active."
+
+ response = form.submit()
+
+.notes WebTest parses the form HTML and can submit it like a browser would.
+
+<!SLIDE incremental>
+
+# The markup matters. #
+
+* If it can break, it should be tested.
+* It can especially break forms.
+* The output of your view is an HTTP response; the template + context is an
+ implementation detail.
+
+.notes Have to know which markup matters, of course.
+
+<!SLIDE incremental>
+
+## WebTest > django.test.Client ##
+
+* Tests are easier and faster to write.
+* Tests give you more confidence that the view works.
+* (django-webtest provides integration.)
+
+.notes Django test client is in the "sour spot" - not a unit test, not a full functional test.
+
+<!SLIDE>
+
+ @@@ python
+ self.assertEqual(
+ response.json, ["one", "two", "three"])
+
+ self.assertEqual(
+ resp.html.find("a", title="Login").href,
+ "/login/"
+ )
+
+.notes Automatically parses JSON or HTML responses (BeautifulSoup or lxml).
@@ -14,17 +14,24 @@
FAILED (errors=2, skipped=1)
Destroying test database for alias 'default'...
+.notes (Oops, guess we're not quite ready to release 1.4.)
+But 412 tests? 14 extra seconds?
<!SLIDE>
-## I'll never get back those 14 seconds. ##
+## I'll never get those 14 seconds back. ##
<!SLIDE>
-## Not all apps are created equal. ##
+## Not all apps are created equal. ##
+
+.notes Non-isolated tests break, isolated tests are pointless to run.
+Integration tests should be written by the integrator.
<!SLIDE>
## No problem. ##
### `./manage.py test just my apps please` ###
+
+.notes Easy to solve with a shell script. But there's more...
@@ -3,7 +3,12 @@
# `tests/__init__.py` #
@@@ python
-
from .test_forms import QuoteFormTest
- from .test_models import QuoteTest, SourceTest
- from .test_views import AddQuoteTest, EditQuoteTest, ListQuotesTest
+ from .test_models import (
+ QuoteTest, SourceTest)
+ from .test_views import (
+ AddQuoteTest, EditQuoteTest,
+ ListQuotesTest
+ )
+
+.notes Django made me do it. (Or worse yet, "import *".)
View
@@ -5,3 +5,5 @@
* Wastes my time with tests I don't care about.
* Requires tests to be in a single module.
* Forces intermingling of tests and non-test code.
+
+.notes But there's good news...
@@ -1,10 +1,12 @@
<!SLIDE incremental>
-# But it's easy to change. #
+# It's easy to change. #
* unittest2 discovery
* `TEST_RUNNER`
+.notes The hipsters like nose or py.test, but unittest2 gets the job done.
+
<!SLIDE small>
@@@ python
@@ -27,6 +29,8 @@
return reorder_suite(suite, (TestCase,))
+.notes (Enhanced version in the code online with the slides.)
+
<!SLIDE small>
# `settings.py` #

0 comments on commit 31adbd2

Please sign in to comment.