Carlos Ble

Carlos Ble

I am a professional software developer, I solve problems.

I also teach and mentor developers to build better software.

Developing software since 2001.

Can I help you?

  • Do you need high quality tailor-made software?
  • Need training on TDD, clean code or refactoring?
  • Is it risky? do you need advise?
  • May I pair with you to write better code?

Events

Upcoming public training courses:

  1. [Online - en Español] 25 y 26 de Junio
    Test Doubles con JavaScript - Online
  2. [in English] July 7, 8 & 9
    TDD (open to the public) - Tenerife, Canary Islands
  3. [en Español] 14 y 15 Julio
    TDD (en abierto) - Gran Canaria, Canary Islands
  4. [in English] October 13, 14 & 15
    TDD (open to the public) - London, UK
  5. [en Español] 29, 30 y 31 de Octubre.
    TDD (en abierto) - Madrid, Spain

Conferences:

  1. I'll be at SocratesUK 2014
  2. I'll be at the London Test Gathering Workshops.

Archive for March, 2009



Pinsor 0.6 released!

I am glad to blog about Pinsor 0.6 release. Pinsor is an Inversion of Control container for Python created by Ryan Svihla. I've started collaborating with Ryan on this release and committed a few changes (not much). It's been nice to talk about design decisions and see that there is a new release after all. Pinsor is useful from now and looks promising for the future.

We've developed a decorator intended to be placed on top of the "soapmethod" decorator that allows you to catch any exception raised by the web method.

Usage:

  1.  
  2. @catchWsExceptions
  3. @soapmethod(String, _returns=Array(String))
  4. def srv_whatever(self, someParam):
  5. # just business logic, no exception handling required.
  6.  

Decorator code:

  1.  
  2. import inspect
  3. import traceback
  4.  
  5. def catchWsExceptions(func):
  6. def wrapped(*args, **kwargs):
  7. if func.__module__ <> "soaplib.service":
  8. raise ApplicationException("catchWsExceptions decorator can only be used before soapmethod decorator")
  9. try:
  10. result = func(*args, **kwargs)
  11. return result
  12. except Exception, e:
  13. tb= sys.exc_info()[2]
  14. tpl = traceback.extract_tb(tb)
  15. failedCall = str(tpl[-1][0])
  16. if re.search("soaplib", failedCall):
  17. Logger.getInstance().logException(e)
  18. raise # this is not an exception within the function but before calling the actual function
  19. if type(e) == type(TypeError):
  20. raise # API mistmach exception should not be filtered
  21. msg = ""
  22. if hasattr(e, "message"):
  23. msg = e.message
  24. Logger.getInstance().logException(e)
  25. return ['2', msg + str(e.args)]
  26. wrapped.__doc__ = func.__doc__
  27. wrapped.func_name = func.func_name
  28. wrapped._is_soap_method = True
  29. return wrapped
  30.  

Logger and ApplicationException belong in our code base but you can just replace them. You may also raise the caught exception rather than return an array with an error code. It is a design decision.

s8000236Ayer, 18 de Marzo de 2009, nos reunimos en el bulevar para tener la primera reunión de Agile Canarias.

Estuvimos un total de 7 personas y pudimos ser más porque hubo gente que llegó tarde y no nos encontró. Como Taco resultó estar cerrado nos rodamos al bar que había debajo.  Una pena.

En la foto, de izquierda a derecha: Esau Rodriguez, Rodolfo Yllada, Carlos Blé, René Martín, Raúl Sánchez, Ruyman Reyes y Ubay Oramas. Hay que decir que Rodolfo vino desde Guia de Isora, nada más y nada menos, hay que reconocer que se lo ha currado.

Estuvimos haciendo repaso a la lista de metodologías que conocíamos y a las que estabamos practicando. Casi todos estamos probando con cositas de SCRUM y algunos estamos liados con XP (parcialmente: TDD, PP y CI).  Se habló bastante de tests, sobre todo los de aceptación y se nombraron cosas como Selenium, Concordion, Fit y Cucumber. La verdad que de Lean, Kanban y Crystal ninguno sabíamos casi nada. La mayoría de los asistentes están trabajando con Django para la web y se estuvo comentando cómo hacer tests de aceptación con las propias herramientas que trae el framework. También hablamos de mocks para simular el comportamiento de objetos.

Nos planteamos si tenía sentido una asociación sin ánimo de lucro y pensamos que la respuesta la sabremos si la gente va demostrando interés en futuras reuniones. La idea es poder organizar talleres, cursos e incluso ir adquiriendo libros para un fondo común, ya que los libros típicamente hay que mandarlos a pedir a EEUU (amazon) y son caros. Realmente lo más interesante es compartir experiencias y ésto lo podemos hacer sin necesidad de asociación.

También acordamos que el nexo de unión entre Agile Spain y Agile Canarias para temas de las webs y de actividades sería Raúl Sánchez porque se ofreció a incorporarse a la lista de coordinación. Mucha suerte Raúl, ahora tienes que dejar el pabellón bien alto :-)

Como recomandación sería conveniente darse de alta el el grupo Foro-Agiles y en el grupo Agile-Spain. Mas info aqui: http://www.agile-spain.com

Al final se nos fueron volando 2 horas y nadie se aburrió. Dije que pondría por aquí los blogs de los asistentes pero están todos en el grupo de LinkedIn y como están los nombres podeis irlos a buscar ahí mismo.

We've developed this to be able to run trusted methods as batch operations that were saved into database to be performed later on. The code is easy to understand reading its tests:

Tests: Check out this file

Code:

  1.  
  2. import os
  3. import inspect
  4. import inspect
  5.  
  6. class PythonInvoker():
  7. def __init__(self, targetBaseClass=None):
  8. self.targetBaseClass = targetBaseClass
  9.  
  10. def evalArgumentsIfNotStrings(self, param):
  11. if len(param) > 0:
  12. param = param.strip()
  13. if param[0] == "\"" or param[0] == "'":
  14. return param
  15. else:
  16. try:
  17. return eval(param,{"__builtins__":None},{})
  18. except NameError:
  19. raise ApplicationException("Security exception: arguments can only be variables. This argument is -> " + param)
  20. else:
  21. return param
  22.  
  23. def secureDynamicInvoke(self, classInstance, methodName, arguments):
  24. if isinstance(classInstance, self.targetBaseClass):
  25. return self.dynamicInvoke(classInstance, methodName, arguments)
  26. else:
  27. raise ApplicationException(str(classInstance) + " does not extends " + str(self.targetBaseClass))
  28.  
  29. def dynamicInvoke(self, classInstance, methodName, arguments):
  30. if arguments == None:
  31. arguments = ""
  32. try:
  33. method = getattr(classInstance, methodName)
  34. except AttributeError:
  35. raise ApplicationException("Method does not belong in class!: " + str(classInstance) + "-" + str(methodName))
  36. retInfo = None
  37. try:
  38. if re.search(",", arguments):
  39. args = arguments.split(",")
  40. args = map(self.evalArgumentsIfNotStrings, args)
  41. retInfo= method(*args)
  42. else:
  43. retInfo= method()
  44. except TypeError, e:
  45. raise ApplicationException(str(classInstance) + "." + str(methodName) + " - " + e.message)
  46. return retInfo
  47.  

When to use mock() and proxy()

This is about Python Mocker features. In order to pass in a mock as a parameter to the SUT (subject under test) you might think of creating the mock using either, "mock()" method or "proxy(SomeClass)" method. They both yield mocks but they work differently.

  • mock() should be used for stubs and to mock out functions which are not contained in any class but in mofules.
  • proxy(SomeClass) should be used to mock out classes. Mocker checks that the expectation set on the mock matches an existent method in the class we're proxying. This adds value to the test as it enforces API matching, which is very valuable in Python.
    1.  
    2. def test_whatever():
    3. mockMyClass = self.mocker.proxy(MyClass)
    4. mockMyClass.someMethod()
    5. self.mocker.count(1)
    6. self.mocker.replay()
    7.  
    8. mySutInstance = MySut(mockMyClass)
    9. result = mySutInstance.methodUnderTest()
    10. self.assertTrue(result)
    11. self.mocker.verify()
    12.  

    In the code above, if "someMethod" wasn't existent in MyClass, that would raise an exception. Using mock(), the test would run with same outcome but no exception, so it misses part of the point.

Lista de TDD en castellano!

Se acaba de crear una nueva lista sobre Test Driven Development en castellano: http://groups.google.com/group/tddev-sp
Muchas gracias a Jose Ramón Diaz por la iniciativa. Esperemos que los recursos en castellano sigan aumentando.

Leyendo la lista de ALT.Net hoy me he llevado una grata sorpresa. Hay una empresa de Madrid que ha liberado un framework open source para .Net. Se llama Signum Framework.
La verdad que no lo he podido mirar pero la web está chula. Independientemente de que sea malo o bueno es una buena noticia que haya más gente en España produciendo software abierto.
Espero que les vaya bien y consigan crear comunidad. Yo nunca he conseguido crear comunidad alrededor de mi framework DesktopRails.

Run just a group of tests

Sometimes we (my team in our current project) have a single TestCase (class) holding tests for several SUTs (classes). That is not right as every class should have its own TestCase (It has to do with the fact that instantiating the tests to run them is a pain with unitttest), however it is our actual situation.
In order to run just the tests belonging to the current SUT, just pass them in as parameters: (tests.py)

  1.  
  2. class MyTests(mocker.MockerTestCase):
  3. def test_method1():
  4. pass
  5. def test_method2():
  6. pass
  7. def test_method3():
  8. pass
  9.  
  10. if __name__ == "__main__":
  11. if len(sys.argv) < 2:
  12. unittest.main(argv = unittest.sys.argv + ['--verbose'])
  13. else:
  14. suite = unittest.TestSuite()
  15. for i in range(1, len(sys.argv)):
  16. suite.addTest(MyTests(sys.argv[i]))
  17. runner = unittest.TextTestRunner()
  18. runner.run(suite)
  19.  

Invocation samples:

  1.  
  2. python tests.py # all the tests in the TestCase
  3. python tests.py test_method1 test_method2 # only these two
  4.  

This way we run all the tests all the time, instead of just running the test we've done writing.

Optional arguments are evil

When it comes to version and API changes, Python optional arguments are evil.
Say you define a function like this:

  1.  
  2. def someFunction(param1, param2, optParam=None)
  3. # code
  4.  

And you are calling it from module whatever.py like this:

  1.  
  2. retValue = someFunction(x, y, z)
  3.  

A few weeks later you need to change the API adding a new mandatory parameter:

  1.  
  2. def someFunction(param1, param2, param3, optParam=None)
  3. # code
  4.  

But you forget to update the call at whatever.py. It does not raise any exception but there is a big semantic error, "someFunction" is considering "z" as the "param3" where the caller is considering it the "optParam".
I try to avoid optional arguments as much as I can.

Same behavior in Python and C#...
Wrong code:

  1.  
  2. try:
  3. # some code
  4. except Exception, e:
  5. # log the exception or whatever
  6. raise e
  7.  

Right code:

  1.  
  2. try:
  3. # some code
  4. except Exception, e:
  5. # log the exception or whatever
  6. raise # JUST RAISE!!!
  7.  

Wrong code:

  1.  
  2. try
  3. {
  4. // whatever
  5. }
  6. catch (Exception e)
  7. {
  8. // log the exception or whatever
  9. throw e
  10. }
  11.  

Right:

  1.  
  2. try
  3. {
  4. // whatever
  5. }
  6. catch (Exception e)
  7. {
  8. // log the exception or whatever
  9. throw // JUST THROW!!!
  10. }
  11.  

Reason: Exception stacktrace is lost if you write "raise e" or "throw e" instead of just "raise" or "throw".