miercuri, 20 mai 2009

Python mocking __new__

In acest tutorial voi prezenta o facilitate python extrem de interesanta: inlocuirea metodei speciale __new__. Acest lucru mi-a fost util in momentul in care am scris o suita de teste pentru o clasa. Problema era ca aceasta clasa era Singleton si in concluzie era dificil sa ma asigur ca resursele injectate sunt corecte de la un test la altul. Solutia de compromis a fost sa elimin comportamentul Singleton al clasei doar pentru teste.

In this tutorial I will show you an unsual feature of python: replacing special method __new__. This seemed extremely useful to me in the moment I wrote some unit tests for a class. Adding the fact the class was a Singleton makes the problem even more interesting. It was difficult for me, to make sure every test was injecting the correct resources. The solution I came up with was to eliminate the "singleton" behavior just for tests.

class TestedClass(float):
def mockedNew(cls, *args, **kwds):
print("Mocked new")
self = float.__new__(*args, **kwds)
return self

def deleteNew(cls):
cls.__new__ = cls.mockedNew

def __new__(cls, arg=0.0):
print("Normal new")
return float.__new__(cls, arg*0.0254)

def __init__(self, arg=0.0):
print("Not working")

deleteNew = classmethod(deleteNew)
mockedNew = classmethod(mockedNew)

if __name__ == "__main__":
TestedClass.deleteNew()
print(TestedClass(12))

In mod normal, in exemplul de mai sus, daca nu as apela deleteNew rezultatul ar fi:
Usually, if I don't invoke deleteNew the result would be:
  • Normal new
  • Not working
  • 0.3048
Calling deleteNew changes the result to:
  • Mocked new
  • Not working
  • 12
In this manner I managed to change __new__ method behavior dynamically.

Python and Mocker

In this tutorial I'll show you how you can test your algorithms by eliminating dependencies(filesystem, database, ldap, and so on). Doing like this, you can easily follow the business logic and assert for the expected results. In the following example, I'll show you how to eliminate filesystem dependency.

def complex_algorithm(objOpen=open):
#here comes the business logic

#here comes the filesystem dependency
f = objOpen("test_file.txt", "w")
f.write("simple test")
f.close()

We can easily see that I provided an argument(objOpen) with default value of open builtin. In this manner, I can inject a mocked function for testing and in a production environment, open builtin will be used. In the following paragraph, I present the "mocking" part:

def test_ComplexAlgorithm():
controller = Mocker()
objOpen = controller.mock()
fMocked = controller.mock()

objOpen.open("test_file.txt", "w")
controller.result(fMocked)

fMocked.write("simple test")

fMocked.close()

controller.replay()

complex_algorithm(objOpen.open)

This is all you need. If the function had returned a result you could have easily asserted it. For more informations about mocking visit the site http://labix.org/mocker.

Python and static methods

At my new working place I had to learn Python so I discovered some features that are hard to understand for someone with Java background. One of these features is definition of static methods.

In Java, you can write something like:

public static void helloWorld() { System.out.println("Hello world"); }

In python lucrurile sunt mai complicate si depinde si de versiune folosita. De exemplu, daca folosim o versiune de python anterioara 2.4 atunci vom defini o metoda statica in felul urmator:

In Python, it's more difficult to define class/static methods. If we use a version earlier than 2.4 we will define a static method like this:

def helloWorld(cls):
print("Salutari")

helloWorld = classmethod(helloWorld)

If we use a version newer than 2.4, the implementation of static methods is simpler because we use a decorator:
@staticmethod
def helloWorld(cls):
print("Hello world")

In both cases, defining a static method in Python is harder than in Java.