Download - TDD Con Python
Test-Driven Developmentcon Python
y un ejemplo: la librería algoritmia
Andrés MarzalDepartamento de Lenguajes y Sistemas Informáticos
Universitat Jaume [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, you’ll 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. I’m 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, there’s 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
• ¿Qué es TDD?
• ¿Con qué herramientas hacemos TDD con Python?
• Una demo
• unitest
• mockito
• coverage
• Algunas conclusiones
• Algoritmia
Guión
viernes 9 de abril de 2010
¿Qué es TDD?
viernes 9 de abril de 2010
Desarrollo Tradicional
viernes 9 de abril de 2010
Desarrollo TradicionalUnified
ModelingLanguage(UML)
RationalUnifiedProcess(RUP)
CapabilityMaturityModel(CMM)Big Design
Up-Front(BDUF)
Desarrolloen Cascada
COCOMO
viernes 9 de abril de 2010
Desarrollo Tradicional
viernes 9 de abril de 2010
2001viernes 9 de abril de 2010
Mike Beedle
Arie van Bennekum
Alistair Cockburn
Ward Cunningham
Martin FowlerJim Highsmith
Andy Hunt
Ron Jeffries
Jon Kern
Brian Marick
Robert C. MartinUncle Bob
Ken Schwaber
Jeff Sutherland
Dave Thomas
viernes 9 de abril de 2010
XP Programming
Scrum
Kanbanviernes 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
• Programación por parejas
• Historias de usuario
• La metáfora del sistema
• Clientes in situ
• Unidades de prueba
• Desarrollo dirigido por pruebas
• Refactorización
• Diseño simple
• Iteraciones cortas
• Propiedad colectiva del código
• Reflexión continua
• Integración continua
viernes 9 de abril de 2010
¿Qué es una prueba unitaria?
• Una unidad de prueba (unit test) es un trozo de código (típicamente un método) que invoca a otro trozo de código y comprueba después la corrección de algunas asunciones.
• Si las asunciones no eran válidas, se dice que la unidad de prueba ha fallado.
• Una unidad (unit) es un método o función.
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 aceptación: el programa supera una demanda del cliente
• Prueba de integración
• Prueba de regresión
• Prueba de prestaciones
• Prueba de carga
• Prueba de estrés
viernes 9 de abril de 2010
Buenos “unit test”
• Automatizable y repetible
• Fácil de implementar
• Debe permanecer, una vez se ha escrito
• Cualquiera debe poder ejecutarlo
• Ejecutarlo debe ser sencillo
• Debe ser rápido
viernes 9 de abril de 2010
Frameworks
• Los frameworks de unit testing permiten:
• Simplificar el diseño de pruebas unitarias
• Facilitar un entorno para la ejecución 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 39
!"#$%&'()*' 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")] public void IsValidFileName_ValidFile_ReturnsTrue() { /// ... }
Running this test in the NUnit GUI will produce a result like that shown in figure 2.6.
What happens when you want to have tests running not by a namespace but by some other type of grouping? That’s where test cate-gories come in.
()+), -&.."/#'.&0.'12.%"&0
You can set up your tests to run under specific test categories, such as slow tests and fast tests. You do this by using NUnit’s [Category] attri-bute:
[Test][Category("Fast Tests")] public void IsValidFileName_ValidFile_ReturnsTrue() { /// ... }
viernes 9 de abril de 2010
¿Con qué herramientas hacemos TDD con
Python?
viernes 9 de abril de 2010
Python
• Versión 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
• Versión 3.5.2, Galileo
• http://eclipse.org
viernes 9 de abril de 2010
• Versión 1.5.6
• http://pydev.org
• Instalación: 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 números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
Creación de un proyecto Pydev
• File:: New:: Pydev project
viernes 9 de abril de 2010
viernes 9 de abril de 2010
Asociar una gramática Python e intérprete
• Menu contextual del proyecto
• Properties
• PyDev - Interpreter/Grammar
• Configurar un intérprete
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 números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
Pruebas de aceptación1. Dada una lista con 4 filas de números, 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 números 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 región 2x2
viernes 9 de abril de 2010
• Dentro creamos un package para las pruebas (menú contextual en el proyecto: New :: Pydev package) y le llamamos test
• En test creamos un módulo Python de tipo Unittest: test_SudokuValidator
• Y creamos el primer ¿método de test?
Manos a la obra
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
class TestSudokuValidator(TestCase): def test_sudoku_isNotAList_isRejected(self): validator = SudokuValidator() sudoku = 0 self.assertFalse(validator.check(sudoku), "Acepta una no lista")
TestCase: Clase cuyos métodos son
tests
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")
TestCase: Clase cuyos métodos son
tests
Método de test: empieza por test_
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")
TestCase: Clase cuyos métodos son
tests
Método de test: empieza por test_
SUT
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")
TestCase: Clase cuyos métodos son
tests
Método de test: empieza por test_
SUT
Aserto
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")
TestCase: Clase cuyos métodos son
tests
Método de test: empieza por test_
SUT
Aserto Mensaje de fallo
viernes 9 de abril de 2010
¿Pasa la prueba?
• Evidentemente, no puede pasarlo. El SUT no existe aún.
• Pero veamos cómo 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'] ... doneImporting 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 módulo: validator
• En ese módulo definimos la clase SudokuValidator con el método 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 TestCasefrom 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'] ... doneImporting 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 aceptación1. Dada una lista con 4 filas de números, 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 números 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 región 2x2
viernes 9 de abril de 2010
from unittest import TestCasefrom 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))
Los mensajes pueden ser superfluos si se
nombran bien los test
viernes 9 de abril de 2010
Finding files...['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... doneImporting test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... FAILtest_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 Trueviernes 9 de abril de 2010
Finding files...['/Users/amarzal/Documents/workspace/Sudoku/src/tests/test_SudokuValidator.py'] ... doneImporting test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
----------------------------------------------------------------------Ran 2 tests in 0.000s
OK
viernes 9 de abril de 2010
from unittest import TestCasefrom 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 TestCasefrom 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 aceptación1. Dada una lista con 4 filas de números, 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 números 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 región 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), (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))): 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
¡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'] ... doneImporting test modules ... done.
test_sudoku_isNotA4x4List_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_isNotAList_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_withNotIntegers_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_withOutOfRangeNumbers_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_withRepeatedNumbersInColumn_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_withRepeatedNumbersInRow_isRejected (test_SudokuValidator.TestSudokuValidator) ... oktest_sudoku_withRepeatedNumbersInSector_isRejected (test_SudokuValidator.TestSudokuValidator) ... ok
----------------------------------------------------------------------Ran 7 tests in 0.001s
OK
viernes 9 de abril de 2010
Un poco de “teoría”• Las pruebas deben ser
• Legibles
• Fácilmente ejecutables
• Rápidos
• Sin estado
• Las pruebas guían el diseño y evitan la sobreingeniería: YAGNI (You ain’t gonna need it)
• La refactorización es más segura: los cambios se prueban instantáneamente contra una batería de pruebas de aceptación
viernes 9 de abril de 2010
Historias de usuario
1. Dada una lista con 4 filas de números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
Pruebas de aceptación2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y
asteriscos en posición 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 líneas de 4 caracteres, debe rechazarla
2.3. Si tiene caracteres diferentes de ‘1’, ‘2’, ‘3’, ‘4’ o ‘*’ en las líneas, 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 unittestfrom 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 números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
Historias de usuario
3. Resolver automáticamente 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 unittestfrom 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 unittestfrom 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)
if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main()
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 números, saber si describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente 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 visualización apropiada para el Sudoku
• ¿Cómo?
viernes 9 de abril de 2010
Historias de usuario1. Dada una lista con 4 filas de números, saber si
describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Presentar gráficamente los Sudoku
5. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
import unittestfrom sudoku.presenter import SudokuPresenter
class TestSudokuPresenter(unittest.TestCase):
def setUp(self): self.presenter = SudokuPresenter()
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 +-+-+-+-+1 | | |3| | +-+-+-+-+2 |4| |1| | +-+-+-+-+3 | |1|4|3| +-+-+-+-+4 | | | | | +-+-+-+-+""" 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 usuario1. Dada una lista con 4 filas de números, saber si
describe un Sudoku de 4x4
2. Dada una cadena con 4 líneas de caracteres entre 1 y 4 y asteriscos en posición libre, obtener las lista que lo describe
3. Resolver automáticamente un Sudoku
4. Presentar gráficamente los Sudoku
5. Jugar partidas contra un jugador humano
viernes 9 de abril de 2010
¿Cómo hacemos pruebas con un humano?
• Se usan impostores (dobles de prueba) para simular el acceso a recursos:
• de producción y/o que interactúan con el entorno
• demasiado lentos para las pruebas
• tan complejos que podrían fallar y llamarnos a engaño 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/
• http://code.google.com/p/mockito/wiki/MockitoForPython
viernes 9 de abril de 2010
Un jugador “impostable”
• Vamos a diseñar una clase que modela al jugador
• El jugador introducirá coordenadas de la casilla que quiere modificar y el número 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.Difícil 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/Oviernes 9 de abril de 2010
import unittestfrom sudoku.player import SudokuPlayerfrom io import StringIOfrom 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'] unittest.main()
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 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()
verificación
Fake/Stub
Mock
Fake/Stub
viernes 9 de abril de 2010
Mocks aren’t 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 unittestfrom sudoku.game import SudokuGamefrom sudoku.player import SudokuPlayerfrom 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))
if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main()
viernes 9 de abril de 2010
from sudoku.solver import SudokuSolverfrom sudoku.presenter import SudokuPresenterfrom sudoku.validator import SudokuValidatorfrom sudoku.parser import SudokuParserfrom sudoku.player import SudokuPlayer
from random import choicefrom 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*"] 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
from sudoku.solver import SudokuSolverfrom sudoku.presenter import SudokuPresenterfrom sudoku.validator import SudokuValidatorfrom sudoku.parser import SudokuParserfrom sudoku.player import SudokuPlayer
from random import choicefrom 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*"] 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 líneas de nuestro código?
• Muy importante en lenguajes dinámicos
viernes 9 de abril de 2010
Herramientas
• coverage.py
• Versión 3.3.1
• Instalación: easy_install coverage
• http://nedbatchelder.com/code/coverage/
viernes 9 de abril de 2010
import osimport unittestimport coverageimport importlibfrom 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()
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()
viernes 9 de abril de 2010
<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-48sudoku/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, 44test/__init__ 1 1 100% test/test_SudokuGame 21 20 95% 33test/test_SudokuParser 19 18 94% 38test/test_SudokuPlayer 34 33 97% 49test/test_SudokuPresenter 11 10 90% 27test/test_SudokuSolver 30 29 96% 41test/test_SudokuValidator 28 27 96% 39---------------------------------------------------------TOTAL 300 287 95%
viernes 9 de abril de 2010
algoritmiaUna librería de estructuras de datos, algoritmos clásicos
y esquemas algorítmicos
MIT License
viernes 9 de abril de 2010
Mercurial
• Versión 1.5
• http://mercurial.selenic.com
viernes 9 de abril de 2010
HgEclipse
• Versión 1.5
• http://www.javaforge.com/project/HGE
• Instalación: 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
Libros
M A N N I N G
the art of
with Examples in .NET
ROY O SHER OVE
ptg
From the Library of Lee Bogdanoff
this print for content only—size & color not accurate spine = 0.7904" 416 page count
BOOKS FOR PROFESSIONALS BY PROFESSIONALS®
Foundations of Agile Python DevelopmentDear Reader,
Python is your chosen development language. You love its power, clarity, and interactivity. But what is the best way to build and maintain Python applications? How can you blend its unique strengths with the best of agile methods to reach still higher levels of productivity and quality? And, at a practical level, where are the tools to automate it all? In this book, I give answers to these questions, backed up by a wealth of down-to-earth examples and working code.
The short development cycles of agile projects require far more automation than traditional processes. There’s simply no way to have a two-week release cycle if development involves a day of integration, a week of QA, and three days for production deployment. You must automate to succeed. But all too often, the best-known tools are language specific. For this reason, this book gives you a complete set of open source tools to turbocharge your Python projects, and shows you how to integrate them into a smoothly functioning whole.
Eclipse and Pydev make an excellent Python IDE. Python ships with an xUnit-based unit-testing framework. Nose is great for running tests, supplemented by PyFit for functional testing. Setuptools is your build harness and packaging mechanism, with functionality similar to Maven in Java. Subversion provides a place to store your code, and Buildbot is an ideal continuous integration server. What makes this book different from others is that I show you how to tie all of these pieces together into one continuous tool chain that builds your software from start to finish—fast!
While the information I present is steeped in the language of agile develop-ment, the details are not limited to that approach. This book is as much about release engineering in Python as it is about agile development.
Jeff Younker
US $42.99
Shelve in Python
User level: Intermediate–Advanced
YounkerFoundations of Agile Python Developm
ent
THE EXPERT’S VOICE® IN OPEN SOURCE
Foundations of
Agile Python Development
CYAN MAGENTA
YELLOW BLACK PANTONE 123 C
Jeff Younker
Companion eBook Available
THE APRESS ROADMAP
Beginning Python:From Novice to Professional
Foundations of PythonNetwork Programming
Foundations of Agile Python Development
Dive into Python
www.apress.comSOURCE CODE ONLINE
Companion eBook
See last page for details
on $10 eBook version
ISBN-13: 978-1-59059-981-5ISBN-10: 1-59059-981-0
9 781590 599815
54299
Python, agile project methods, and a comprehensive open source tool chain!
viernes 9 de abril de 2010
Code and have fun!¡Gracias por vuestra atención!
viernes 9 de abril de 2010