¿Qué es doctest en Python y para qué sirve?

Autor: | Última modificación: 15 de diciembre de 2022 | Tiempo de Lectura: 3 minutos
Temas en este post: , ,

Toda función en Python debería de tener su docstring: una cadena de varias líneas al principio de todo que explica qué hace dicha función. Es un principio básico de buen diseño y documentación.

Un ejemplo sería el siguiente:

def factorial(n):
	“””
Devuelve el factorial de n, que ha de ser un entero positivo.
	La función lanza excepciones en los siguientes casos:
a) Es un valor de coma flotante
b) Es negativo
c) Es demasiado grande y provocará un overflow
	“””
import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result

La docstring es una especie de comentario que va asociado al código y se puede consultar usando el atributo __doc__ del objeto resultante. Es una especie de comentario mejorado.

Ejemplos de uso en el comentario

Una buena forma de mejorar tus comentarios es incluir un ejemplo de uso de la función (o clase, o lo que sea) en él. 

En el caso de la función factorial de arriba, un par de ejemplos serían:

>>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]

>>> factorial(30)
    265252859812191058636308480000000

Esto está copiado directamente del repl de Python.

Podríamos mejorar la docstring de la función factorial si incluyéramos un par de ejemplos, como los anteriores:

def factorial(n):
	“””
Devuelve el factorial de n, que ha de ser un entero positivo.
	La función lanza excepciones en los siguientes casos:
a) Es un valor de coma flotante
b) Es negativo
c) Es demasiado grande y provocará un overflow

>>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]

>>> factorial(30)
    265252859812191058636308480000000
	“””
import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
    result *= factor
        factor += 1
    return result

Comentarios desactualizados o incorrectos

¿Hay algo peor que una función sin un comentario? Sí, una función con comentarios erróneos o desactualizados.

¡Ahí es donde entra doctest!

Doctest

Doctest es un módulo de Python que se encarga de revisar todas las docstrings en busca de aquello que parezca código Python (como el de arriba) y comprueba que es correcto. Es decir, es algo así como TDD para tus comentarios y se trata de una herramienta esencial para crear código de calidad, especialmente cuando vaya a ser usado por muchos otros programadores (como un framework).

Para usarlo, lo único que tienes que hacer es, en aquellos ficheros donde tengas código interactivo de ejemplo en las docstrings, añadir la siguiente línea al final del todo:

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Al llamar ese módulo desde la línea de comandos, estarás comprobando que todo el código de ejemplo funciona correctamente.

python factorial.py

Si hay algún error, te los sacará en pantalla, como si fuese un test unitario de TDD. Se trata de un excelente extra para los equipos que ya están utilizando TDD y que quieren ampliar la cobertura de su código, así como asegurar que la documentación del mismo sigue correcta y actualizada.

El ejemplo completo del fichero factorial.py con doctest quedaría así:

def factorial(n):
	“””
Devuelve el factorial de n, que ha de ser un entero positivo.
	La función lanza excepciones en los siguientes casos:
a) Es un valor de coma flotante
b) Es negativo
c) Es demasiado grande y provocará un overflow

>>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]

>>> factorial(30)
    265252859812191058636308480000000
	“””
import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()