Test-Driven
Development
con Python
y un ejemplo: la librera algoritmia
Andrs Marzal
Departamento de Lenguajes y Sistemas Informticos
Universitat Jaume I
[email protected]
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