TDD Con Python

Preview:

DESCRIPTION

tdd con python en pdf

Citation preview

Test-Driven Developmentcon Python

y un ejemplo: la librería algoritmia

Andrés MarzalDepartamento de Lenguajes y Sistemas Informáticos

Universitat Jaume Iamarzal@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, 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.&#3%"&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