[TIP] running away from RSpec

Michael Foord fuzzyman at voidspace.org.uk
Sun Feb 13 05:43:23 PST 2011


On 13/02/2011 13:33, Michael Foord wrote:
> On 12/02/2011 19:58, Gary Bernhardt wrote:
>> On Sat, Feb 12, 2011 at 10:34 AM, Alfredo 
>> Deza<alfredodeza at gmail.com>  wrote:
>>> This more of an open question I guess, but why there isn't a project in
>>> Python that grabs the best features from RSpec
>>> and implements them?
>> As you suggest, it's impossible. Mote and PySpec are just fancy ways
>> to name your tests: weak implementations of one tiny corner of RSpec.
>> Mote uses horrible settrace magic; PySpec adds a bunch of
>> domain-irrelevant noise. Neither even supports arbitrary context
>> strings. RSpec is more terse, more expressive, removes the noise, and
>> is an entirely reasonable thing to build in Ruby.
>>
>> That last point is important: it's not just that RSpec is possible in
>> Ruby; it's actually idiomatic. I think that people don't realize just
>> how easy it is to build its syntax. RSpec lets you say things like:
>>
>>    describe Integer do
>>      it "adds numbers" do
>>        (1 + 1).should == 2
>>      end
>>    end
>>
>> Looks complicated and magical, right? Nope. Here's an implementation
>> of describe and it:
>>
>>    def describe(name)
>>      puts name
>>      yield
>>    end
>>
>>    def it(name)
>>      puts "- #{name}"
>>      yield
>>    end
>>
>> This will output the spec output that's become so familiar:
>>
>>    Integer
>>    - adds numbers
>>
>> I spent a couple days of my life trying to get Python to do this when
>> writing Mote and failed completely.
>>
>> The `2.should == 2` syntax is also simple, but here you can get close
>> in Python. Expecter Gadget [1] lets you say `expect(1 + 1) == 2`. I
>> still prefer RSpec because the statement reads as "subject,
>> relationship, expected value".
>>
>> There's much more terseness to be had than this, though; the above is
>> just the most trivial example. You can say things like:
>>
>>    describe Dog do
>>      it { should have(4).feet }
>>      it { should be_thirsty }
>>    end
>>
>
> You could do the following in Python.
>
> with describe(Dog) as it:
>     it.should.have(4).feet
>     it.should.be_thirsty
>
> Where "it" collects tests which a runner executes when collection 
> completes (or in response to __exit__). You could inject "it" using 
> "with hacks" style magic too.

Or "it" could just be imported. It knows from the describe(Foo) 
__enter__ and __exit__ pairs when you are entering a new test context, 
which with the right magic would allow for nesting (entering a new 
context before exiting the current one means you are nested).

For hooking up functions to calls to "it.should.something" you could use 
naming conventions. For example:

def Dog_something(dog):
   ...

Test collection is done on import, functions are mapped after import but 
before test execution.

Michael

>
> The Integer example *could* become:
>
> with describe(int) as it:
>     with it ("adds numbers"):
>         assert 1 + 1 == 2
>
> Not that I'm particularly fond of this or going to implement it myself...
>
> Michael
>
>> This is totally stock RSpec (and totally self-contained).
>> `have(4).feet` asserts that `Dog.new.feet.size == 4`; `be_thirsty`
>> asserts that `Dog.new.thirsty?` returns true.
>>
>> The most equivalent Python is:
>>
>>      class DogTest:
>>          def setup(self):
>>              self.dog = Dog()
>>
>>          def test_that_it_has_4_feet(self):
>>              assert len(self.dog.feet) == 4
>>
>>          def test_that_it_is_thirsty(self):
>>              assert self.dog.is_thirsty()
>>
>> (Inline the dog construction if you like; that just moves the problem 
>> around.)
>>
>> Some Python programmers will probably turn their noses up at that
>> RSpec example because it's fancy (I know I would've). I think that's
>> unreasonable. It's significantly more terse *and* more readable than
>> the Python: a combination we don't often get.
>>
>> "More readable" does depend on learning RSpec, but that's easy because
>> it's so intuitive. And I bet that you knew exactly what it meant upon
>> reading it, even if its mapping onto the Dog class wasn't obvious.
>>
>> Getting back to the point: you just can't do this stuff in Python.
>> I've spent a lot of time over the last couple years thinking about
>> this and eventually I just gave up. It's one of the reasons that
>> almost all of my programming is now done in Ruby.
>>
>> [1] https://github.com/garybernhardt/expecter
>>
>
>


-- 
http://www.voidspace.org.uk/

May you do good and not evil
May you find forgiveness for yourself and forgive others
May you share freely, never taking more than you give.
-- the sqlite blessing http://www.sqlite.org/different.html




More information about the testing-in-python mailing list