Monday, January 17, 2011

Configuring PyQt across unit-tests

We had some problems with configuring the PyQt API version across unit-tests. The problems arise because several modules try to call sip.setapi, which you can only do once (even if you call it with the same args, which ought to be idempotent, you'd think.)

Anyway, what I did was to create a module called 'pyqtconfig' with the following contents:


import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
sip.setapi('QTextStream', 2)


import PyQt4.Qt
Qt = PyQt4.Qt

Then I simply do 'from pyqtconfig import Qt' in each module. Works like a charm.

(If you want to import each module separately, you need to adapt the above code somewhat. I'll leave that as an exercise for the reader. :)

Trac and Kanban

(Don't miss my update Trac and Kanban Redux)


We use a Trac wiki page as a simple kanban board. It's pretty neat, especially when put on a 42" LCD TV in the project room.


Here's how it looks in the browser:

And here's the wiki code, all built using TickeyQuery-macros, both for counting (to keep an eye on the WIP-limit) and listing the tickets themselves. It's pretty basic, but it works well  for our small team and it's easy to understand and maintain.


This is the ticket dashboard for Orcamp for [milestone:"Orcamp 1.0"] ([/query?milestone=2010+M11&order=priority&col=id&col=summary&col=status&col=type&col=priority&col=milestone&col=component tickets])  -- [wiki:Orcamp/FeatureList current feature list]

{{{
#!div style="float:left; margin-right:1em; width:30%"

= Open Tickets = 

Total: [[TicketQuery(status=new|reopened,milestone=Orcamp 1.0,format=count)]] 

[[TicketQuery(group=priority,status=new|reopened,milestone=Orcamp 1.0)]]

}}}
{{{
#!div style="float:left; margin-right:1em; width:30%" 

= Work In Progress =

Total: [[TicketQuery(status=accepted|testable,milestone=Orcamp 1.0,format=count)]] 

== Started ==

[[TicketQuery(status=accepted,order=priority,group=owner,milestone=Orcamp 1.0)]]

== Ready for Test ==

[[TicketQuery(status=testable&review_issues=0&reviewed!=,group=developertest,milestone=Orcamp 1.0)]]

== Reviewed tickets with issues ==

[[TicketQuery(review_issues=1,group=owner,status=testable|closed,resolution=fixed,milestone=Orcamp 1.0)]]

== Unreviewed tickets == 

[[TicketQuery(review_issues!=1,status=closed|testable,group=status,resolution=fixed,reviewed=,milestone=Orcamp 1.0)]]


}}}
{{{
#!div style="float:left; margin-right:1em; width:30%" 


= Closed Tickets = 

Total fixed: [[TicketQuery(status=closed,milestone=Orcamp 1.0,resolution=fixed,format=count)]] 
Other: [[TicketQuery(status=closed,milestone=Orcamp 1.0,resolution!=fixed,format=count)]] 

== Fixed tickets ==

[[TicketQuery(status=closed,group=type,resolution=fixed,milestone=Orcamp 1.0)]]

== Other resolutions ==

[[TicketQuery(status=closed,group=resolution,resolution!=fixed,milestone=Orcamp 1.0)]]


}}}

{{{
#!div style="clear:both"
}}}

Note that the above wiki code contains some references to custom fields (notably review_issues) that we use to manage code reviews on a ticket-by-ticket basis. In our small team, we've decided to allow commits of unreviewed code, but require code to be reviewed before it's cleared for testing. 


As always, you should adapt to local conditions. :)

Sunday, January 9, 2011

BuildBotIcon - Python Style (and it's got tests this time!)

If you've read earlier posts here, you might know that I'm behind BuildBotIcon, a small Java application that sits in the system tray, polling one or more BuildBot servers and reports any changes. It also plays sounds and turns lava lamps on or off.

I've decided to do a version in Python using PyQt instead. It's mostly as a fun exercise swim in the ecosystem of python (setuptools, unittest, mocking, coverage, yaml, etc) but it might come out better than the original.

PyQs nice, and the new-style signals-slots makes it a breeze to do what you want if, assuming you know your way around Qt. (Not that it's hard to learn, I just knew Qt pretty well before embarking on this project.)

Writing unittests are a breeze, and I think that it sure helps knowing that the code works, instead of knowing it only compiles. Coming from C++ this is actually pretty nice change. However, you should write with testing from the beginning. Grafting unit-testing later on is difficult and might not be a net win for all components, especially in C++ . (Mostly since you've run them for a while and the obvious (and thus easy-to-test-for) bugs have already been ironed out.)

However, the greatest fun so far has been using Mock, yet another mock object module for python that takes a nice and intuitive approach to mocking, IMHO. Instead of record/replay, or setup/run/verify, it's more like mock/run/assert. Read Mocking, Patching, Stubbing: all that Stuff (and it's link to Mocks aren't Stubs) for more information on this.

The ability of substituting any function or object in the APIs you're running makes unittesting immensely powerful and thorough. If you trust the framework (Qt in my case) it's very easy to verify that play() is called on the right QSound (without making noises) or that get() is run with the right URL to access the network.
Of course, more complex behaviours are trickier to mock properly, so it helps to refactor your app into testable parts (which you should do anyway).

Also, I've used YAML for the settings file this time (using PyYAML). All is good and well, except that it turns out that writing the settings file first and implementing the app second had probably been a better idea. TDD again.

It has all fallen out pretty nicely so far, except that, similar to the Java version, I've bungled together the backend that does networking and parsing with the UI. Since I currently have three ways to show build state (tray icon, sound and lava lamps) I should really had put a Listener interface in there. Fixing that now means refactoring the unit tests too, which is less fun. :-|

Nevertheless, all of this is experience is bound to pay back in my ThrustRacer-project (should I get it off the ground) or at work, where we're about to embark on a year of much Python coding. I don't expect either to get it right from the start, one never does. :)