
Soy un desarrollador principiante autodidacta que puede escribir aplicaciones simples. Pero tengo una confesión que hacer. Es imposible recordar cómo todo está interconectado en mi cabeza.
Esta situación empeora si vuelvo al código que escribí después de unos días. Resulta que este problema podría superarse siguiendo una metodología de desarrollo basado en pruebas (TDD).
¿Qué es TDD y por qué es importante?
En términos sencillos, TDD recomienda escribir pruebas que verifiquen la funcionalidad de su código antes de escribir el código real. Solo cuando esté satisfecho con sus pruebas y las características que prueba, comenzará a escribir el código real para satisfacer las condiciones impuestas por la prueba que les permitirían pasar.
Seguir este proceso asegura que planifica cuidadosamente el código que escribe para pasar estas pruebas. Esto también evita la posibilidad de que la redacción de pruebas se posponga para una fecha posterior, ya que es posible que no se consideren necesarias en comparación con funciones adicionales que podrían crearse durante ese tiempo.
Las pruebas también le dan confianza cuando comienza a refactorizar el código, ya que es más probable que detecte errores debido a la retroalimentación instantánea cuando se ejecutan las pruebas.

¿Cómo empezar?
Para comenzar a escribir pruebas en Python usaremos el unittest
módulo que viene con Python. Para ello creamos un nuevo archivo mytests.py
, que contendrá todas nuestras pruebas.
Comencemos con el habitual "hola mundo":
import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')
Observe que estamos importando una helloworld()
función desde un mycode
archivo. En el archivo mycode.py
, inicialmente solo incluiremos el código a continuación, que crea la función pero no devuelve nada en esta etapa:
def hello_world(): pass
La ejecución python mytests.py
generará la siguiente salida en la línea de comando:
F
====================================================================
FAIL: test_hello (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 7, in test_hello
self.assertEqual(hello_world(), 'hello world')
AssertionError: None != 'hello world'
--------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
Esto indica claramente que la prueba falló, lo que se esperaba. Afortunadamente, ya hemos escrito las pruebas, por lo que sabemos que siempre estará ahí para comprobar esta función, lo que nos da confianza para detectar posibles errores en el futuro.
Para asegurar que el código pase, cambiemos mycode.py
a lo siguiente:
def hello_world(): return 'hello world'
Ejecutando de python mytests.py
nuevo obtenemos el siguiente resultado en la línea de comando:
.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK
¡Felicidades! Acabas de escribir tu primera prueba. Pasemos ahora a un desafío un poco más difícil. Crearemos una función que nos permitiría crear una lista de comprensión numérica personalizada en Python.
Comencemos escribiendo una prueba para una función que crearía una lista de longitud específica.
En el archivo, mytests.py
este sería un método test_custom_num_list
:
import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world') def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10)
Esto probaría que la función create_num_list
devuelve una lista de longitud 10. Creemos la función create_num_list
en mycode.py
:
def hello_world(): return 'hello world'
def create_num_list(length): pass
La ejecución python mytests.py
generará la siguiente salida en la línea de comando:
E.
====================================================================
ERROR: test_custom_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 14, in test_custom_num_list
self.assertEqual(len(create_num_list(10)), 10)
TypeError: object of type 'NoneType' has no len()
--------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)
Esto es como se esperaba, así que vamos a seguir adelante y función de cambio create_num_list
en mytest.py
el fin de pasar la prueba:
def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
La ejecución python mytests.py
en la línea de comando demuestra que la segunda prueba también ha pasado:
..
--------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Let’s now create a custom function that would transform each value in the list like this: const * ( X ) ^ power
. First let’s write the test for this, using method test_custom_func_
that would take value 3 as X, take it to the power of 3, and multiply by a constant of 2, resulting in the value 54:
import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10) def test_custom_func_x(self): self.assertEqual(custom_func_x(3,2,3), 54)
Let’s create the function custom_func_x
in the file mycode.py
:
def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): pass
As expected, we get a fail:
F..
====================================================================
FAIL: test_custom_func_x (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 17, in test_custom_func_x
self.assertEqual(custom_func_x(3,2,3), 54)
AssertionError: None != 54
--------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1)
Updating function custom_func_x
to pass the test, we have the following:
def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power
Running the tests again we get a pass:
...
--------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
Finally, let’s create a new function that would incorporate custom_func_x
function into the list comprehension. As usual, let’s begin by writing the test. Note that just to be certain, we include two different cases:
import unittestfrom mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self): self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self): self.assertEqual(len(create_num_list(10)), 10)
def test_custom_func_x(self): self.assertEqual(custom_func_x(3,2,3), 54)
def test_custom_non_lin_num_list(self): self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16) self.assertEqual(custom_non_lin_num_list(5,3,2)[4], 48)
Now let’s create the function custom_non_lin_num_list
in mycode.py
:
def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power
def custom_non_lin_num_list(length, const, power): pass
As before, we get a fail:
.E..
====================================================================
ERROR: test_custom_non_lin_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 20, in test_custom_non_lin_num_list
self.assertEqual(custom_non_lin_num_list(5,2,3)[2], 16)
TypeError: 'NoneType' object has no attribute '__getitem__'
--------------------------------------------------------------------
Ran 4 tests in 0.000s
FAILED (errors=1)
In order to pass the test, let’s update the mycode.py
file to the following:
def hello_world(): return 'hello world'
def create_num_list(length): return [x for x in range(length)]
def custom_func_x(x, const, power): return const * (x) ** power
def custom_non_lin_num_list(length, const, power): return [custom_func_x(x, const, power) for x in range(length)]
Running the tests for the final time, we pass all of them!
....
--------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Congrats! This concludes this introduction to testing in Python. Make sure you check out the resources below for more information on testing in general.
The code is available here on GitHub.
Useful resources for further learning!
Web resources
Below are links to some of the libraries focusing on testing in Python
25.3. unittest - Unit testing framework - Python 2.7.14 documentation
The Python unit testing framework, sometimes referred to as "PyUnit," is a Python language version of JUnit, by Kent…docs.python.orgpytest: helps you write better programs - pytest documentation
The framework makes it easy to write small tests, yet scales to support complex functional testing for applications and…docs.pytest.orgWelcome to Hypothesis! - Hypothesis 3.45.2 documentation
It works by generating random data matching your specification and checking that your guarantee still holds in that…hypothesis.readthedocs.iounittest2 1.1.0 : Python Package Index
The new features in unittest backported to Python 2.4+.pypi.python.org
YouTube videos
If you prefer not to read, I recommend watching the following videos on YouTube.