domingo, 3 de febrero de 2013

Executando as probas do repositorio ao facer commit [GIT][Python]

Os sistemas de control de versións son desas cousas que non botas en falla ata que as coñeces, sobretodo se están ben feitos. A miña primeira experiencia con estes foi usando Subversion e a impresión non foi en absoluto positiva, se querías gardar un grupo de cambios necesitabas conexión o servidor (polo tanto a internet, e polo tanto non era algo intantáneo), se querías ler a lista de cambios necesitabas conexión o servidor (o mesmo), se querías seguir a partires dun estado previo era unha auténtica odisea e se alguén actualizaba o código mentres ti o facías outra... e xa non quixen saber nada máis diso.

Avancemos un pouco, co auxe de GitHub e compaña acabei por probar GIT e todo cambiou, os cambios? ata que queiras mandálos o servidor gárdanse en local (é distribuído por natureza); a lista de cambios? o mesmo, está en local así que é instantáneo; se queres voltar a un estado previo so tes que facer `git checkout id-do-estado`, e se ben os cambios concurrentes poden requerir algún manexo manual ata o de agora non foi necesario, o propio sistema toma cargo deles.

Un día compría facer un tutorial en condicións destes sistemas, pero por agora quero amosarvos un truco que evitou que código defectuoso chegara ó servidor nun proxecto que tiven que desenvolver fai pouco.

Resulta que GIT ten unha funcionalidade que permite executar código en diversos puntos da execución das súas accións e modificar o curso a tomar pola aplicación, por exemplo, podemos escribir un script que se executará o facer commit, e no caso de voltar un valor distinto de 0 na súa saída deterá o proceso, impedindo que se realice, isto permite por exemplo evitar enviar código que non pasa as probas.


Pero deixemos de esbardallar e ímos a práctica, partirei deste repositorio como exemplo:

codigo.py (tomará o papel do código a probar)
#!/usr/bin/env python
# coding: utf-8

def dobra(x):
    """Dobra o valor de entrada."""
    return x * 2


def main():
    """Función principal."""
    try:
        v = int(raw_input('Introduce o valor a dobrar: '))
    except ValueError:
        print "Erro: a entrada debe ser un número en base 10"
    else:
        print dobra(v)


if __name__ == '__main__':
    main()

proba.py (conterá o código das probas)
#!/usr/bin/env python
# coding: utf-8

from codigo import dobra
import unittest

class TestSequence(unittest.TestCase):
    
    def test_dobra(self):
        """Proba a funcion "dobra"."""
        # Datos de proba
        datos = [(1, 2), (2, 4), (3, 6),
                 (4, 8), (5, 10)]

        for par in datos:
            self.assertEqual(dobra(par[0]), par[1])


if __name__ == '__main__':
    unittest.main()

.gitignore (para que non ande as voltas co bytecode)
*.pyc

E engadimos todo...
$ git init
Initialized empty Git repository in /home/kenkeiras/proba/.git/
$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
# .gitignore
# codigo.py
# proba.py
nothing added to commit but untracked files present (use "git add" to track)
$ git add codigo.py proba.py .gitignore 
$ git commit -m 'Código inicial'
[master (root-commit) 726d181] Código inicial
 3 files changed, 41 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 codigo.py
 create mode 100644 proba.py
$ 

Agora ímos facer que cada vez que gardemos un cambio execute a proba, no directorio de hooks (`.git/hooks/`) crearemos un arquivo, daremoslle permisos de execución e diremoslle o que ten que executar, se sae co código 0, terá vía libre, senon deberá deterse, neste caso a orde será executar as probas:

pre-commit (recordade facer `chmod +x .git/hooks/pre-commit`)
#!/usr/bin/env bash

python proba.py
exit $?

E xa está, todo o anterior ía por estas 3 liñas. Se facemos un cambio benigno e o gardamos...

def dobra(x):
    """Dobra o valor de entrada, agora só sumando."""
    return x + x

$ git commit -m 'Agora o dobrar só se suma'
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
[master 64d094d] Agora o dobrar só se suma
 1 files changed, 2 insertions(+), 2 deletions(-)
$ git status
# On branch master
nothing to commit (working directory clean)
$

E se o cambio resulta nun erro
def dobra(x):
    """Dobra o valor de entrada, agora só restando."""
    return x - x

$ git commit -m 'Agora dobrase restando, probado co número 0'
F
======================================================================
FAIL: test_dobra (__main__.TestSequence)
Proba a funcion "dobra".
----------------------------------------------------------------------
Traceback (most recent call last):
  File "proba.py", line 16, in test_dobra
    self.assertEqual(dobra(par[0]), par[1])
AssertionError: 0 != 2

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   codigo.py
#
$ 

Xa non permite o cambio e avisa de que a proba faia, por suposto isto non está limitado a python/unittest, o depender só do valor de saída é moi flexible, sen ir máis lonxe o `test` de Apache Maven funcionaría igual.

E iso é todo, espero que vos sexa útil.

No hay comentarios:

Publicar un comentario