Lately, I've been spending some private time learning Python. It is definitely a different beast than C#. For one, it has taken a little while to get used to the dynamicism of Python compared to C#. Now, don't get me wrong, I'm still in love with C#, I just am falling in love with certain aspects of the Python language. And, well, I'm going to blog about it here because I don't really have any one to share this with at the moment and I need to get it off my chest.
I just had my first “OH SHIT!” moment in Python. Before I describe that though, a little background to what I've been trying to do with unit testing lately. I've been thinking a lot about mock objects. What I wanted was an easy way to intercept any call to a given class at runtime and redirect it to a mock object. For instance, if class Foo creates an instance of class Bar, I want to say that any future method call to anything in Bar should be redirected to my class MockBar. Now, there are easy ways to solve this issue for most circumstances. For example, passing an IBar object to the method call or to the Foo class constructor is an option most of the time. But, there are times where this can lead to code and APIs that aren't perhaps the most usable APIs.
Usually, it requires a little give and take. Code that is really testable isn't necessarily the most intuitive to use. You have to compromise (I'm talking generally here). As with many other things, it is something where you have to choose which side to design for. For instance, highly performant code isn't necessarily the easiest code to read. On the other hand, code that is really easy to read isn't necessarily the most performant. At times, it's the same with testability and usability, I believe. You may believe otherwise of course, but that doesn't mean you're right :P. Of course, there are exceptions to everything I'm saying here :).
Okay, with that out of the way, on to the moment I had just now. Since Python is a dynamic language, and everything is considered an object (even function and class definitions themselves), we can automatically alias our mock objects to test our code. As an example, let's take a small example. First, we will just alias a function definition. Here is a small snippet of code that has a method that we may want to mock. Here is the pre-mocked version:
>>> class Foo:
... def bar(self):
... print 'Hello from boring bar'
...
>>> myFoo = Foo()
>>> myFoo.bar()
Hello from boring bar
To mock this, we simply need to define a new mock function and re-alias the bar function to point to our mock function. Here's how it might look (and you can see the output):
>>> def mockBar(self):
... print 'Hello from mockBar, baby!!!'
...
>>> Foo.bar = mockBar #<-- Alias the bar definition in class foo to our new mockBar function
>>> myFoo.bar()
Hello from mockBar, baby!!!
That is extremely cool! And it just doesn't stop with functions. You can do the same aliasing at the class level. Remember, EVERYTHING is an object in Python, even class and method declarations. Check it out:
>>> class Foo:
... def myMethod(self):
... myBar = Bar()
... myBar.sayHello()
...
>>> class Bar:
... def sayHello(self):
... print 'Hello from boring Bar'
...
>>> myFoo = Foo()
>>> myFoo.myMethod()
Hello from boring Bar
>>>
>>> class MockBar:
... def sayHello(self):
... print 'Hello from MockBar, Baby!!!!'
...
>>> #Alias Bar class now
>>> Bar = MockBar
>>>
>>> #Retest myMethod()
>>> myFoo.myMethod()
Hello from MockBar, Baby!!!!
I love this language!
Now, there is a method to do something very similar to this with C# using context-bound objects and switching the context at runtime. However, you have to realize that there is overhead involved in making an object and context-bound object. Also, since multiple inheritance is not possible in .NET, sometimes making an object a context-bound object just isn't possible.
One thing is for sure, I'm sure this love affair isn't over. However, it's not all bright and cheery. There are some things that I have found that I don't like in Python. I think that a lot of those though are simply because I've been so used to using statically-typed languages. Time will tell, and I'm sure you all will hear more about it as time passes.
NOTE: This doesn't mean that you will automatically write good tests by having this tool available to you. Also, I would say that you should try not to use this testing method every time either. There are better ways to test your code. However, in those situations where other roads aren't available, this could be a life saver (or so I imagine, since I actually haven't been able to use it in a production environment yet).