Atheist, No seas crédulo ¡¡Prueba!!


Hay dos tipos de programadores: los creyentes y los ateos.

Los creyentes se _creen_ tan buenos que no necesitan pruebas. Tienen fe en su forma de escribir código.

Los ateos, necesitan _pruebas_ para poder creer.

Precisamente por esta razón nace Atheist .

Motivación

Ayer, en la reunión de Agile-CR , David Villa nos contó las maravillas de Atheist.

Personalmente, me impresionó bastante.

No es un sistema para hacer pruebas unitarias, aunque permite integrar pruebas unitarias en python, C/C++ o Java. Es un sistema para hacer pruebas de sistema.

Algunas de las ventajas que le vi:

  • Permite utilizar toda la potencia de python.
  • -Los informes que genera son simples pero muy visuales-. Permite obtener informes en terminal o generar informes complejos en HTML.
  • Tiene numerosas opciones y posibilidades.
  • Permite realizar operaciones realmente complejas, como probar una aplicación cliente-servidor.
  • Es ampliable mediante plugins.
  • Se integra con TestUnit.
  • Se integra con Jenkins/Hudson.
  • Es paquete Debian y Ubuntu.

Lo malo que tiene es la documentación, que está desactualizada :D

Veamos un par de ejemplos, de manera que podamos aprender un poco de Atheist.

Primeros pasos

Probando "ls"

Vamos a probar la orden "ls".

Para ello crearemos el archivo "ls.test", que será un archivo python:

t = Test('ls /')
t.post += StdOutContains('lost+found')

Y lo probamos:

$ atheist ls.test
[ OK ] TaskCase: ./ls.test
[  ALL OK!  ] - 0.33s - 1 tests - 1 tasks

(en la consola tiene colorines, pero prefiero dejarlo así que estar poniendo imágenes).

¿Qué ha ocurrido aquí?

Para empezar, hemos creado un test, que ejecutará la orden "ls /". Después hemos añadido una post-condición, que consiste en comprobar que la salida estándar contiene la cadena "lost+found".

¡En rojo!

Vamos a provocar un fallo. Para ello, vamos a tratar de listar /magmax, que es un directorio que, probablemente, no existirá:

t = Test('ls /')
t.post += StdOutContains('lost+found')

t2 = Test ('ls /magmax')

Ejecutando:

$ atheist ls.test
[FAIL] TaskCase: ./ls1.test
[ OK ] ├─ Test-1  -( 0: 0)  ls /
[FAIL] └─ Test-2  -( 2: 0)  ls /magmax
[    FAIL   ] - 0.56s - 1/2 tests

Como puede observarse, en este caso nos muestra más información: Entre paréntesis tenemos dos números. Estos números representan el resultado recibido por el programa seguido del esperado. Lo habitual en los programas es que devuelvan un 0 cuando funcionan, pero es probable que queramos comprobar que fallan con un código de error determinado.

Corregiré el ejemplo:

t = Test('ls /')
t.post += StdOutContains('lost+found')

t2 = Test ('ls /magmax', expected=2)

Con eso le digo que se espera un código de error 2, es decir, que espero que falle con ese error concreto:

$ atheist ls.test
[ OK ] TaskCase: ./ls1.test
[  ALL OK!  ] - 0.56s - 2 tests - 2 tasks

Y volvemos al verde.

Probando "touch"

Ahora vamos a probar la orden "touch":

fname = "/tmp/magmax"
t = Test("touch "  + fname )
t.gen += fname
t.pre += Not(FileExists(fname))
t.post += FileExists(fname)

Y probamos:

$ atheist touch.test
[ OK ] TaskCase: ./touch.test
[  ALL OK!  ] - 0.32s - 1 test - 1 task

En esta ocasión hemos guardado una cadena con la ruta de un archivo en una variable, y después la hemos utilizado durante el test. Después, avisamos a Atheist que se va a generar un archivo (así lo borrará después del test) y realizamos la precondición de que el archivo no existe. Finalmente, realizamos la comprobación de que el archivo sí existe.

Obteniendo más información

Aunque Atheist nos despliega la información de forma automática ante un fallo, es posible que queramos tener más información. Para ello podemos usar las opciones -v, -vv, -vvv y -vvvv, que van mostrando cada vez más información. Con el ejemplo de "ls":

$ atheist ls.test -vvvv
[DD] Log level is DEBUG
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/FileContainsRE.pyc'
[DD] PluginManager: ['FileContainsRE']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/__init__.pyc'
[DD] PluginManager: []
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/alias.pyc'
[DD] PluginManager: ['Alias']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/composite.pyc'
[DD] PluginManager: ['CompositeCondition', 'Or']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/cxxtest.pyc'
[DD] PluginManager: ['CxxTest']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/debianpkg.pyc'
[DD] PluginManager: ['DebPkgBuild', 'DebPkgInstall', 'DebPkgInstalled']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/doctest_legacy.pyc'
[DD] PluginManager: ['DocTest']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/imap.pyc'
[DD] PluginManager: ['IMAP']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/inotify-runner.pyc'
[DD] PluginManager: ['INotifyRunner', 'Plugin']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/jabber-reporter.pyc'
[DD] PluginManager: ['JabberReporter']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/manual-repeat-runner.pyc'
[DD] PluginManager: ['ManualRepeatRunner']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/net.pyc'
[DD] PluginManager: ['OpenPort']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/perf.pyc'
[DD] PluginManager: ['TimeLessThan']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/smtp.pyc'
[DD] PluginManager: ['SMTP']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/smtp-reporter.pyc'
[DD] PluginManager: ['SMTP_Reporter']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/step-runner.pyc'
[DD] PluginManager: ['StepRunner']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/task_management.pyc'
[DD] PluginManager: ['TaskFinished', 'TaskRunning', 'TaskTerminator']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/unittest_legacy.pyc'
[DD] PluginManager: ['UnitTestCase']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/until_fail_runner.pyc'
[DD] PluginManager: ['UntilFailRunner']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/webtesting.pyc'
[DD] PluginManager: ['WebTest']
[DD] PluginManager: Loading module '/usr/lib/pymodules/python2.6/atheist/plugins/xunit_reporter.pyc'
[DD] PluginManager: ['XUnit_Reporter']
[DD] Registered plugins: ['Alias', 'CompositeCondition', 'CxxTest', 'DebPkgBuild', 'DebPkgInstall', 'DebPkgInstalled', 'DocTest', 'FileContainsRE', 'IMAP', 'INotifyRunner', 'JabberReporter', 'ManualRepeatRunner', 'OpenPort', 'Or', 'Plugin', 'SMTP', 'SMTP_Reporter', 'StepRunner', 'TaskFinished', 'TaskRunning', 'TaskTerminator', 'TimeLessThan', 'UnitTestCase', 'UntilFailRunner', 'WebTest', 'XUnit_Reporter']
[DD] excluding ['.svn', '.hg', '_setup.test', '_teardown.test', '_SETUP.test', '_TEARDOWN.test']
[II] Using runner: <ThreadPoolRunner>
[DD] loading ./ls1.test
[II] Test case ./ls1.test ---------------------------------------------------------------------------------------------------------------------------------------------
[II] T1: Test('ls /')
[DD] T1: pre-run
[DD] T1: pre-run <[ -- ] FileContains 'None' (1) content:'['lost+found']'>
[II] T1: Pre : <[ OK ] Not (FileExists '/tmp/atheist-miguel/10196/T1.out'>
[DD] T1: starts (pid: 10199)
[DD] T1: [ OK ] finish with 0
[II] T1: Post: <[ OK ] FileExists '/tmp/atheist-miguel/10196/T1.out'>
[II] T1: Post: <[ OK ] FileContains '/tmp/atheist-miguel/10196/T1.out' (1) content:'['lost+found']'>
[II] T2: Test('ls /magmax')
[DD] T2: pre-run
[DD] T2: starts (pid: 10200)
[DD] T2: [ OK ] finish with 2
[DD] - removing file '/tmp/atheist-miguel/10196/T1.out'
[DD] ConsoleReporter: sending report to 'console'
[ OK ] TaskCase: ./ls1.test
[ OK ] ├─ Test-1  -( 0: 0)  ls /
[ OK ] │  └─+pre:  Not (FileExists '/tmp/atheist-miguel/10196/T1.out'
[ OK ] │  ├─+post: FileExists '/tmp/atheist-miguel/10196/T1.out'
[ OK ] │  └─ post: FileContains '/tmp/atheist-miguel/10196/T1.out' (1) content:'['lost+found']'
[ OK ] └─ Test-2  -( 2: 2)  ls /magmax
[  ALL OK!  ] - 0.57s - 2 tests - 2 tasks

[DD] - removing directory '/tmp/atheist-miguel/10196'

h2 . Desplegando siempre

También es posible que queramos despelgar los tests aunque éstos sean correctos. Para ello utilizaremos la orden "-i#", siendo # el número de niveles que desplegar:

$ atheist ls.test -i5
[ OK ] TaskCase: ./ls1.test
[ OK ] ├─ Test-1  -( 0: 0)  ls /
[ OK ] │  └─+pre:  Not (FileExists '/tmp/atheist-miguel/10206/T1.out'
[ OK ] │  ├─+post: FileExists '/tmp/atheist-miguel/10206/T1.out'
[ OK ] │  └─ post: FileContains '/tmp/atheist-miguel/10206/T1.out' (1) content:'['lost+found']'
[ OK ] └─ Test-2  -( 2: 2)  ls /magmax
[  ALL OK!  ] - 0.56s - 2 tests - 2 tasks

En este caso se puede apreciar cómo Atheist incluye ciertas pre y post condiciones automáticamente, como la comprobación de que el archivo temporal de la salida de las pruebas no existe.

Más información

Para obtener más información, está la página del proyecto.

También podéis consultar el manual on-line , aunque os advierto que puede estar bastante desactualizado. Quizá por eso sea preferible atacar directamente al código fuente , aunque sólo sean los ejemplos o las propias pruebas de Atheist , que no dejan de estar hechas en Atheist :D.

Finalmente, avisaros de que existe el paquete Debian.


Comentarios

Comments powered by Disqus