perlish testing in python

rjbs on 2005-08-10T01:47:04

I am not a Python programmer. I don't do much work at all in Python. (It is almost entirely accurate to say that I do not use Python at all.) I haven't tried their test systems, which seem sort of OK. One is jUnit. The other sounds neater: you provide a transcript of an interactive Python session, which the test framework tries to reproduce.

I just thought it would be fun to produce TAP in Python. It's got some issues, but not many, and basically works. It's super-simple because it isn't thread safe -- and I have no interest in making it so.

I had some fun writing it, and even found a nice little bug in Test::Builder while doing it. In talking to Schwern about the bug, I learned about the refactoring Schwern and chromatic were working on, and stole those ideas. (I think I also contributed one back, so I don't feel too thiefly.)

So, my library provides TAP.Builder, which is very similar to Test::Builder. It doesn't deal with exporting at all, because that's all different in Python. I also implemented Test::More, but it sucked. To write a useful Test::More equivalent, I need to use Python more so that I understand the useful things to provide, and how to do so properly.

Writing this reminded me of two things: (1) Python isn't as annoyance-ridden as I sometimes thing and (2) Python's actual annoyances are incredibly annoying. I long for anonymous functions, useful destructors, and interpolation (not template strings). Still, I had fun.


Some Answers (Hopefully)

polarcowz on 2005-08-10T15:21:26

Anonymous functions: Generally, if it's a small amount of logic, you can get away with using a lambda. Alternatively, you might be looking for a generator, depending on how you use your anonymous subroutines. A decent example is located here:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/361452

Destructors: Destructors are present in Python. Within the class, the __del__(self) method provides this functionality. (Un)Fortunately, it gets called when the object is garbage-collected, so calling del varname removes the variable, not the referred-to object. If the reference count drops to zero, the object will get garbage-collected but you don't have much control. You might be able to call it manually (i.e.: varname.__del__()) but I don't know if that yields any nasty errors or other problems, so I wouldn't advise it. Again, example:
http://mail.python.org/pipermail/python-list/2000-January/019837.html

Interpolation: Unfortunately, there's not much I can offer up on this. Because of the lack of sigils, it would be difficult to do interpolation reliably. In Python, explicit is better than implicit. So you're basically left with the print "%s %s %d" % var1, var2, var3 idiom. It's not necessarily as convenient as Perl but it's also easy to see what's being used in the string. (Shrugs)

I'm sorry if I haven't been much help or have repeated what you already knew. If you have questions, please feel free to contact me.

Re:Some Answers (Hopefully)

rjbs on 2005-08-10T15:40:05

Thanks for the reply!

Generators are almost what I want, but not quite, and the same goes for lambdas. It may just be a matter of programming Python in Python, instead of what I'm doing now. I've used both, but sometimes I just want a coderef. I'd probably mind it less if I didn't feel it was one of the "don't give the programmer sharp tools" decisions (like "let's remove reduce!").

I know about and use destructors, even in the example. Unfortunately, their use not only occurs at garbage collection, but breaks GC. AIUI, GC stops at any found destructor -- or something quite like that. Also, destructors aren't reliably called at program end, so you can't rely on them to cleanup at end time. My hack is gnarly: register the destructor as an atend callback, and if it's called twice, do nothing the second time. (Because there are also no END blocks, and atend doesn't seem to allow removal of registrations.)

The interpolation thing is just me whining. :)

Re:Some Answers (Hopefully)

polarcowz on 2005-08-10T19:05:26

My apologies, I should have looked at the code you had written and previous posts first.

The only thing I don't understand is why the destructors are not being called at the end of the program, as that should not happen. Everything is supposed to be cleaned up at the end.

You might want to check into the gc module. It provides an interface and debug options for garbage collection. Some doc is located here: http://docs.python.org/lib/module-gc.html

And if I'm still telling you things you know, put me in my place. ;D

Re:Some Answers (Hopefully)

rjbs on 2005-08-10T19:39:00

Wow, the gc module looks like it would be obnoxious to use here, although it could surely do the job!

I asked on #python (freenode) about this a few days ago, and there was shock and horror at the suggestion of using a destructor to do object cleanup (or anything, for that matter). What the heck?

Re:Some Answers (Hopefully)

Andrew Langmead on 2005-08-14T16:08:15

I can understand the difference being a bit uncomfortable, but I eventually got used to using named inner functions in place of anonymous functions. Since the large Python project I worked on started under Python 2.1.x, when the scoping rules were still a bit weak, it was the only way of getting variables shared in closure.

I guess one of the things that still bugs me about python is the lack of an equivilent to "my". Errors in perl that would be caught at compile time wind up being runtime "UnboundLocal" exceptions.