You are on page 1of 113

Test-Driven

Development
con Python
y un ejemplo: la librera algoritmia
Andrs Marzal

Departamento de Lenguajes y Sistemas Informticos

Universitat Jaume I
amarzal@lsi.uji.es

viernes 9 de abril de 2010

viernes 9 de abril de 2010

viernes 9 de abril de 2010

If you look at how most programmers spend their time,


youll find that writing code is actually a small
fraction. Some time is spent figuring out what ought to be
going on, some time is spent designing, but most time is
spent debugging. Im sure every reader can remember
long hours of debugging, often long into the night.
Every programmer can tell a story of a bug that took a
whole day (or more) to find. Fixing the bug is usually
pretty quick, but finding it is a nightmare. And
then when you do fix a bug, theres always a chance that
another one will appear and that you might not even notice
it until much later. Then you spend ages finding that
bug.

Martin Fowler

viernes 9 de abril de 2010

Guin

viernes 9 de abril de 2010

Qu es TDD?
Con qu herramientas hacemos TDD con Python?
Una demo

unitest
mockito
coverage

Algunas conclusiones
Algoritmia

Qu es TDD?

viernes 9 de abril de 2010

Desarrollo Tradicional

viernes 9 de abril de 2010

Desarrollo Tradicional
Desarrollo
en Cascada

Rational
Unified
Process
(RUP)

viernes 9 de abril de 2010

COCOMO

Big Design
Up-Front
(BDUF)

Unified
Modeling
Language
(UML)
Capability
Maturity
Model
(CMM)

Desarrollo Tradicional

viernes 9 de abril de 2010

2001
viernes 9 de abril de 2010

Andy Hunt

Ward Cunningham Dave Thomas

Robert C. Martin
Uncle Bob

Brian Marick
Arie van Bennekum

Jeff Sutherland

Ron Jeffries

Ken Schwaber
Martin Fowler
Jim Highsmith
Alistair Cockburn
Mike Beedle
Jon Kern

viernes 9 de abril de 2010

XP Programming
Scrum
Kanban
viernes 9 de abril de 2010

Manifesto for Agile


Software Development

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools
Working software over comprehensive documentation

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan
That is, while there is value in the items on the right, we
value the items on the left more.

viernes 9 de abril de 2010

Manifesto for Agile


Software Development
Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan
That is, while there is value in the items on the right, we
value the items on the left more.
http://agilemanifesto.org

viernes 9 de abril de 2010

Desarrollo gil

viernes 9 de abril de 2010

Programacin por parejas


Historias de usuario
La metfora del sistema
Clientes in situ
Unidades de prueba
Desarrollo dirigido por pruebas

Refactorizacin
Diseo simple
Iteraciones cortas
Propiedad colectiva del cdigo
Reflexin continua
Integracin continua

Qu es una prueba
unitaria?

Una unidad de prueba (unit test) es un


trozo de cdigo (tpicamente un mtodo)
que invoca a otro trozo de cdigo y
comprueba despus la correccin de
algunas asunciones.

Si las asunciones no eran vlidas, se dice que


la unidad de prueba ha fallado.

Una unidad (unit) es un mtodo o funcin.


viernes 9 de abril de 2010

El sistema a prueba
El sistema sometido a prueba (system under
test) recibe el nombre de SUT.

Hay quien llama CUT a la clase sometida a


prueba.

viernes 9 de abril de 2010

No confundir
Prueba de aceptacin: el programa supera
una demanda del cliente

Prueba de integracin
Prueba de regresin
Prueba de prestaciones
Prueba de carga
Prueba de estrs
viernes 9 de abril de 2010

Buenos unit test


Automatizable y repetible
Fcil de implementar
Debe permanecer, una vez se ha escrito
Cualquiera debe poder ejecutarlo
Ejecutarlo debe ser sencillo
Debe ser rpido
viernes 9 de abril de 2010

Frameworks
Los frameworks de unit testing permiten:
Simplificar el diseo de pruebas unitarias
Facilitar un entorno para la ejecucin de
las pruebas

Proporcionar informes de los resultados


viernes 9 de abril de 2010

Frameworks
xUnit:
JUnit: Java
NUnit: C#
PyUnit: Python
Test::Unit: Ruby
...
viernes 9 de abril de 2010

The various meanings


of TDD
1) Test Driven Development: the idea of writing your code in a test first
manner.You may already have an existing design in place.
2) Test Oriented Development: Having unit tests of integration tests in your
code and write them out either before or after our write the code.Your code
has lots of tests.You recognize the value of tests but you don't necessarily write
them before you write the code. Design probably exists before you write the
code
3) Test Driven Design(the eXtreme Programming way): The idea of using a
test-first approach as a fully fledged design technique, where tests are a bonus
but the idea is to drive full design from little to no design whatsoever.You
design as you go.
4) Test Driven Development and Design: using the test-first technique to
drive new code and changes, while also allowing it to change and evolve your
design as an added bonus.You may already have some design in place before
starting to code, but it could very well change because the tests point out
various smells.

http://weblogs.asp.net/rosherove/archive/2007/10/08/the-various-meanings-of-tdd.aspx
viernes 9 de abril de 2010

viernes 9 de abril de 2010

Desarrollo dirigido por


las pruebas (TDD)

viernes 9 de abril de 2010

More NUnit attributes

!"#$%&'()*' In NUnit, an ignored test is marked in yellow (the middle test), and the
reason for not running the test is listed under the Tests Not Run tab on the right.

It can look like this:


[Test]
[Ignore("there is a problem with this test")]
viernes 9 de abril de 2010

39

Con qu herramientas
hacemos TDD con
Python?

viernes 9 de abril de 2010

Python
Versin 3.1.2
http://www.python.org

viernes 9 de abril de 2010

unittest (PyUnit)

Viene de serie con Python 3.1

viernes 9 de abril de 2010

Eclipse
Versin 3.5.2, Galileo
http://eclipse.org

viernes 9 de abril de 2010

Versin 1.5.6
http://pydev.org
Instalacin: http://pydev.org/updates

viernes 9 de abril de 2010

Pydev

viernes 9 de abril de 2010

Una demo

viernes 9 de abril de 2010

Sudoku
Queremos un programa que permita jugar
a Sudokus

En aras de la brevedad, Sudokus de 4x4

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber
si describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Creacin de un
proyecto Pydev

File:: New:: Pydev project

viernes 9 de abril de 2010

viernes 9 de abril de 2010

Asociar una gramtica


Python e intrprete
Menu contextual del proyecto
Properties
PyDev - Interpreter/Grammar
Configurar un intrprete
viernes 9 de abril de 2010

Fijar perspectiva Pydev


Open Perspective :: Other... ::

viernes 9 de abril de 2010

Empezamos
Ye hemos creado el proyecto Sudoku
Contendr una carpeta src
Dentro creamos un package para las
pruebas (men contextual en el
proyecto: New :: Pydev package) y le
llamamos test

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber
si describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Pruebas de aceptacin
1. Dada una lista con 4 filas de nmeros, saber si describe un
Sudoku de 4x4
1.1. Rechaza una no lista
1.2. Rechaza lista que no es de 4x4
1.3. Rechaza lista que no contiene 4x4 enteros
1.4. Se suministra una lista de 4x4 con nmeros menores que 0 o
mayores que 4 y la rechaza
1.5. Rechaza lista con repetidos en fila
1.6. Rechaza lista con repetidos en columna
1.7. Rechaza lista con repetidos en regin 2x2

viernes 9 de abril de 2010

Manos a la obra
Dentro creamos un package para las

pruebas (men contextual en el proyecto:


New :: Pydev package) y le llamamos test

En test creamos un mdulo Python de tipo


Unittest: test_SudokuValidator

Y creamos el primer mtodo de test?


viernes 9 de abril de 2010

'''
Created on 06/04/2010
@author: amarzal
'''
import unittest

class Test(unittest.TestCase):

def testName(self):
pass

if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()

viernes 9 de abril de 2010

from unittest import TestCase


class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

viernes 9 de abril de 2010

from unittest import TestCase

TestCase: Clase
cuyos mtodos son
tests

class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

viernes 9 de abril de 2010

from unittest import TestCase

TestCase: Clase
cuyos mtodos son
tests

class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

Mtodo de test:
empieza por test_
viernes 9 de abril de 2010

from unittest import TestCase

TestCase: Clase
cuyos mtodos son
tests

class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
SUT
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

Mtodo de test:
empieza por test_
viernes 9 de abril de 2010

from unittest import TestCase

TestCase: Clase
cuyos mtodos son
tests

class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
SUT
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

Mtodo de test:
empieza por test_
viernes 9 de abril de 2010

Aserto

from unittest import TestCase

TestCase: Clase
cuyos mtodos son
tests

class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
SUT
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

Mtodo de test:
empieza por test_
viernes 9 de abril de 2010

Aserto

Mensaje de fallo

Pasa la prueba?
Evidentemente, no puede pasarlo. El SUT no
existe an.

Pero veamos cmo falla:


Men contextual en test_SudokuValidator.py,

Run As :: Python unit-test_SudokuValidator.py

viernes 9 de abril de 2010

Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ERROR
======================================================================
ERROR: test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator)
---------------------------------------------------------------------Traceback (most recent call last):
File "/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py", line 7,
in test_sudoku_isNotAList_isRejected
validator = SudokuValidator()
NameError: global name 'SudokuValidator' is not defined
---------------------------------------------------------------------Ran 1 test in 0.001s
FAILED (errors=1)

viernes 9 de abril de 2010

A por el SUT

En src creamos un package: sudoku


En el package creamos un mdulo: validator
En ese mdulo definimos la clase SudokuValidator con el
mtodo check
class SudokuValidator:
def check(self, sudoku):
if not isinstance(sudoku, list):
return False
return True

viernes 9 de abril de 2010

from unittest import TestCase


from sudoku.validator import SudokuValidator
class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku),
"Acepta una no lista")

viernes 9 de abril de 2010

Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
---------------------------------------------------------------------Ran 1 test in 0.000s
OK

viernes 9 de abril de 2010

Tests de aceptacin
1. Dada una lista con 4 filas de nmeros, saber si describe un
Sudoku de 4x4
1.1. Rechaza una no lista
1.2. Rechaza lista que no es de 4x4
1.3. Rechaza lista que no contiene 4x4 enteros
1.4. Se suministra una lista de 4x4 con nmeros menores que 0 o
mayores que 4 y la rechaza
1.5. Rechaza lista con repetidos en fila
1.6. Rechaza lista con repetidos en columna
1.7. Rechaza lista con repetidos en regin 2x2

viernes 9 de abril de 2010

Los mensajes pueden


ser superfluos si se
nombran bien los test

from unittest import TestCase


from sudoku.validator import SudokuValidator
class TestSudokuValidator(TestCase):

def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku))
def test_sudoku_isNotA4x4List_isRejected(self):
validator = SudokuValidator()
sudoku = []
self.assertFalse(validator.check(sudoku))

viernes 9 de abril de 2010

Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... FAIL
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
======================================================================
FAIL: test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator)
---------------------------------------------------------------------Traceback (most recent call last):
File "/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py", line 15,
in test_sudoku_isNotA4x4List_isRejected
self.assertFalse(validator.check(sudoku))
AssertionError: True is not False
---------------------------------------------------------------------Ran 2 tests in 0.001s
FAILED (failures=1)

viernes 9 de abril de 2010

class SudokuValidator:
def check(self, sudoku):
if not isinstance(sudoku, list):
return False
if len(sudoku) != 4:
return False
for row in sudoku:
if not isinstance(sudoku, list):
return False
if len(row) != 4:
return False
return True
viernes 9 de abril de 2010

Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
---------------------------------------------------------------------Ran 2 tests in 0.000s
OK

viernes 9 de abril de 2010

from unittest import TestCase


from sudoku.validator import SudokuValidator
class TestSudokuValidator(TestCase):
def test_sudoku_isNotAList_isRejected(self):
validator = SudokuValidator()
sudoku = 0
self.assertFalse(validator.check(sudoku))
def test_sudoku_isNotA4x4List_isRejected(self):
validator = SudokuValidator()
sudoku = []
self.assertFalse(validator.check(sudoku))

viernes 9 de abril de 2010

from unittest import TestCase


from sudoku.validator import SudokuValidator
class TestSudokuValidator(TestCase):
def setUp(self):
self.validator = SudokuValidator()
def test_sudoku_isNotAList_isRejected(self):
sudoku = 0
self.assertFalse(self.validator.check(sudoku))
def test_sudoku_isNotA4x4List_isRejected(self):
sudoku = []
self.assertFalse(self.validator.check(sudoku))

viernes 9 de abril de 2010

Tests de aceptacin
1. Dada una lista con 4 filas de nmeros, saber si describe un
Sudoku de 4x4
1.1. Rechaza una no lista
1.2. Rechaza lista que no es de 4x4
1.3. Rechaza lista que no contiene 4x4 enteros
1.4. Se suministra una lista de 4x4 con nmeros menores que 0 o
mayores que 4 y la rechaza
1.5. Rechaza lista con repetidos en fila
1.6. Rechaza lista con repetidos en columna
1.7. Rechaza lista con repetidos en regin 2x2

viernes 9 de abril de 2010

class SudokuValidator:
def check(self, sudoku):
if not isinstance(sudoku, list):
return False
if len(sudoku) != 4:
return False
for row in sudoku:
if not isinstance(sudoku, list):
return False
if len(row) != 4:
return False
for row in sudoku:
for number in row:
if not isinstance(number, int):
return False
for row in sudoku:
for number in row:
if not (0 <= number <= 4):
return False

for i in range(4):
numbers = set()
for j in range(4):
n = sudoku[i][j]
if n != 0 and n in numbers:
return False
numbers.add(n)
for j in range(4):
numbers = set()
for i in range(4):
n = sudoku[i][j]
if n != 0 and n in numbers:
return False
numbers.add(n)
for region in (((0,0), (0,1), (1,0),
((0,2), (0,3), (1,2),
((2,0), (2,1), (3,0),
((2,2), (2,3), (3,2),
numbers = set()
for (i, j) in region:
n = sudoku[i][j]
if n != 0 and n in numbers:
return False
numbers.add(n)
return True

viernes 9 de abril de 2010

(1,1)),
(1,3)),
(3,1)),
(3,3))):

Hora de refactorizar!
Pero ya no saltamos sin red:
podemos refactorizar con la tranquilidad de
que los tests nos vigilan

viernes 9 de abril de 2010

class SudokuValidator:
def __init__(self):
rows = tuple(tuple((i, j) for j in range(4)) for i in range(4))
columns = tuple(tuple((i, j) for i in range(4)) for j in range(4))
sectors = (((0,0), (0,1), (1,0), (1,1)),
((0,2), (0,3), (1,2), (1,3)),
((2,0), (2,1), (3,0), (3,1)),
((2,2), (2,3), (3,2), (3,3)))
self.regions = rows + columns + sectors
def check(self, sudoku):
if not isinstance(sudoku, list) or len(sudoku) != 4:
return False
for row in sudoku:
if not isinstance(sudoku, list) or len(row) != 4:
return False
for number in row:
if not (isinstance(number, int) and 0 <= number <= 4):
return False
for region in self.regions:
numbers = set()
for (i, j) in region:
n = sudoku[i][j]
if n != 0 and n in numbers:
return False
numbers.add(n)
return True
viernes 9 de abril de 2010

Finding files...
['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... done
Importing test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_withNotIntegers_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_withOutOfRangeNumbers_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_withRepeatedNumbersInColumn_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_withRepeatedNumbersInRow_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
test_sudoku_withRepeatedNumbersInSector_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
---------------------------------------------------------------------Ran 7 tests in 0.001s
OK

viernes 9 de abril de 2010

Un poco de teora

Las pruebas deben ser

Legibles
Fcilmente ejecutables
Rpidos
Sin estado

Las pruebas guan el diseo y evitan la sobreingeniera:YAGNI (You aint


gonna need it)

La refactorizacin es ms segura: los cambios se prueban


instantneamente contra una batera de pruebas de aceptacin

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber
si describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Pruebas de aceptacin
2. Dada una cadena con 4 lneas de caracteres entre 1 y 4 y
asteriscos en posicin libre, obtener las lista que lo describe
2.1. Si se pasa un dato que no es una cadena, debe
rechazarla
2.2. Si la cadena no tiene 4 lneas de 4 caracteres, debe
rechazarla
2.3. Si tiene caracteres diferentes de 1, 2, 3, 4 o * en las
lneas, debe rechazarla
2.4. Si se le pasa una cadena correcta, debe proporcionar la
lista de lista correspondiente

viernes 9 de abril de 2010

import unittest
from sudoku.parser import SudokuParser
class TestSudokuParser(unittest.TestCase):
def setUp(self):
self.parser = SudokuParser()
def test_parses_withNonString_returnsParseException(self):
unproper_sudoku = 0
self.assertRaises(TypeError, self.parser.parse, unproper_sudoku)
def test_parses_unproperSizeString_returnsParseException(self):
unproper_sudoku="""****
****
****
***"""
self.assertRaises(ValueError, self.parser.parse, unproper_sudoku)
def test_parses_invalidChars_returnsParseException(self):
unproper_sudoku="""****
****
****
***="""
self.assertRaises(ValueError, self.parser.parse, unproper_sudoku)
def test_parses_validSudoku_returnsSudoku(self):
sudoku="""1***
*2**
**3*
***4"""
self.assertEquals(self.parser.parse(sudoku),
[[1,0,0,0], [0,2,0,0], [0,0,3,0], [0,0,0,4]])

if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber
si describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Historias de usuario
3. Resolver automticamente un Sudoku
3.1. Dado un sudoku completo, devolverlo
tal cual
3.2. Dado un sudoku incompleto, devolverlo
resuelto

viernes 9 de abril de 2010

import unittest
from sudoku.solver import SudokuSolver
class TestSudokuSolver(unittest.TestCase):
def setUp(self):
self.solver = SudokuSolver()
self.sudoku = [[0,0,4,0],[1,0,0,0], [0,0,0,3], [0,1,0,0]]
self.solution = [[[2, 3, 4, 1], [1, 4, 3, 2], [4, 2, 1, 3], [3, 1, 2, 4]]]
def test_solver_withCompleteSudoku_returnSameSudoku(self):
self.assertEquals(list(self.solver.solve(self.solution[0])), self.solution)
def test_solver_withValidSudoku_returnSolution(self):
self.assertEquals(list(self.solver.solve(self.sudoku)), self.solution)

if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()

viernes 9 de abril de 2010

from copy import deepcopy


class SudokuSolver:
def __init__(self):
self.rows = tuple(tuple((i, j) for j in range(4)) for i in range(4))
self.columns = tuple(tuple((i, j) for i in range(4)) for j in range(4))
sectors = (((0,0), (0,1), (1,0), (1,1)),
((0,2), (0,3), (1,2), (1,3)),
((2,0), (2,1), (3,0), (3,1)),
((2,2), (2,3), (3,2), (3,3)))
self.sector = {}
for sector in sectors:
for (i, j) in sector:
self.sector[i, j] = sector
self.regions = self.rows + self.columns + sectors
def solve(self, sudoku):
return self._backtrack(deepcopy(sudoku), 0, 0)
def _backtrack(self, sudoku, i, j):
for n in self._options(sudoku, i, j):
old_value, sudoku[i][j] = sudoku[i][j], n
sudoku[i][j] = n
if self._has_next(i, j):
for solution in self._backtrack(sudoku, *self._next(i, j)):
yield solution
else:
yield deepcopy(sudoku)
sudoku[i][j] = old_value
viernes 9 de abril de 2010

import unittest
from sudoku.solver import SudokuSolver
class TestSudokuSolver(unittest.TestCase):
def setUp(self):
self.solver = SudokuSolver()
self.sudoku = [[0,0,4,0],[1,0,0,0], [0,0,0,3], [0,1,0,0]]
self.solution = [[[2, 3, 4, 1], [1, 4, 3, 2], [4, 2, 1, 3], [3, 1, 2, 4]]]
def test_solver_withCompleteSudoku_returnSameSudoku(self):
self.assertEquals(list(self.solver.solve(self.solution[0])), self.solution)
def test_solver_withValidSudoku_returnSolution(self):
self.assertEquals(list(self.solver.solve(self.sudoku)), self.solution)
def test_options_ofFreeCell_areEnumerated(self):
options = set(self.solver._options(self.sudoku, 0, 0))
self.assertEquals(options, {2,3})
def test_options_ofCellWithNumber_isTheNumber(self):
options = set(self.solver._options(self.sudoku, 0, 2))
self.assertEquals(options, {4})
def test_hasNext_beforeLast_returnTrue(self):
self.assertTrue(self.solver._has_next(3, 2))
def test_hasNext_atLast_returnTrue(self):
self.assertFalse(self.solver._has_next(3, 3))
def test_next_fromOrigin_iteratesAllCellIndices(self):
(x, y) = (0, 0)
for i in range(4):
for j in range(4):
self.assertEquals((x,y), (i,j))
if self.solver._has_next(x, y):
(x, y) = self.solver._next(x, y)

viernes 9 de abril de 2010

class SudokuSolver:
...
def _has_next(self, i, j):
if i < 3: return True
if j < 3: return True
return False
def _next(self, i, j):
if j < 3: return (i, j+1)
if i < 3: return (i+1, 0)
def _options(self, sudoku, i, j):
if sudoku[i][j] != 0:
yield sudoku[i][j]
else:
used = set(sudoku[x][y] for (x, y) in self.rows[i] + self.columns[j] \
+ self.sector[i, j])
for n in set(range(1, 5)) - used:
yield n

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber
si describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Nos faltas historias de


usuario
Ahora caemos en que hemos de preparar

una visualizacin apropiada para el Sudoku

Cmo?

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber si
describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Presentar grficamente los Sudoku
5. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

import unittest
from sudoku.presenter import SudokuPresenter

class TestSudokuPresenter(unittest.TestCase):
def setUp(self):
self.presenter = SudokuPresenter()

1
2
3
4

def test_show_validSudoku_returnValidString(self):
sudoku = [[0, 0, 3, 0], [4, 0, 1, 0], [0, 1, 4, 3], [0, 0, 0, 0]]
presentation = """
1 2 3 4
+-+-+-+-+
| | |3| |
+-+-+-+-+
|4| |1| |
+-+-+-+-+
| |1|4|3|
+-+-+-+-+
| | | | |
+-+-+-+-+"""
self.assertEquals(self.presenter.show(sudoku), presentation)

if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()

viernes 9 de abril de 2010

class SudokuPresenter:
def show(self, sudoku):
s = []
s.append("
1 2 3 4")
for (i, row) in enumerate(sudoku):
s.append(" +-+-+-+-+")
s.append("{} |".format(i+1))
for number in row:
r = " " if number == 0 else repr(number)
s[-1] += r + "|"
s.append(" +-+-+-+-+")
return '\n'.join(s)

viernes 9 de abril de 2010

Historias de usuario
1. Dada una lista con 4 filas de nmeros, saber si
describe un Sudoku de 4x4
2. Dada una cadena con 4 lneas de caracteres
entre 1 y 4 y asteriscos en posicin libre,
obtener las lista que lo describe
3. Resolver automticamente un Sudoku
4. Presentar grficamente los Sudoku
5. Jugar partidas contra un jugador humano

viernes 9 de abril de 2010

Cmo hacemos pruebas


con un humano?
Se usan impostores (dobles de prueba) para
simular el acceso a recursos:

de produccin y/o que interactan con el


entorno

demasiado lentos para las pruebas


tan complejos que podran fallar y

llamarnos a engao sobre el responsable


del fallo

viernes 9 de abril de 2010

Herramientas
Mockito for Python
Mockito es un framework para Java, con
port para Python 3.1

http://code.google.com/p/mockito/

viernes 9 de abril de 2010

http://code.google.com/p/mockito/wiki/MockitoForPython

Un jugador
impostable
Vamos a disear una clase que modela al
jugador

El jugador introducir coordenadas de la


casilla que quiere modificar y el nmero
que quiere poner en esa casilla (0 ser
borrar)

viernes 9 de abril de 2010

class SudokuPlayer():
def get_coordinates_and_number(self):
while True:
print("Play i j n:", end="")
line = input().strip()
words = line.split()
if len(words) == 3:
try:
i = int(words[0])
j = int(words[1])
n = int(words[2])
if 1 <= i <= 4 and 1 <= j <= 4 and 0 <= n <= 9:
return (i-1, j-1, n)
else:
raise ValueError()
except ValueError:
print("Invalid input")

Feo: imprime en pantalla, lee de teclado.


Difcil de impostar: no hay costuras (seams)
viernes 9 de abril de 2010

class SudokuPlayer():
def __init__(self,
prompt=lambda: print("Play i j n:", end=""),
notify_error = lambda: print("Invalid input"),
get_input=input):
self.prompt = prompt
self.notify_error = notify_error
self.get_input = get_input
def get_coordinates_and_number(self):
while True:
self.prompt()
line = self.get_input().strip()
words = line.split()
if len(words) == 3:
try:
i = int(words[0])
j = int(words[1])
n = int(words[2])
if 1 <= i <= 4 and 1 <= j <= 4 and 0 <= n <= 9:
return (i-1, j-1, n)
else:
raise ValueError()
except ValueError:
self.notify_error()

Podemos impostar la propia I/O


viernes 9 de abril de 2010

import unittest
from sudoku.player import SudokuPlayer
from io import StringIO
from mockito import *
class TestSudokuPlayer(unittest.TestCase):
def test_getCoordinatesAndNumber_readsGoodUserValues_returnsProperValues(self):
ioMock = Mock() #@UndefinedVariable
when(ioMock).get_input().thenReturn("1 1 1") #@UndefinedVariable
output = StringIO()
player = SudokuPlayer(prompt=lambda: None,
notify_error=lambda: None,
get_input=ioMock.get_input)
(i, j, n) = player.get_coordinates_and_number()
verify(ioMock, times=1).get_input() #@UndefinedVariable
self.assertEquals((i,j,n), (0,0,1))
output.close()
def test_getCoordinatesAndNumber_readsBadAndGoodUserValues_returnsProperValues(self):
ioMock = Mock() #@UndefinedVariable
when(ioMock).get_input().thenReturn("1 1 100").thenReturn("1 1 1") #@UndefinedVariable
output = StringIO()
player = SudokuPlayer(prompt=lambda: None,
notify_error=lambda: None,
get_input=ioMock.get_input)
(i, j, n) = player.get_coordinates_and_number()
verify(ioMock, times=2).get_input() #@UndefinedVariable
self.assertEquals((i,j,n), (0, 0, 1))
output.close()
def test_getCoordinatesAndNumber_readsBadAndGoodUserValues_errorIsNotified(self):
ioMock = Mock() #@UndefinedVariable
when(ioMock).notify_error().thenReturn("ERROR") #@UndefinedVariable
when(ioMock).get_input().thenReturn("1 1 100").thenReturn("1 1 1") #@UndefinedVariable
output = StringIO()
player = SudokuPlayer(prompt=lambda: None,
notify_error=ioMock.notify_error,
get_input=ioMock.get_input)
(i, j, n) = player.get_coordinates_and_number()
verify(ioMock, times=1).notify_error() #@UndefinedVariable
output.close()
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
viernes 9 de abril de 2010

class TestSudokuPlayer(unittest.TestCase):
def test_getCoordinatesAndNumber_readsGoodUserValues_returnsProperValues(self):
ioMock = Mock() #@UndefinedVariable
when(ioMock).get_input().thenReturn("1 1 1") #@UndefinedVariable

Fake/Stub

output = StringIO()
player = SudokuPlayer(prompt=lambda: None,
notify_error=lambda: None,
get_input=ioMock.get_input)
(i, j, n) = player.get_coordinates_and_number()
verify(ioMock, times=1).get_input() #@UndefinedVariable
self.assertEquals((i,j,n), (0,0,1))
output.close()

Fake/Stub

Mock

verificacin

viernes 9 de abril de 2010

Mocks arent Stubs


(Martin Fowler)
Dummy objects are passed around but never actually used. Usually they are
just used to fill parameter lists.
Fake objects actually have working implementations, but usually take some
shortcut which makes them not suitable for production (an in memory database
is a good example).
Stubs provide canned answers to calls made during the test, usually not
responding at all to anything outside what's programmed in for the test. Stubs
may also record information about calls, such as an email gateway stub that
remembers the messages it 'sent', or maybe only how many messages it 'sent'.
Mocks are what we are talking about here: objects pre-programmed with
expectations which form a specification of the calls they are expected to
receive.

viernes 9 de abril de 2010

import unittest
from sudoku.game import SudokuGame
from sudoku.player import SudokuPlayer
from mockito import *
class TestSudokuGame(unittest.TestCase):
def test_game_withInvalidSudokuString_raisesException(self):
game = SudokuGame(sudoku_chooser=lambda x: "")
self.assertRaises(ValueError, game.start, None)
def test_game_withIncompleteSudoku_raisesException(self):
game = SudokuGame(sudoku_chooser=lambda x: "234\n341*\n214*\n*321")
self.assertRaises(ValueError, game.start, None)
def test_game_withImpossibleSudoku_raisesException(self):
game = SudokuGame(sudoku_chooser=lambda x: "2234\n341*\n214*\n*321")
self.assertRaises(ValueError, game.start, None)
def test_game_withValidSudoku_plasyOK(self):
player = Mock() #@UndefinedVariable
when(player).get_coordinates_and_number().thenReturn((0,0,1)) \
.thenReturn((1,3,2)) \
.thenReturn((2,3,3)) \
.thenReturn((3,0,4))
game = SudokuGame(sudoku_chooser=lambda x: "*234\n341*\n214*\n*321")
self.assertTrue(game.start(player))

viernes 9 de abril de 2010

from
from
from
from
from

sudoku.solver import SudokuSolver


sudoku.presenter import SudokuPresenter
sudoku.validator import SudokuValidator
sudoku.parser import SudokuParser
sudoku.player import SudokuPlayer

from random import choice


from copy import deepcopy
class SudokuGame:
def __init__(self, sudoku_chooser=lambda sudokus: choice(sudokus)):
self.parser = SudokuParser()
self.validator = SudokuValidator()
self.solver = SudokuSolver()
self.presenter = SudokuPresenter()
self.sudoku_chooser = sudoku_chooser
self.sudokus = ["2143\n4**1\n1**4\n3412",
"*234\n341*\n214*\n*321",
"*23*\n1**4\n2**3\n*14*",
"4**2\n*31*\n*42*\n3**1",
"4*1*\n1*2*\n*4*1\n*1*2",
"12**\n**21\n24**\n**42",
"*13*\n****\n****\n3421",
"*3*1\n**2*\n**3*\n*4*2",
"****\n12**\n3***\n***1",
"**3*\n****\n*2**\n*14*"]

viernes 9 de abril de 2010

def start(self, player):


sudoku_string = self.sudoku_chooser(self.sudokus)
sudoku = self.parser.parse(sudoku_string)

"**3*\n****\n*2**\n*14*"]
def start(self, player):
sudoku_string = self.sudoku_chooser(self.sudokus)
sudoku = self.parser.parse(sudoku_string)
if not self.validator.check(sudoku):
raise ValueError("Invalid Sudoku\n" + self.presenter.show(sudoku))
original_sudoku = deepcopy(sudoku)
print(self.presenter.show(sudoku))
while not self.validator.complete(sudoku):
(i, j, n) = player.get_coordinates_and_number()
if sudoku[i][j] == 0:
sudoku[i][j] = n
if not self.validator.check(sudoku):
sudoku[i][j] = 0
elif n == 0 and original_sudoku[i][j] == 0:
sudoku[i][j] = 0
print(self.presenter.show(sudoku))
return True
if __name__ == "__main__":
game = SudokuGame()
game.start(SudokuPlayer())

viernes 9 de abril de 2010

Pruebas de cobertura
Hemos puesto a prueba todas y cada una
de las lneas de nuestro cdigo?

Muy importante en lenguajes dinmicos

viernes 9 de abril de 2010

Herramientas
coverage.py
Versin 3.3.1
Instalacin: easy_install coverage
http://nedbatchelder.com/code/coverage/
viernes 9 de abril de 2010

import os
import unittest
import coverage
import importlib
from unittest import TestResult
def find_test_paths(startDir="test"):
result = []
directories = [startDir]
while len(directories)>0:
directory = directories.pop()
for name in os.listdir(directory):
fullpath = os.path.join(directory,name)
if os.path.isfile(fullpath) and \
name.startswith("test"):
result.append(fullpath)
elif os.path.isdir(fullpath):
directories.append(fullpath)
return result
test_paths = find_test_paths()

viernes 9 de abril de 2010

cov = coverage.coverage()
cov.start()
loaded = set()
suite = unittest.TestSuite()
for module in test_paths:
mod = importlib.import_module(''.join(
module.split(".")[:-1]).replace("/", "."))
exec("import {}".format(mod.__name__))
for c in dir(mod):
if c.startswith("Test"):
fullname = mod.__name__ + "." + c
testclass = eval(fullname)
if testclass not in loaded:
loaded.add(testclass)
suite.addTest(unittest.TestLoader()\
.loadTestsFromTestCase(testclass))
result = TestResult()
suite.run(result)
print(result)
cov.stop()
cov.report()

<unittest.TestResult run=52 errors=0 failures=0>


Name
Stmts
Exec Cover
Missing
--------------------------------------------------------sudoku/__init__
1
1
100%
sudoku/game
35
30
85%
40-42,
47-48
sudoku/parser
16
16
100%
sudoku/player
20
20
100%
sudoku/presenter
12
12
100%
sudoku/solver
36
36
100%
sudoku/validator
36
34
94%
17, 44
test/__init__
1
1
100%
test/test_SudokuGame
21
20
95%
33
test/test_SudokuParser
19
18
94%
38
test/test_SudokuPlayer
34
33
97%
49
test/test_SudokuPresenter
11
10
90%
27
test/test_SudokuSolver
30
29
96%
41
test/test_SudokuValidator
28
27
96%
39
--------------------------------------------------------TOTAL
300
287
95%

viernes 9 de abril de 2010

algoritmia
Una librera de estructuras de datos, algoritmos clsicos
y esquemas algortmicos
MIT License

viernes 9 de abril de 2010

Mercurial

Versin 1.5
http://mercurial.selenic.com

viernes 9 de abril de 2010

HgEclipse
Versin 1.5
http://www.javaforge.com/project/HGE
Instalacin: http://hge.javaforge.com/
hgeclipse

viernes 9 de abril de 2010

viernes 9 de abril de 2010

CodePlex
Repositorio Mercurial con el proyecto
algoritmia

http://algoritmica.codeplex.com

viernes 9 de abril de 2010

Clonar algoritmia

viernes 9 de abril de 2010

viernes 9 de abril de 2010

Importar algoritmia

viernes 9 de abril de 2010

viernes 9 de abril de 2010

with Examples in .NET

Libros
From the Library of Lee Bogdanoff

R OY O SHER OVE

MANNING

THE EXPERTS VOICE IN OPEN SOURCE


Companion
eBook Available

Foundations of

Agile Python Development


Younker

the art of

Foundations of

Agile Python
Development
Python, agile project methods, and a
comprehensive open source tool chain!

Jeff Younker

spine = 0.7904" 416 page count

viernes 9 de abril de 2010

Code and have fun!


Gracias por vuestra atencin!

viernes 9 de abril de 2010