Here is an interesting video (and blog post) on Ruby vs. Python by Gary Bernhardt. One of the advantages Gary gives to Ruby is the ability to develop and use tools like rspec or cucumber, which use Ruby's block syntax for really nice looking unit tests, runnable specs, etc.
In the talk Gary shows that he was able to create a spec runner with syntax similar to rspec by using "really nasty" and "ugly" hacks (sys.settrace I believe). But, even if these techniques are ugly, they are becoming more easily accessible and more easy to develop with tools like withhacks, which abstracts the ugliness away.
I'm sure this does much less than what Gary's mote does, but here is a simple spec runner using withhacks:
from __future__ import with_statement from withhacks import CaptureOrderedLocals, CaptureBytecode class specs(CaptureOrderedLocals,CaptureBytecode): def __init__(self,what, *args, **kwargs): self.__what = what self.__args = args self.__kwargs = kwargs self.results =  super(specs,self).__init__() def __exit__(self,*args): retcode = super(specs,self).__exit__(*args) results = self.run_specs(self.locals) self.results = results def run_specs(self, cases): results =  num_pass, num_fail = 0,0 print "Testing %s specs for %s:" % (len(cases), repr(self.__what)) for (name, func) in cases: if not callable(func): continue name = name.replace('_', ' ') name = name.capitalize() + '.' print "->", try: func() error = None num_pass += 1 except BaseException, e: error = repr(e) num_fail += 1 if error: print "[FAIL]", name print "--->", error results.append((name, False, error)) else: print "[pass]", name results.append((name, True, None)) print "Result: %s/%s passed, %s/%s failed" % (num_pass, len(cases), num_fail, len(cases)) print "-"*20 return results
And here is an example spec:
class MyClass(object): def add(self, a, b): return a+b with specs(MyClass): def it_adds_two_and_two(): c = MyClass() assert c.add(2,2) == 4 def it_adds_negatives(): c = MyClass() assert c.add(10,-10) == 0 def it_fails_adding_int_and_string(): c = MyClass() try: c.add(10, 'foo') except TypeError: pass #correct! def testing_what_a_spec_failure_looks_like(): c = MyClass() c.thisdoesntexist()
And here is the output:
Testing 4 specs for <class '__main__.MyClass'>: -> [pass] It adds two and two. -> [pass] It adds negatives. -> [pass] It fails adding int and string. -> [FAIL] Testing what a spec failure looks like. ---> AttributeError("'MyClass' object has no attribute 'thisdoesntexist'",) Result: 3/4 passed, 1/4 failed --------------------
I put the code on bitbucket here.blog comments powered by Disqus