- tests
- fewer skin customizations and monkeypatches
- more generic code
- motivation
- how to get there
Why do we care?
- Tests help us make better quality code
- Better quality means a more satisfied customer
- Testing through the web is no alternative:
- Slower (think 3rd time you fake something TTW to test a feature)
- Regression bugs
- Automated tests mean better quality in less time
- Essential part of XP
- Think about what you want to do
- Think about how you test it
- Test-first: Write a test and have it fail
- Leads to better code, which allows for easy code changes (also refactoring) and helps in removing unneeded code (a.k.a. clutter)
- Helps you find design/API problems
- A tested system has fewer bugs
- pdb is your friend: Failing tests are very helpful for finding bugs. Put the pdb where the heart of the problem is -> immediate feedback!
- When you have tests but you're still encountering many errors through the web, you're not testing hard enough. The problem with this situation is that people might ask: Why do we have tests at all? or Tests aren't that useful after all.
- Test against interfaces of a unit (again: makes you think of what interface you want)
- Test everything that you think could fail. One test per function is too little!
- Add a test for every bug to show the bug and to help you fix the bug. And to avoid bug regression.
Tests can be very good developer documentation if applied and documented properly. By these measures, this isn't quite the full shilling:
def testConnectAndDisconnect(self):
triples = [(self.objs[0].UID(), self.objs[1].UID(), ruleset.getId())
for ruleset in self.rulesets]
processor.process(self.portal, connect=triples)
for ruleset in self.rulesets:
self.assertEquals(self.objs[0].getRefs(ruleset.getId())[0],
self.objs[1])
processor.process(self.portal, disconnect=triples)
self.assertEquals(len(self.objs[0].getRefs()), 0)
self.assertEquals(len(self.objs[1].getBRefs()), 0)
This test is not documentation. Also, in case this test goes wrong, I'll have a hard time understanding what it does.
An example for a test that doesn't really do something useful:
def test_tools(self):
"""
"""
ids = self.portal.objectIds()
self.failUnless('archetype_tool' in ids)
A couple of these and you are led to believe that the system is well tested when you run the tests.
This is better:
# ...
# Note that 'f1' is a folder that's owned by the manager, but
# we still can delete and rename objects that we own inside
# that folder.
# anotheritem1 isn't owned by us. So we can't folder_rename
# or folder_delete.
paths = request['paths'] = [pathOf(f1.anotheritem1)]
self.assertRaises(Unauthorized,
f1.folder_rename,
paths=paths,
new_ids=['mynewitem'],
new_titles=['Junk'])
f1.folder_delete()
self.failUnless('anotheritem1' in f1.contentIds())
# We own myitem1. That means we can folder_rename and
# folder_delete it.
f1.folder_rename(paths=[pathOf(f1.myitem1)],
new_ids=['myfreshitem1'],
new_titles=['Junk'])
self.failUnless('myfreshitem1' in f1.contentIds())
self.failIf('myitem1' in f1.contentIds())
And this is terriffic ;-):
class ReferenceSource(object):
"""A base implementation of an IReferenceSource adapter that
relies on a IReferenceQuery local utility to look up targets and
on an IReferenceStorage adapter to add references.
Basic setup:
>>> import archetypes.testing
>>> archetypes.testing.setupPortal()
>>> archetypes.testing.wireUp()
We create an object that we adapt to our ReferenceSource:
>>> obj = archetypes.testing.FakeObject('sourceobj')
>>> source = ReferenceSource(obj)
Continued example:
We don't have any targets or relationships at this point:
>>> source.getTargets()
[]
>>> source.getRelationships()
set([])
Let's add a target:
>>> target = archetypes.testing.FakeObject('targetobj')
>>> ref = source.addReference(target, 'pooh')
>>> ref
<archetypes.testing.FakeReference object at ...>
>>> ref.source is obj
True
>>> ref.target is target
True
Good things:
- Serves as developer documentation
- Uses mockup objects
- Also known as functional tests
- We need both unit tests and integrational tests; they test our software at different levels
- Black-box tests: test the composition of units from the outside, in our case: from the HTTP level
- Well suited for acceptance tests and user documentation if in the form of zope.testbrowser (traditional functional tests are not suited for that)
- Can be run against live (preview) system
A zope.testbrowser test fragment:
...
Now that everything is in place, we can start two seperate browser
sessions.
>>> from Products.Five.testbrowser import Browser
>>> browser1 = Browser()
>>> browser2 = Browser()
Let's login with the first browser:
>>> browser1.open('http://nohost/plone/')
>>> browser1.getLink('Log in').click()
>>> browser1.url
'http://nohost/plone/login_form'
>>> browser1.getControl(name='__ac_name').value = 'test_user_1_'
>>> browser1.getControl(name='__ac_password').value = 'secret'
Continued example:
>>> browser1.getControl(name='submit').click() >>> "You are now logged in" in browser1.contents True At this point, the first browser has two cookies: >>> printCookies(browser1) # doctest: +ELLIPSIS __ac="..." koekje="..." >>> len(listCookies(browser2)) 0
- Uses Python's unittest.
- Tests: Example 1 to 3
- We know this style
- Still, we have to write more and better tests in this style!
- Uses Python's doctest
- Uses reStructured Text
- Tests: Example 5 and zc.relationship documentation.
- The original idea behind doctests is to test documentation so it doesn't get outdated
- Documentation is good, outdated documentation is bad
- The other view on doctests is tests that are well documented
- Doctests can be in:
- seperate text files (doctest.DocFileSuite), e.g. the "README.txt" we just saw (Emacs rst-mode)
- docstrings of implementation (doctest.DocTestSuite), e.g. Tests: Example 4
- easy to find: documentation where the code is
- get crowded if you address too many corner cases
- docstrings of test class
- Allow for easy through the web rapid style development
- But they have problems:
- not easily testable
- violate against DRY: lots of code duplication
- too much logic in templates, python scripts
- result in dependency problems between versions of Plone (breakage not easily testable)
Skin customizations are not avoidable in some cases, but their number should be minimized. Examples:
- for skins: try to do it with CSS only (DIYPloneStyle)
- for template/python (script) logic: try to do it in views or unrestricted code
- try to make it a configuration, try to push your changes down into Plone
- Monkey patching is bad
- Try to find other ways to do it (e.g. inheritance, adapters)
- By more generic code we mean reusable code
- Reusable and tested components will be used by others who find it helpful
- more features for things we need (think about Danny's UI enhancements for OrderableReferenceField)
- share maintenance work with others
- General code helps us being good open source citizens and contribute back
What makes a motivated developer?
- Doing something well is fun
- Guessing and fearing breakage is not fun
- Learning is fun. Besides that, it'll increase your personal value, which means $$$
- $$$ is exciting
- Python is exciting
- Zope 2 is not exciting (too much guessing, hard to learn), but Python is still exciting
- Zope 3 is new and exciting, and with it Python gets even more exciting
- Doing something new is exciting
- Contributing back to the community is exciting
- Nice working environment and understanding for the other is good
- Helping each other is good
I think we're pretty good at motivating customers, but still:
- Surpass their expectations, by showing them terrific work
- Never leave QA work to the customer; if something isn't tested, it shouldn't be released to the customer
- Large customers have higher expectations than merely Plone; we want to support their ideas and visions in the best possible way
- A motivated customer is the best advertisement
- This is all nice and good, but how should we explain this to the customer?
- It's not something that we need to explain to the customer
- Don't let payability get in the way of progress
- The customer expects us to learn in order to deliver quality software
- We have to deliberately and continuously work on ourselves because of the nature of our business
- Learning means we can do things better and faster (Others are getting better, too)
- It will pay off sooner than you think!
- Think how much time you spend bughunting
- Bughunting is not fun
- Good software designs are fun
- Ask your colleagues if you don't know how to apply a certain technique!
- Doesn't cost you anything
- Means $$$
- Pair program:
- Team spirit
- Learning is best done face to face
- Pair programming is the best knowledge transfer
- Take your time because you don't have anything better to do!
- By transferring knowledge everyone grows
- I think we should pair program all the time (any combination of skills works!)
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren't special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one-- and preferably only one --obvious way to do it.
- Although that way may not be obvious at first unless you're Dutch.
- Now is better than never.
- Although never is often better than right now.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea -- let's do more of those!