1666 lines
47 KiB
Markdown
1666 lines
47 KiB
Markdown
**Ir a:**
|
|
[*Repositorio*](https://gitea.kickto.net/devfzn/Apuntes_Python),
|
|
[*Modulo 2*](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_2#modulo-2-python-basico),
|
|
[*Modulo 4*](https://gitea.kickto.net/devfzn/Apuntes_Python/src/branch/master/01_curso/Modulo_4#modulo-4-python-basico)
|
|
|
|
## Modulo 3 - Python basico
|
|
# Programación Orientada a Objetos
|
|
|
|
#### [Indice](#modulo-3-python-basico)
|
|
- [Intro POO](#intro-poo)
|
|
- [Polimorfismo](#polimorfismo)
|
|
- [Clases](#clases)
|
|
- [Herencia](#herencia)
|
|
- [Variables privadas](#variables-privadas)
|
|
- [Ambitos de variables](#ambitos-y-espacios-de-variables)
|
|
- [Buenas practicas](#buenas-practicas-poo)
|
|
- [Isomorfismo](#isomorfismo)
|
|
- [Abstraccion](#abstraccion)
|
|
- [Codigo repetido](#eliminar-codigo-repetido)
|
|
- [Actividad - cantidad de dinero](#cantidad-de-dinero)
|
|
- [Diseño con objetos](#introduccion-al-diseño-con-objetos)
|
|
- [Reglas de diseño](#reglas-de-diseño)
|
|
- [Mapeo con dominio de problema](#mapeo-con-dominio-de-problema)
|
|
- [Subclasificacion](#subclasificacion)
|
|
- [Polimorfismo, creacion de objetos](#polimorfismo-respaso-creacion-de-objetos)
|
|
- [Actividad - pila](#actividad-pila)
|
|
- [UML](#uml)
|
|
- [Actividad UML - semaforo](#actividad-uml)
|
|
- [Refactorización](#refactorización-de-código)
|
|
- [Intro Test Driven Development](#intro-a-test-driven-development)
|
|
- [Testing](#testing)
|
|
- [Ejemplo test - factorial](#ejemplos-de-test)
|
|
- [Ejemplo test - primos](#test-primos)
|
|
- [Actividades](#actividades)
|
|
- [Nros. Romanos](#actividad-números-romanos)
|
|
- [Caja Registradora](#actividad-caja-registradora)
|
|
|
|
|
|
----
|
|
|
|
## Intro POO
|
|
|
|
### Caracteristicas:
|
|
- Todo es un objeto
|
|
- Tipado Dinamico
|
|
- Menor Acoplamiento, facilita realizar cambios en los modelos
|
|
- Permite mejor aprovechamiento del polimorfismo
|
|
- Es meta-circular, permite hacer meta-programación
|
|
|
|
|
|
### Programa (POO)
|
|
Objetos que colaboran enter si enviandose mensajes.
|
|
Los obejtos son creados mediante clases.
|
|
Las clases son objetos que representan conceptos o ideas del dominio del problema.
|
|
Las clases definen el msj q sabe el responder objeto.
|
|
Los obejtos son conocidos por los mensajes q saben responder.
|
|
|
|
### Metodos
|
|
Son objetos que representan un conjunto de colaboraciones
|
|
|
|
En las clases se definen 2 tipos de metodos:
|
|
- Metodos **de instancia**:
|
|
Son metodos que implementan los mensajes que se envian a los objetos que
|
|
son instancias de una clase.
|
|
- Metodos **de clase**:
|
|
Son metodos que implementan los mensajes que se envian a la clase.
|
|
|
|
Existen 2 implementaciones de POO:
|
|
- Prototipos:
|
|
Un objeto ejemplar que representa el comportamiento de un conjunto de obetos similares.
|
|
- Clases:
|
|
Una clases es un onjeto que representa un concepto o idea del dominio del problema.
|
|
|
|
|
|
La **subclasificacion** o **herencia** es una herramienta para organizar el conocimiento en ontologías
|
|
|
|
La clasificacion es:
|
|
1. Una relacion estatica entre clases.
|
|
2. Obliga a tener una clase y por lo tanto su nombre antes del objeto concreto, antinatural.
|
|
3. Obliga a generealizar cuadno aun no se posee el conocimiento total de aquello que representa.
|
|
|
|
La subclasificacion (**herencia**):
|
|
1. Debe ser especificada de manera inversa a como se obtiene el conociento
|
|
2. Rompe el encapsulamiento, puesto que la subclase debe conocer la implementacion de la superclase
|
|
|
|
|
|
### Polimorfismo
|
|
Dos o mas objeto son polimorficos entre si para un conjunto de mensajes, si responden a dicho
|
|
conjunto de mensajes semanticamente igual, es decir, que hacen lo mismo.
|
|
|
|
Recibir objetos polimorficos y devolver objetos polimorficos.
|
|
Un método es un objeto que representa un conjunto e colaboraciones.
|
|
La relación de conocimiento es la única relación que existe entre los objetos.
|
|
La definición de tipo en el POO es que es un conjunyo de mensajes.
|
|
|
|
**Multiparadigma**, **funcional** e **imperativo** además de POO.
|
|
|
|
Todo es un objeto, es dinamicamente tipado, se puede utlizar al máximo el polimorfismo.
|
|
Se puede hacer metaprogramación.
|
|
|
|
|
|
#### Ejemplificación de polimorfismo
|
|
Los enteros y los decimales son polimórficos respecto a la suma
|
|
```python
|
|
print( 4 + 6,
|
|
4.5 + 5.7,)
|
|
# 10 10.2
|
|
```
|
|
Los enteros y decimales son polimórficos respecto a la resta
|
|
```python
|
|
print( 4 - 3,
|
|
8.5 - 5.7,)
|
|
# 1 2.8
|
|
```
|
|
Los enteros y decimales son polimórficos respecto a la multiplicación
|
|
```python
|
|
print( 4 * 3,
|
|
8.5 * 5.7,)
|
|
# 12 48.45
|
|
```
|
|
Pero los enteros no son polimórficos respecto a la funcion factorial
|
|
```python
|
|
import math
|
|
|
|
print( math.factorial(4) )
|
|
# 24
|
|
|
|
print( math.factorial(4.5) )
|
|
# ValueError
|
|
```
|
|
```python
|
|
class ClassA(object):
|
|
def m1(self, a, b):
|
|
return a + b
|
|
```
|
|
|
|
----
|
|
|
|
## Clases
|
|
```python
|
|
class MiClase:
|
|
"""Simple clase de ejemplo"""
|
|
|
|
i = 12345
|
|
|
|
def f(self):
|
|
return 'hola mundo'
|
|
|
|
x = MiClase()
|
|
|
|
print(x.__init__())
|
|
|
|
|
|
class Complejo:
|
|
def __init__(self, partereal, parteimaginaria):
|
|
self.r = partereal
|
|
self.i = parteimaginaria
|
|
|
|
|
|
x = Complejo(3.0, -4.5)
|
|
print(x.r, x.i)
|
|
|
|
# Objetos Instancia
|
|
|
|
x.contador = 1
|
|
while x.contador < 10:
|
|
x.contador *= 2
|
|
print(x.contador)
|
|
|
|
del x.contador
|
|
|
|
# Objetos Metodo
|
|
|
|
x = MiClase()
|
|
|
|
xf = x.f
|
|
|
|
cont = 0
|
|
while cont < 10:
|
|
print(xf())
|
|
cont += 1
|
|
```
|
|
|
|
|
|
### Variables de clase y de instancia
|
|
|
|
```python
|
|
class Perro:
|
|
tipo = 'canino' # variable de clase compartida por todas las instancias
|
|
def __init__(self, nombre):
|
|
self.nombre = nombre # variable de instancia única para la instancia
|
|
|
|
|
|
d = Perro('Fido')
|
|
e = Perro('Buddy')
|
|
|
|
print(d.tipo) # compartido por todos los perros 'canino'
|
|
|
|
print(e.tipo) # compartido por todos los perros 'canino'
|
|
|
|
print(d.nombre) # único para d 'Fido'
|
|
|
|
print(e.nombre) # único para e 'Buddy'
|
|
```
|
|
|
|
|
|
La lista trucos en el siguiente código no debería ser usada como variable
|
|
de clase porque una sola lista sería compartida por todos las instancias de ***Perro***
|
|
#### Forma Incorrecta
|
|
```python
|
|
class Perro:
|
|
trucos = [] # uso INCORRECTO de una variable de clase
|
|
def __init__(self, nombre):
|
|
self.nombre = nombre
|
|
|
|
def agregar_truco(self, truco):
|
|
self.trucos.append(truco)
|
|
|
|
d = Perro('Fido')
|
|
e = Perro('Buddy')
|
|
d.agregar_truco('girar')
|
|
e.agregar_truco('hacerse el muerto')
|
|
d.trucos # compartidos por todos los perros inesperadamente
|
|
```
|
|
|
|
#### Forma Correcta
|
|
```python
|
|
class Perro:
|
|
def __init__(self, nombre):
|
|
self.nombre = nombre
|
|
self.trucos = []
|
|
# crea una nueva lista vacía para cada perro
|
|
def agregar_truco(self, truco):
|
|
self.trucos.append(truco)
|
|
|
|
d = Perro('Fido')
|
|
e = Perro('Buddy')
|
|
d.agregar_truco('girar')
|
|
e.agregar_truco('hacerse el muerto')
|
|
print(d.trucos) # ['girar']
|
|
print(e.trucos) # ['hacerse el muerto']
|
|
```
|
|
|
|
Una función definida fuera de la clase
|
|
```python
|
|
def f1(self, x, y):
|
|
return min(x, x+y)
|
|
|
|
class C:
|
|
f = f1
|
|
def g(self):
|
|
return 'hola mundo'
|
|
|
|
h = g
|
|
```
|
|
|
|
Los métodos pueden llamar a otros métodos de la instancia usando el argumento **self**
|
|
```python
|
|
class Bolsa:
|
|
def __init__(self):
|
|
self.datos = []
|
|
|
|
def agregar(self, x):
|
|
self.datos.append(x)
|
|
|
|
def dobleagregar(self, x):
|
|
self.agregar(x)
|
|
self.agregar(x)
|
|
```
|
|
Todo valor es un objeto, y por lo tanto tiene una clase (también llamado su tipo).
|
|
Esta se almacena como **objeto.\_\_class\_\_**.
|
|
|
|
|
|
## Herencia
|
|
```python
|
|
class ClaseDerivada(modulo.ClaseBase):
|
|
|
|
# Python tiene dos funciones integradas que funcionan con herencia
|
|
# isinstance(obj, int)
|
|
|
|
# ejm
|
|
issubclass(float, int)
|
|
```
|
|
|
|
### Herencia multiple
|
|
```python
|
|
class ClaseDerivada(Base1, Base2, Base3):
|
|
...
|
|
```
|
|
[Python Method-resolution-order](https://www.python.org/download/releases/2.3/mro/)
|
|
|
|
### Variables privadas
|
|
```python
|
|
class Mapeo:
|
|
def __init__(self, iterable):
|
|
self.lista_de_items = []
|
|
self.__actualizar(iterable)
|
|
|
|
def actualizar(self, iterable):
|
|
for item in iterable:
|
|
self.lista_de_items.append(item)
|
|
|
|
__actualizar = actualizar # copia privada del actualizar() original
|
|
|
|
|
|
class SubClaseMapeo(Mapeo):
|
|
def actualizar(self, keys, values): # provee una nueva signatura para actualizar()
|
|
for item in zip(keys, values): # pero no rompe __init__()
|
|
self.lista_de_items.append(item)
|
|
```
|
|
|
|
|
|
### Cambalache
|
|
|
|
```python
|
|
class Empleado:
|
|
pass
|
|
|
|
juan = Empleado() # Crear un registro de empleado vacío
|
|
|
|
juan.nombre = 'Juan Flores' # Llenar los campos del registro
|
|
juan.depto = 'lab. de computación'
|
|
juan.salario = 1000
|
|
```
|
|
|
|
Algún código Python que espera un tipo abstracto de datos en particular, puede frecuentemente
|
|
recibir en cambio una clase que emula los métodos de aquel tipo de datos.
|
|
|
|
Por ejemplo, si tenés una función que formatea algunos datos a partir de un objeto archivo,
|
|
podés definir una clase con métodos **read()** y **readline()** que obtengan los datos de
|
|
alguna cadena en memoria intermedia, y pasarlo como argumento.
|
|
|
|
Los objetos método de instancia tienen atributos también:
|
|
- **m.__self__** es el objeto instancia con el método m() , y
|
|
- **m.__func__** es el objeto función correspondiente al método.
|
|
|
|
|
|
|
|
### Ambitos y espacios de variables
|
|
Alcance de variables
|
|
|
|
```python
|
|
def prueba_ambitos():
|
|
|
|
def hacer_local():
|
|
algo = "algo local"
|
|
|
|
|
|
def hacer_nonlocal():
|
|
nonlocal algo
|
|
algo = "algo no local"
|
|
|
|
|
|
def hacer_global():
|
|
global algo
|
|
algo = "algo global"
|
|
|
|
algo = "algo de prueba"
|
|
hacer_local()
|
|
print("Luego de la asignación local:", algo)
|
|
|
|
hacer_nonlocal()
|
|
print("Luego de la asignación no local:", algo)
|
|
|
|
hacer_global()
|
|
print("Luego de la asignación global:", algo)
|
|
|
|
|
|
|
|
prueba_ambitos()
|
|
# Luego de la asignación local: algo de prueba
|
|
# Luego de la asignación no local: algo no local
|
|
# Luego de la asignación global: algo no local
|
|
|
|
print("In global scope:", algo)
|
|
In global scope: algo global
|
|
```
|
|
----
|
|
|
|
## Buenas practicas POO
|
|
ELimar un `if`, utilizando polimorfismo:
|
|
- en POO todo deberian ser objetos que colaboran enviandose mensajes.
|
|
- el `If` en Python, no es un mensaje que se le manda un objeto,
|
|
sino que es una sentencia del lenguaje
|
|
|
|
### Isomorfismo
|
|
- Si aparece algo nuevo en el domino deber aparecer algo nuevo en el modelo (no modificarlo).
|
|
- Si se modifica algo del dominio, solo se debe modificar su representacion en el modelo.
|
|
|
|
En lenguajes de clasificación el `if` se implementa con polimorfismo.
|
|
|
|
Usar un `if` no implica no estamos usando polimorfismo. Esto implica que se tiene
|
|
diseños menos mantenibles y ademas diseños NO orientados a objetos.
|
|
ej.
|
|
```python
|
|
llamada = ''
|
|
|
|
def calculadora_costo_de_llamada(llamada):
|
|
costo = 0
|
|
if llamada.es_local():
|
|
costo = calcular_costo_local_de(llamada)
|
|
elif llamada.es_nacional():
|
|
costo = calcular_costo_internacional_de(llamada)
|
|
elif llamada.es_internacional():
|
|
costo = calcular_costo_internacional_de(llamada)
|
|
return costo
|
|
```
|
|
|
|
Como sacar el **if**?
|
|
1. Crear una jerarquia polimorfica con una abstracción por cada condición
|
|
2. Usando el mismo nombre de mensaje repartir el cuerpo del if en cada abstraccion( polimorfismo )
|
|
3. Nombrar el mensaje del paso anterior
|
|
4. Nombrar las abstracciones
|
|
5. Reemplazar if por envío de mensaje polimórfico
|
|
6. Buscar objeto polimoefico si es necesario
|
|
|
|
### Abstraccion
|
|
```python
|
|
class CondicionSuperClase(object):
|
|
def m(self):
|
|
raise NotImplementedError("Responsabilida de subclase")
|
|
|
|
class CondicionLocal(CondicionSuperClase):
|
|
def m(self):
|
|
# código de calcular_costo_local_de
|
|
|
|
class CondicionNacional(CondicionSuperClase):
|
|
def m(self):
|
|
# código de calcular_costo_nacional_de
|
|
|
|
class CondicionInternacional(CondicionSuperClase):
|
|
def m(self):
|
|
# código de calcular_costo_internacional_de
|
|
```
|
|
**Implementación puntos 1° 2° 3° 4°**
|
|
```python
|
|
class CalcularCostoLLamada(object):
|
|
|
|
@classmethod
|
|
def to_handle(clase, llamada):
|
|
pass # Código q busca CalcularCostoLLamada correspondiente
|
|
|
|
def calcular(self):
|
|
raise NotImplementedError("Responsabilida de subclase")
|
|
|
|
|
|
class CalcularCostoLLamadaLocal(CalcularCostoLLamada):
|
|
def calcular(self):
|
|
pass # código de calcular_costo_local_de
|
|
|
|
|
|
class CalcularCostoLLamadaNacional(CalcularCostoLLamada):
|
|
def calcular(self):
|
|
pass # código de calcular_costo_nacional_de
|
|
|
|
|
|
class CalcularCostoLLamadaInternacional(CalcularCostoLLamada):
|
|
def calcular(self):
|
|
pass # código de calcular_costo_internacional_de
|
|
|
|
# 6to buscar obj polimorfico
|
|
# 5to reemplazo del if por envio de mensajes polimorficos
|
|
#calculadora_costo = CalcularCostoLLamada.to_handle(llamada)
|
|
#calculadora_costo.calcular()
|
|
```
|
|
|
|
|
|
### Eliminar codigo repetido
|
|
1. Copiar código repetido
|
|
2. Parametrizar lo que cambia
|
|
3. Poner nombre
|
|
```python
|
|
def inicial_nombre_cliente(letra):
|
|
"Selecion de clientes con nombres de inicial @letra"
|
|
clientes_selec = []
|
|
for cliente in clientes:
|
|
if cliente.nombre.startswith(letra):
|
|
clientes_selec.append(cliente)
|
|
return clientes_selec
|
|
|
|
|
|
def sobrescribir_cuentas():
|
|
"Selección de cuentas con giro descubierto"
|
|
cuentas_selec = []
|
|
for cuenta in cuentas:
|
|
if cuenta.is_overdraw():
|
|
cuentas_selec.append(cuenta)
|
|
return cuentas_selec
|
|
```
|
|
Funcion que reifica las anteriores
|
|
```python
|
|
def selecionar(objetos, condicion):
|
|
"Selecciona obejtos que cumplen una condición"
|
|
selecion = []
|
|
for objeto in objetos:
|
|
if condicion(objeto):
|
|
selecion.append(objeto)
|
|
return selecion
|
|
|
|
# selecionar(clientes, lambda cliente: cliente.nombre.startswith(letra))
|
|
# selecionar(cuentas, lambda cuenta: cuenta.is_overdraw())
|
|
```
|
|
|
|
----
|
|
|
|
## Actividad
|
|
|
|
### Cantidad de dinero
|
|
```python
|
|
class Moneda():
|
|
"""Representa una Moneda"""
|
|
def __init__(self, nombre, simbolo, factor):
|
|
self.nombre = nombre
|
|
self.simbolo = simbolo
|
|
self.factor = factor
|
|
|
|
def convert_cant_a_moneda_base(self, numero):
|
|
return round(numero / self.factor, 3)
|
|
|
|
def convert_monto_de_moneda_base(self, numero):
|
|
return round(numero * self.factor, 3)
|
|
|
|
# Se llama para mostrar objeto en patalla ej, consola
|
|
def __repr__(self):
|
|
return self.nombre
|
|
```
|
|
```python
|
|
class Dinero(object):
|
|
"""Representa una cantidad de dinero"""
|
|
def __init__(self, monto, divisa):
|
|
self.cant = monto
|
|
self.moneda = divisa
|
|
|
|
def monto_moneda_base(self):
|
|
return self.moneda.convert_cant_a_moneda_base(self.cant)
|
|
|
|
def __add__(self, cantDinero):
|
|
monto = self.monto_moneda_base() + cantDinero.monto_moneda_base()
|
|
monto = self.moneda.convert_monto_de_moneda_base(monto)
|
|
return Dinero(monto, self.moneda)
|
|
|
|
def __sub__(self, cantDinero):
|
|
monto = self.monto_moneda_base() - cantDinero.monto_moneda_base()
|
|
monto = self.moneda.convert_monto_de_moneda_base(monto)
|
|
return Dinero(monto, self.moneda)
|
|
|
|
def __mul__(self, mult):
|
|
return Dinero(self.cant * mult, self.moneda)
|
|
|
|
def __truediv__(self, divi):
|
|
return Dinero(self.cant / divi, self.moneda)
|
|
|
|
def __repr__(self):
|
|
return '{} {}'.format(self.moneda.simbolo, self.cant)
|
|
```
|
|
```python
|
|
valorEuro = 912.35
|
|
valorDolar = 808.6
|
|
pesoDolar = 1 / valorDolar
|
|
pesorEuro = 1 / valorEuro
|
|
Peso = Moneda('Peso', '$', 1)
|
|
Dolar = Moneda('Dolar', 'U$', pesoDolar)
|
|
Euro = Moneda('Euro', '€', pesorEuro)
|
|
|
|
#DosPesos = Moneda('$', 2)
|
|
#CincoDolares = Moneda('U$', 5)
|
|
|
|
I_Dolar = Dinero(1, Dolar)
|
|
V_Dolar = Dinero(5, Dolar)
|
|
X_Dolar = Dinero(10, Dolar)
|
|
VX_Dolar = Dinero(50, Dolar)
|
|
C_Dolar = Dinero(100, Dolar)
|
|
VC_Dolar = Dinero(500, Dolar)
|
|
M_Dolar = Dinero(1000, Dolar)
|
|
|
|
I_Peso = Dinero(1, Peso)
|
|
X_Peso = Dinero(10, Peso)
|
|
VX_Peso = Dinero(50, Peso)
|
|
C_Peso = Dinero(100, Peso)
|
|
VC_Peso = Dinero(500, Peso)
|
|
M_Peso = Dinero(1000, Peso)
|
|
VM_Peso = Dinero(5000, Peso)
|
|
XM_Peso = Dinero(10000, Peso)
|
|
XXM_Peso = Dinero(20000, Peso)
|
|
|
|
I_Euro = Dinero(1, Euro)
|
|
V_Euro = Dinero(5, Euro)
|
|
X_Euro = Dinero(10, Euro)
|
|
VX_Euro = Dinero(50, Euro)
|
|
C_Euro = Dinero(100, Euro)
|
|
VC_Euro = Dinero(500, Euro)
|
|
M_Euro = Dinero(1000, Euro)
|
|
```
|
|
|
|
```python
|
|
print(I_Dolar+I_Dolar+I_Dolar)
|
|
# U$ 3.0
|
|
|
|
print(X_Dolar + XM_Peso + X_Euro)
|
|
# U$ 33.65
|
|
|
|
print(X_Dolar-VC_Dolar)
|
|
# U$ -490.0
|
|
|
|
print(V_Euro - C_Peso + X_Dolar)
|
|
# € 13.753
|
|
|
|
print(C_Euro * 3)
|
|
# € 300
|
|
|
|
print((XXM_Peso / 40))
|
|
# 500.0
|
|
```
|
|
|
|
----
|
|
|
|
## Introduccion al diseño con objetos
|
|
|
|
### Principios de diseño
|
|
|
|
- Eje funcional:
|
|
- Que tan buena es la representacion del dominio
|
|
Se espera que pueda representar toda observación de aquello q modela.
|
|
si aparece algo nuevo en el dominio, debe aparecer algo nuevo en el modelo.
|
|
No modificarlo.
|
|
Si se modifica algo del dominio,
|
|
solo se debe modificar su representación en el modelo.
|
|
|
|
La relación entre el dominio y el modelo debería ser de uno a uno,
|
|
lo que se denomina isomorfismo.
|
|
Este eje es la parte observacional del desarrollo.
|
|
<br>
|
|
- Eje descriptivo:
|
|
- Que tan bien está escrito el modelo, que tan entendible es un modelo
|
|
es bueno cuando se le puede entender y, por lo tanto, cambiar.
|
|
En este sentido, es muy importante usar buenos nombres, usar el mismo
|
|
lenguaje que el del dominio de problema, y el código debe ser 'lindo'.
|
|
Este eje es la parte artística del desarrollo.
|
|
<br>
|
|
- Eje implementativo:
|
|
- Como ejecuta en el aspecto técnico
|
|
un modelo es bueno cuando ejecuta en el tiempo esperado con los recursos
|
|
definidos como necesarios.
|
|
En este eje se consideran los requerimientos no funcionales,
|
|
por ejemplo performance, espacio, escalabilidad y seguridad.
|
|
<br>
|
|
|
|
Para conseguir buenos diseños existen unos principios básicos.
|
|
|
|
Entre otros, simplicidad, mantener el software simple;
|
|
consistencia, utilizar metáforas;
|
|
entendible, debe ser legible y tiene que haber un mapeo con el dominio del problema;
|
|
máxima cohesión, objetos bien funcionales;
|
|
mínimo acoplamiento, minimizar ripple effect o efecto dominó.
|
|
|
|
Para resumir todo esto, vimos los ejes por los cuales podemos evaluar nuestros
|
|
modelos y mencionamos los principales principios de diseño que nos permiten
|
|
que nuestros programas sean mantenibles, entendibles y preparados para el cambio.
|
|
|
|
----
|
|
|
|
|
|
## Reglas de diseño
|
|
|
|
### Mapeo con dominio de problema
|
|
|
|
- Regla 1
|
|
***Cada ente del dominio del problema debe estar representado por un objeto***
|
|
- Las ideas son representadas con una sola clase.
|
|
- Los entes deben tener una o mas representaciones en objetos,
|
|
depedendiendo de la implementacion.
|
|
<br>
|
|
- Regla 2
|
|
***Los objetos deben ser cohesivos representando respnsabilidades de un solo
|
|
dominio de problema. Mietnra mas cohesivo un objeto mas reutilizable es.***
|
|
<br>
|
|
- Regla 3
|
|
**Se deben utilizar buenos nombres, que sinteticen correctamenten el conocimiento
|
|
contenido por aquello que están nombrando.**
|
|
- Los nombres son el resultado de sintetizar el conocimiento
|
|
que se tiene de aquello está nombrando.
|
|
- Los nombres que se utilizan crean el vocabulario que se utiliza
|
|
en el lenguaje del modelo que se está creando.
|
|
<br>
|
|
- Regla 4
|
|
***Las clases deben representar conceptos del dominio del problema***
|
|
- Las clases no son módulos ni componentes de reuso de código.
|
|
- Crear una clase por cada componente de conocimiento o informacion del dominio del problema.
|
|
- La ausencia de clases implica ausencia de conocimiento y por lo tanto la imposibilidad
|
|
del sistema de referirse a dicho conocimiento.
|
|
|
|
|
|
### Subclasificacion
|
|
|
|
- Regla 1
|
|
**Se deben utilizar clases abstractas para representar conceptos abstractos.**
|
|
No denominar las clases abstractas como abstractas.
|
|
|
|
- Regla 2
|
|
**Las clases no-hojas del árbol de subclasificación deben ser clases abstractas**
|
|
|
|
- Regla 3
|
|
**Evitar definir variables de instancia en las clases abstractas, ya q esto impone una
|
|
implementacíon en todas las subclases.**
|
|
|
|
- Regla 4
|
|
**El motivo de subclasificación debe pertener al domonmio del problema que se esta modelando.**
|
|
|
|
- Regla 5
|
|
**No se deben mezclar motivos de subclasificación al subclasificar una clase.**
|
|
|
|
|
|
### Polimorfismo, respaso, creacion de objetos
|
|
|
|
- Regla 1
|
|
**Reemplazar el uso de if con polimorfismo**
|
|
- El if en el POO implementado usando polimorfismo
|
|
- Cada if es un indicio de la falta de un objeto y del uso del polimorfismo
|
|
|
|
- Regla 2
|
|
**El codigo repetido refleja la falta de algún objeto que represente el motivo
|
|
de dicho codigo**
|
|
- Código repetido no significa 'texto repetido', sino que patrones de colaboraciones
|
|
repetidas.
|
|
- hay reedificar ese código repetido y darle un significado por medio de un nombre.
|
|
|
|
- Regla 3
|
|
**Un objeto debe estar completo desde el momento de su creación**
|
|
- El no hacerlo abre la posibilidad a errores por estar imcompleto,
|
|
habrá mensajes que no sabrá responder.
|
|
- Si un objeto está completo desde su creación, siempre respondera los mensajes que definio.
|
|
|
|
- Regla 4
|
|
**Un objeto debe ser válido desde el momento de su creación**
|
|
- Un objeto debe representar correctamente el ente desde su inicio
|
|
- Junto a la regla anterior mantienen el modelo consistente constantemente
|
|
|
|
<br>
|
|
|
|
**Evitar** usar **none**, **objetos inmutables**, **modelar la arquitectura del sistema**.
|
|
|
|
|
|
- Regla 1
|
|
**No utlitizar None.**
|
|
- None no es polimorfico con ningún objeto.
|
|
- Por no ser polimórfico implica la necesidad de poner un if lo que abre a errores.
|
|
- None es un objeto con muchos significados.
|
|
<br>
|
|
- Regla 2
|
|
**Favorecer el uso de objetos inmutables**
|
|
- Un objeto debe ser inmutable si el ente que representa es inmutable.
|
|
- La mayoría de los entes son inmutables.
|
|
- Todo modelo mutable puede ser representado por un inmutable donde
|
|
se modele los cambios de los objetos por medio de eventos temporales.
|
|
<br>
|
|
- Regla 3
|
|
**Evitar el uso de setters**
|
|
- Para aquellos objetos mutables, ebvitar el uso de settets pq pueden generar
|
|
objetos invalidos.
|
|
- Utilizar un único mensaje de modificación.
|
|
<br>
|
|
- Regla 4
|
|
**Modelar la arquitectura del sistema**
|
|
- Crear un modelo de la arquitectura del sistema (subsistemas, etc).
|
|
- Otorgar a los subsistemas la responsabilidad de mantener la validez de todo el sistema.
|
|
(la relación entre los objetos)
|
|
- Otorgar la responsabilidad a los subsistemas de modificar un objeto por su impacto
|
|
en el conjunto.
|
|
|
|
----
|
|
|
|
## Actividad pila
|
|
```python
|
|
class Pila(object):
|
|
"ejercicio 3-2"
|
|
|
|
def __init__(self, *args):
|
|
self.base = []
|
|
for arg in args:
|
|
self.base.append(arg)
|
|
|
|
def len(self):
|
|
return len(self.base)
|
|
|
|
def top(self):
|
|
indice = self.len() - 1
|
|
return self.base[indice]
|
|
|
|
def push(self, args):
|
|
self.base.append(args)
|
|
|
|
def pop(self):
|
|
try:
|
|
return self.base.pop()
|
|
except IndexError:
|
|
return "Fuera del indice/vacío"
|
|
|
|
def is_empty(self):
|
|
return (lambda x: True if x == 0 else False)(len(self.base))
|
|
```
|
|
|
|
```python
|
|
miPila = Pila('uno', 'dos', 'tres', 'cuatro')
|
|
|
|
print("Mi Pila1 :", miPila.base, '\n')
|
|
# Mi Pila1 : ['uno', 'dos', 'tres', 'cuatro']
|
|
|
|
print("Pila1 top() :", miPila.top(), '\n')
|
|
# Pila1 top() : cuatro
|
|
|
|
print("Pila1 post - push('dieciseis') :", end='')
|
|
# Pila1 post - push('dieciseis')
|
|
|
|
miPila.push('dieciseis')
|
|
print(miPila.base, '\n')
|
|
# ['uno', 'dos', 'tres', 'cuatro', 'dieciseis']
|
|
|
|
print("Pila1 pop() :", miPila.pop(), '\n')
|
|
# Pila1 pop() : dieciseis
|
|
|
|
print("Largo Pila1 :", miPila.len(), '\n')
|
|
# Largo Pila1 : 4
|
|
|
|
print("Esta la Pila1 vacía ? :", miPila.is_empty(), '\n')
|
|
# Esta la Pila1 vacía ? : False
|
|
|
|
miPila2 = Pila()
|
|
print("Esta vacía la Pila2 ? :", miPila2.is_empty(),'\n')
|
|
# Esta vacía la Pila2 ? : True
|
|
|
|
print("Pila2 pop() : ", miPila2.pop())
|
|
# Pila2 pop() : Fuera del indice/vacío
|
|
```
|
|
|
|
----
|
|
|
|
|
|
# UML
|
|
**Unified Modeling Languaje**
|
|
|
|
### Programar es representar conocimiento
|
|
Proceso, generalmente, de 3 etapas.
|
|
|
|
### Analisis
|
|
Descripción en lenguaje natural. Informal - Inclompleto.
|
|
|
|
### Diseño
|
|
Se diagrama el conocimiento de la etapa anterior.
|
|
Informal - Incompleto - Explícito
|
|
|
|
### Programación
|
|
Codificación a lenguaje de programacion, de la etapa anterior.
|
|
Formal - Completo - Explícito
|
|
|
|
|
|
### UML es lenguaje mas usado y conocido para modelar software
|
|
Provee diversos tipos de diagramas, es ampliamente utilizado para
|
|
diseñar y documentar software. Tiene la cualidad de ser compresible.
|
|
|
|
|
|
### Diagramas Estructurales
|
|
Muestran la estructura estática de los objetos en un sistema
|
|
- Diagramas:
|
|
- **Diagrama de clases**
|
|
- **Diagrama de objetos**
|
|
- Diagrama de componentes
|
|
- Diagrama de despliegue
|
|
- Diagrama de paquetes
|
|
- Diagrama de estructura compuesta
|
|
|
|
### Diagramas de Comportamiento
|
|
Muestran el comportalmiento dinámico de los objetos en el sistema
|
|
- Diagramas:
|
|
- Diagrama de actividades
|
|
- Diagrama de casos de uso
|
|
- **Diagrama de secuencia**
|
|
- Diagrama de comunicación
|
|
- Diagrama de tiempos
|
|
- Diagrama global de interacciones
|
|
|
|
----
|
|
|
|
## Actividad UML
|
|
|
|
### Semaforo
|
|
|
|
Enfrentar un problema acotado,
|
|
incorporando los conceptos vistos,
|
|
resolver el problema pensando en objetos.
|
|
|
|
### Modelar un semáforo para automóviles
|
|
|
|
El semáforo debe ser capáz de cambiar la luz que está encendida, cada cierto tiempo,
|
|
ej. cada 40 segundos que cambie la luz encendida.
|
|
Para los diagramas usar app o web que provea un editor de UML.
|
|
Por ejemplo: [draw](https://www.draw.io/)
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant unSemaforo
|
|
participant unaLuzVerde
|
|
participant unaLuzAmarilla
|
|
participant unaLuzRoja
|
|
participant unTimer
|
|
|
|
loop Timer
|
|
unSemaforo->>unaLuzVerde: Encender
|
|
unSemaforo->>unTimer: envia_mensj_a_en(msj_cambiar,40,self)
|
|
unTimer->>unSemaforo: cambiar
|
|
unSemaforo->>unaLuzVerde: Apagar
|
|
|
|
unSemaforo->>unaLuzAmarilla: Encender
|
|
unSemaforo->>unTimer: envia_mensj_a_en(msj_cambiar,40,self)
|
|
unTimer->>unSemaforo: cambiar
|
|
unSemaforo->>unaLuzAmarilla: Apagar
|
|
|
|
unSemaforo->>unaLuzRoja: Encender
|
|
unSemaforo->>unTimer: envia_mensj_a_en(msj_cambiar,40,self)
|
|
unTimer->>unSemaforo: cambiar
|
|
unSemaforo->>unaLuzRoja: Apagar
|
|
end
|
|
Note right of unSemaforo: mermaid <br/> sequenceDiagram
|
|
```
|
|
|
|
----
|
|
|
|
|
|
## Refactorización de Código
|
|
Tecnica para reestructurar el código, cambiando la estructura interna
|
|
sin cambiar el comportamiento del programa.
|
|
|
|
Se utliza para mejorar el código o mantiendo la funcionalidad ya desarrollada.
|
|
|
|
La programación es un proceso de aprendizaje, iterativo e incremental. Y este conocimiento se
|
|
debe incorporar al código.
|
|
|
|
```txt
|
|
Ej. aprendemos que cierto concepto en el dominio del problema
|
|
se llama de una manera, pero fue nombrado de otra.
|
|
|
|
Se renombra la clase para que tenga una relación directa
|
|
con el dominio del problema.
|
|
|
|
El código queda mas comprensible y evita errores de interpretación.
|
|
```
|
|
|
|
### Ejemplos de Refactoring
|
|
- **Rename**:
|
|
Renombrar objeto y sus referencias (nombre de clase, metodo, variable, etc.
|
|
|
|
- **Extract method**:
|
|
Crear nuevo método con el código seleccionado y reemplaza con un envío de mensaje
|
|
a ese método (algunas herramientas reemplazan todas las repeticiones de la selección).
|
|
|
|
- **Move method**:
|
|
Mueve el método seleccionado a una clase visible dentro del contexto en que se mueve.
|
|
Modifica todas las referencias al método movido.
|
|
|
|
- **Convert local to field**:
|
|
Convierte una variable local, en una variable de instancia.
|
|
|
|
- **Extract to local**:
|
|
Extrae el código seleccionado en una variable local iniciada con este.
|
|
|
|
- **Changue method signature**:
|
|
Permite modificar los elementos que definen un método .
|
|
ej. los parametros. Modificas todos los senders acorde a la nueva definición.
|
|
|
|
- **Inline**:
|
|
Copia el código representado por el método o variable a los lugares donde se referencia dicho método o variable.
|
|
|
|
- **Encapsulated field**:
|
|
Referencias a variables de instancia se reemplazan por ***getters*** y ***setters***.
|
|
|
|
- **Pull up**:
|
|
Mueve variables de instancia y/o métodos a la superclase o declara el método como abstracto en la superclase.
|
|
|
|
- **Push down**:
|
|
mueve un conjunto de variables de instancia y/o métodos a las subclases.
|
|
|
|
Es recomendable **tener bien testeado** el programa antes de relizar un **Refactor**.
|
|
|
|
El software está en constante cambio, por ende, hay debe estar preparado para cambiar facilmente.
|
|
|
|
Por ello esta es una técnica muy utilizada porqué el proceso de desarrollo de software,
|
|
es iterativo e incremental.
|
|
|
|
|
|
## Mantenimiento de Software
|
|
|
|
El mantenimiento de un programa no es solo correción de errores.
|
|
|
|
Es una etapa en el ciclo de desarrollo del software, como mejoras
|
|
de funcionalidad, nuevos *features*, optimización, etc.
|
|
|
|
El mantenimiento es un desarrollo evolutivo.
|
|
|
|
|
|
----
|
|
|
|
|
|
## Intro a Test Driven Development
|
|
|
|
Desarrollo \**Guiado* por pruebas(tests). `* orientado, dirigido`
|
|
Técnica de aprendizaje iterativa e incremental, y constructivista.
|
|
Involucra escritura de pruebas y refactorización de código.
|
|
|
|
Generalmente para las pruebas se utilizan prue4bas unitarias (unit test).
|
|
|
|
### Realizando TDD
|
|
|
|
1) **Escribir un test.** Definicion del problema
|
|
a) El mas sencillo
|
|
b) Debe fallar al correrlo
|
|
|
|
2) **Correr todos los test.** Resolución del problema
|
|
a) Corregir errores y repetir.
|
|
`Resolver el problema hasta el punto donde esta definido`
|
|
|
|
3) **Evaluar posibles mejoras.** Mejora de Diseño
|
|
a) Si hay mejoras posibles, refactorizar y volver al paso 2.
|
|
`Mejorando el diseño del programa`
|
|
b) *Si no hay mejoras, volver al paso 1.*
|
|
|
|
|
|
### Caracteristicas TDD
|
|
|
|
**Es incremental:**
|
|
- Se concreta al hacer el test más sencillo
|
|
- Al fallar al correrlo
|
|
|
|
**Recuerda lo aprendido, y Asegura q se siga cumpliendo:**
|
|
- Se concreta al correr todos los test.
|
|
|
|
**Feedbak inmediato:**
|
|
- Se concreta cuando hay errores
|
|
|
|
**Es iterativo:**
|
|
- Se concreta al saltar al paso 1 o 2
|
|
|
|
> *Refelxion en el paso 3, meta-aprender*
|
|
|
|
|
|
El **TDD** permite escribir el mínimo código necesario para alcanzar
|
|
la solución del problema, y resolver solo este.
|
|
|
|
Al Tener tests se logra que el programa sea mantenible, y se pueden
|
|
realizar cambios sabiendo que agún test advertirá si se rompe algna
|
|
funcionalidad.
|
|
|
|
|
|
### No es TDD:
|
|
|
|
- Cuando **no hay feedback inmediato**:
|
|
Escribiendo o modificando código antes de escribir un test
|
|
o escribiendo muchos test antes de escribir el código.
|
|
|
|
- Cuando **no se desarrolla de manera iterativa e incremental**:
|
|
Escribiendo la solución completa de entrada o escribiendo
|
|
los test luego de tener el código (eso ya es testing).
|
|
|
|
|
|
----
|
|
|
|
## Testing
|
|
|
|
***Detalle de:*** Test Unitario Automático
|
|
|
|
Los test poseen una estructura interna común:
|
|
- **Anatomía de los Tests**
|
|
- ***Setup***:
|
|
Es donde se crean los objetos necesarios (el contexto) para el test.
|
|
|
|
- ***Act***:
|
|
Acción a realizar o probar.
|
|
|
|
- ***Assertions***:
|
|
Verificaciones sobre resultados obtenidos.
|
|
Los assertions son afirmaciones que gerealmente chequean
|
|
el estado del sistema, comparando los resultados obtenidos
|
|
con los esperados.
|
|
|
|
Algunos métodos de la clase TestCase:
|
|
- assertEqual
|
|
- assertNotEqual
|
|
- assertTrue
|
|
- assertFalse
|
|
- assertRises
|
|
|
|
<br>
|
|
|
|
- **Caracteristicas deseables de los test**
|
|
- Ser de rápida ejecución.
|
|
- Ser reducidos en tamaño.
|
|
- Ser entendibles.
|
|
- Debe tener 1 test por caso.
|
|
- Debe tener control de todo.
|
|
- Misma cantidad de lineas de código q las del sistema.
|
|
- Nombres declarativos y resumir el: ***GIVEN*** / ***WHEN*** / ***THEN***
|
|
<br>
|
|
|
|
|
|
- **Buenas practicas**
|
|
- Testear un caso por test (no implica tener solo un assert).
|
|
- Empezar siempre por el test mas sencillo.
|
|
- Comenzar por la aserción, ayuda a entender qué se quiere hacer.
|
|
- Siempre debe haber un assert en el test (o fail, etc).
|
|
- Recordar testear casos **negativos** (posibles fallas), no solo positivos.
|
|
- Recordar que el test debe estar en control de todo:
|
|
*En el caso de testeo sobre secuencias Verificar la longitud que debe tener
|
|
y que estén unicamente los objetos que deben estar.
|
|
<br>
|
|
|
|
- **Clasificación de Test según funcionamiento**
|
|
- Test Insoportables: Tardan mucho, posible uso de algún recurso
|
|
lento (bd, conex, etc).
|
|
- Test Fragiles: Se "rompen" cuando se modifica la implementación
|
|
interna de un objeto. Son test de "caja blanca".
|
|
- Test Erraticos: Aveces funcionan, otras no. Hay dependencias
|
|
de "pictures" entre test o usan recursos externos.
|
|
|
|
|
|
### Framework Xunit
|
|
|
|
Frameworks de desarrollo guiado por por pruebas conocidos colectivamente como **xUnit**.
|
|
|
|
Disponibles para otros lenguajes y plataformas.
|
|
|
|
### Librería Unittest
|
|
|
|
Framework de test unitarios **unittest** de la libreria estándar (inspirado en **JUnit**).
|
|
|
|
Similar a la mayoria de los frameworks de otros lengajes, soporta automatización de test,
|
|
código compartido para setup y shutdown de test, agregar test en colecciones
|
|
e independencia de tests respecto del framework de reporte.
|
|
|
|
### Componentes Xunit
|
|
|
|
- **Test Fixture :**
|
|
Representa la preparación necesaria para correr uno o más test,
|
|
y cualquier acción de limpieza asociada.
|
|
|
|
- **Test Case :**
|
|
Unidad de prueba individial. Ademas de definir los metodos de Assertions,
|
|
define también:
|
|
- setUp:
|
|
Llamado antes de ejecutar cada test del TestCase.
|
|
Aqui va el código para preparar el contexto del test.
|
|
|
|
- tearDown:
|
|
LLamado al finalizar cada ejecución de test del TestCase.
|
|
Aquí va el código para limpiar el contexto que pudo haber modificado el test.
|
|
|
|
- setUpClass:
|
|
Ejecutado cuando se crea una instancia del TestCase.
|
|
Utilizado para hacer un setup del contexto común entre todos los test del TestCase.
|
|
|
|
- tearDownClass:
|
|
Ejecutado cuando se destruye una instancia del TestCase.
|
|
Utilizado para hacer limpieza del contexto. Evitando que afecte otros TestCase.
|
|
|
|
- addCleanup:
|
|
Agrega una función de limpieza que se ejecutará en el teardown del test.
|
|
Al agregar muchas opciones, estas se ejecutarán en orden LIFO.
|
|
|
|
- **Test Suite :**
|
|
Colección de test cases, test suites o ambos.
|
|
|
|
* **Test Runner:**
|
|
Coordina la ejecución de test y provee el Output al usuario.
|
|
|
|
|
|
----
|
|
|
|
|
|
## Ejemplos de test
|
|
|
|
### Test Factorial
|
|
Ejemplo de implementacion de test y pruebas de la funcion factorial
|
|
```
|
|
factorial(0) = 1
|
|
factorial(1) = 1
|
|
factorial(2) = 2
|
|
factorial(3) = 6
|
|
factorial(4) = 24
|
|
factorial(5) = 120
|
|
|
|
n! = n*(n-1)*(n-2)*...*1
|
|
```
|
|
```python
|
|
import unittest
|
|
|
|
class ErrorValorNegativo(Exception):
|
|
pass
|
|
|
|
# Esta función se puede definir de forma recursiva o iterativa
|
|
# Para este caso se define usando recursión.
|
|
def factorial(n):
|
|
if n < 0:
|
|
raise ErrorValorNegativo("Número Negativo")
|
|
elif n == 0:
|
|
return 1
|
|
elif n == 1:
|
|
return 1
|
|
else:
|
|
return n * factorial(n - 1)
|
|
|
|
class FactoriaTestCase(unittest.TestCase):
|
|
|
|
def test_factorial_numero_negativo(self):
|
|
self.assertRaises(ErrorValorNegativo, factorial, -1)
|
|
|
|
def test_factorial_0(self):
|
|
self.assertEqual(1, factorial(0))
|
|
|
|
def test_factorial_1(self):
|
|
self.assertEqual(1, factorial(1))
|
|
|
|
def test_factorial_2(self):
|
|
self.assertEqual(2, factorial(2))
|
|
|
|
def test_factorial_3(self):
|
|
self.assertEqual(6, factorial(3))
|
|
|
|
def test_factorial_4(self):
|
|
self.assertEqual(24, factorial(4))
|
|
|
|
def test_factorial_5(self):
|
|
self.assertEqual(120, factorial(5))
|
|
|
|
def test_factorial_6(self):
|
|
self.assertEqual(720, factorial(6))
|
|
|
|
# if __name__ == '__main__':
|
|
# unittest.main()
|
|
for i in range(10):
|
|
print(factorial(i))
|
|
```
|
|
Resultados
|
|
```python
|
|
........
|
|
----------------------------------------------------------------------
|
|
Ran 8 tests in 0.000s
|
|
|
|
OK
|
|
```
|
|
|
|
----
|
|
|
|
### Test Primos
|
|
Obtener los factores primos de un número entero
|
|
```
|
|
2 ---> 2
|
|
4 ---> 2,2
|
|
6 ---> 2,3
|
|
12---> 2,2,3
|
|
91---> 7,13
|
|
```
|
|
|
|
```python
|
|
import unittest
|
|
|
|
|
|
def es_primo(n):
|
|
if n <= 1:
|
|
result = False
|
|
else:
|
|
result = True
|
|
for div in range(2, n):
|
|
if n % div == 0:
|
|
result = False
|
|
break;
|
|
return result
|
|
|
|
|
|
def primos_hasta(n):
|
|
primos = []
|
|
for x in range(2, n+1):
|
|
if es_primo(x):
|
|
primos.append(x)
|
|
return primos
|
|
|
|
|
|
def factores_primos(n):
|
|
result = []
|
|
for div in primos_hasta(n):
|
|
while n % div == 0:
|
|
result.append(div)
|
|
n /= div
|
|
return result
|
|
|
|
|
|
class EsPrimoTestCase(unittest.TestCase):
|
|
|
|
def test_es_primo_1(self):
|
|
result = es_primo(1)
|
|
self.assertFalse(result)
|
|
|
|
def test_es_primo_2(self):
|
|
result = es_primo(2)
|
|
self.assertTrue(result)
|
|
|
|
def test_es_primo_3(self):
|
|
result = es_primo(3)
|
|
self.assertTrue(result)
|
|
|
|
def test_es_primo_4(self):
|
|
result = es_primo(4)
|
|
self.assertFalse(result)
|
|
|
|
@unittest.skip("test malo, saltado")
|
|
def test_es_primo_5(self):
|
|
result = es_primo(5)
|
|
#self.assertFalse(result)
|
|
self.assertFalse(result)
|
|
|
|
def test_es_primo_6(self):
|
|
result = es_primo(6)
|
|
self.assertFalse(result)
|
|
|
|
def test_es_primo_7(self):
|
|
result = es_primo(7)
|
|
self.assertTrue(result)
|
|
|
|
|
|
class PrimosHastaTestCase(unittest.TestCase):
|
|
|
|
def test_primos_hasta_1(self):
|
|
result = primos_hasta(1)
|
|
self.assertEqual([], result)
|
|
|
|
def test_primos_hasta_2(self):
|
|
result = primos_hasta(2)
|
|
self.assertEqual([2], result)
|
|
|
|
def test_primos_hasta_3(self):
|
|
result = primos_hasta(3)
|
|
self.assertEqual([2, 3], result)
|
|
|
|
def test_primos_hasta_4(self):
|
|
result = primos_hasta(4)
|
|
self.assertEqual([2, 3], result)
|
|
|
|
def test_primos_hasta_5(self):
|
|
result = primos_hasta(5)
|
|
self.assertEqual([2, 3, 5], result)
|
|
|
|
def test_primos_hasta_6(self):
|
|
result = primos_hasta(6)
|
|
self.assertEqual([2, 3, 5], result)
|
|
|
|
def test_primos_hasta_7(self):
|
|
result = primos_hasta(7)
|
|
self.assertEqual([2, 3, 5, 7], result)
|
|
|
|
|
|
class FactoresPrimosTestCase(unittest.TestCase):
|
|
def test_factores_primos_de_1(self):
|
|
result = factores_primos(1)
|
|
self.assertEqual([], result)
|
|
|
|
def test_factores_primos_de_2(self):
|
|
result = factores_primos(2)
|
|
self.assertEqual([2], result)
|
|
|
|
def test_factores_primos_de_4(self):
|
|
result = factores_primos(4)
|
|
self.assertEqual([2, 2], result)
|
|
|
|
def test_factores_primos_de_6(self):
|
|
result = factores_primos(6)
|
|
self.assertEqual([2, 3], result)
|
|
|
|
def test_factores_primos_de_12(self):
|
|
result = factores_primos(12)
|
|
self.assertEqual([2, 2, 3], result)
|
|
|
|
def test_factores_primos_de_91(self):
|
|
result = factores_primos(91)
|
|
self.assertEqual([7, 13], result)
|
|
self.assertEqual(2, len(result))
|
|
self.assertIn(7, result)
|
|
self.assertIn(13, result)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
```
|
|
Resultados
|
|
```python
|
|
....s...............
|
|
----------------------------------------------------------------------
|
|
Ran 20 tests in 0.001s
|
|
|
|
OK (skipped=1)
|
|
```
|
|
|
|
----
|
|
|
|
## Actividades
|
|
|
|
### Actividad números romanos
|
|
|
|
Aplicar técnica de programación Test Driven Development (TDD)
|
|
|
|
Implementar de manera iterativa e incremental la conversión de números
|
|
enteros a números romanos.
|
|
|
|
- Se debe poder convertir cualquier número entero desde el 1 hasta el 1000.
|
|
- Escribe el código y los tests en un archivo de Python.
|
|
|
|
Relación números y los símbolos:
|
|
|Número entero | Número romano|
|
|
|-|-|
|
|
|1 | I|
|
|
|5 | V|
|
|
|10 | X|
|
|
|50 | L|
|
|
|100 | C|
|
|
|500 | D|
|
|
|1000| M|
|
|
|
|
```python
|
|
import unittest
|
|
|
|
class NumeroRomano(object):
|
|
|
|
valores = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
|
|
simbolos = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
|
|
|
|
def conv_a_romano(self, n):
|
|
restante = n
|
|
nro_romano = ''
|
|
for i in range(len(self.valores)):
|
|
nro_romano, restante = self.agregar_nro_romano(restante,
|
|
self.valores[i],
|
|
self.simbolos[i],
|
|
nro_romano)
|
|
return nro_romano
|
|
|
|
def agregar_nro_romano(self, n, numero, valor_romano, num_romano):
|
|
restante = n
|
|
while restante >= numero:
|
|
num_romano = num_romano + valor_romano
|
|
restante -= numero
|
|
return num_romano, restante
|
|
|
|
|
|
class NumeroRomanoTest(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.nro_romano = NumeroRomano()
|
|
|
|
def test_1_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(1)
|
|
self.assertEqual('I', nro_romano)
|
|
|
|
def test_2_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(2)
|
|
self.assertEqual('II', nro_romano)
|
|
|
|
def test_3_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(3)
|
|
self.assertEqual('III', nro_romano)
|
|
|
|
def test_4_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(4)
|
|
self.assertEqual('IV', nro_romano)
|
|
|
|
def test_5_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(5)
|
|
self.assertEqual('V', nro_romano)
|
|
|
|
def test_9_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(9)
|
|
self.assertEqual('IX', nro_romano)
|
|
|
|
def test_10_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(10)
|
|
self.assertEqual('X', nro_romano)
|
|
|
|
def test_40_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(40)
|
|
self.assertEqual('XL', nro_romano)
|
|
|
|
def test_50_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(50)
|
|
self.assertEqual('L', nro_romano)
|
|
|
|
def test_90_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(90)
|
|
self.assertEqual('XC', nro_romano)
|
|
|
|
def test_100_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(100)
|
|
self.assertEqual('C', nro_romano)
|
|
|
|
def test_400_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(400)
|
|
self.assertEqual('CD', nro_romano)
|
|
|
|
def test_500_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(500)
|
|
self.assertEqual('D', nro_romano)
|
|
|
|
def test_900_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(900)
|
|
self.assertEqual('CM', nro_romano)
|
|
|
|
def test_989_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(989)
|
|
self.assertEqual('CMLXXXIX', nro_romano)
|
|
|
|
def test_1000_romano(self):
|
|
nro_romano = self.nro_romano.conv_a_romano(1000)
|
|
self.assertEqual('M', nro_romano)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
```
|
|
Resultados
|
|
```python
|
|
................
|
|
----------------------------------------------------------------------
|
|
Ran 16 tests in 0.000s
|
|
|
|
OK
|
|
```
|
|
|
|
----
|
|
|
|
### Actividad Caja Registradora
|
|
Para este proyecto, deberás programar una caja registradora para una almacén.
|
|
|
|
- [x] El sistema debe poder escanear un producto (el cajero puede tipear el código del producto),
|
|
- [x] y agregarlo a la lista de productos comprados para ese cliente.
|
|
|
|
- [x] Además debe mostrar el subtotal.
|
|
|
|
- [x] El cajero cuando lo desee puede finalizar la compra y
|
|
- [x] el sistema deberá aplicar los descuentos correspondientes a los productos.
|
|
|
|
- [x] Luego, el cajero indica con cuánto paga el cliente y el sistema
|
|
- [x] debe mostrar el cambio que debe devolver al cliente.
|
|
|
|
Se pide hacer los modelos y las pruebas de las funcionalidades.
|
|
|
|
> No es necesario hacer una interfaz gráfica (o de consola), sino que puede estar
|
|
> todo el funcionamiento validado con las pruebas unitarias.
|
|
>
|
|
> @author devfzn@gmail.com
|
|
|
|
```python
|
|
import unittest
|
|
|
|
|
|
class CajaRegistradora(object):
|
|
|
|
def __init__(self):
|
|
self.catalogo = {'001': ['Leche', 1050, 5.0],
|
|
'002': ['Azúcar', 950, 3.0],
|
|
'003': ['Café', 2000, 7.0]}
|
|
self.pedido = []
|
|
|
|
def escanear_producto(self, codigo):
|
|
if codigo in self.catalogo:
|
|
return self.catalogo[codigo]
|
|
else:
|
|
return 'null', 'null', 'null'
|
|
|
|
def agregar_a_pedido(self, codigo):
|
|
self.pedido.append(self.escanear_producto(codigo))
|
|
|
|
def ver_pedido(self):
|
|
return self.pedido + [['TOTAL ', self.subtotal()]]
|
|
|
|
def subtotal(self):
|
|
subtotal = 0
|
|
for item in self.pedido:
|
|
subtotal += item[1] - (item[1]*item[2]/100)
|
|
return subtotal
|
|
|
|
def finalizar_pedido(self, pago):
|
|
if pago > self.subtotal():
|
|
return ['Vuelto', pago - self.subtotal()]
|
|
|
|
|
|
class CajaRegistradoraScanTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.caja_registradora = CajaRegistradora()
|
|
|
|
def test_escanear_producto_1(self):
|
|
resultado = self.caja_registradora.escanear_producto('001')
|
|
self.assertEqual(['Leche', 1050, 5.0], resultado)
|
|
|
|
def test_escanear_producto_2(self):
|
|
resultado = self.caja_registradora.escanear_producto('002')
|
|
self.assertEqual(['Azúcar', 950, 3.0], resultado)
|
|
|
|
def test_escanear_producto_3(self):
|
|
resultado = self.caja_registradora.escanear_producto('003')
|
|
self.assertEqual(['Café', 2000, 7.0], resultado)
|
|
|
|
|
|
class CajaRegistradoraListaPedidoSimpleTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.caja_registradora = CajaRegistradora()
|
|
|
|
def test_agregar_a_pedido_001(self):
|
|
self.caja_registradora.agregar_a_pedido('001')
|
|
resultado = self.caja_registradora.pedido
|
|
self.assertEqual([['Leche', 1050, 5.0]], resultado)
|
|
|
|
def test_agregar_a_pedido_002(self):
|
|
self.caja_registradora.agregar_a_pedido('002')
|
|
resultado = self.caja_registradora.pedido
|
|
self.assertEqual([['Azúcar', 950, 3.0]], resultado)
|
|
|
|
def test_agregar_a_pedido_003(self):
|
|
self.caja_registradora.agregar_a_pedido('003')
|
|
resultado = self.caja_registradora.pedido
|
|
self.assertEqual([['Café', 2000, 7.0]], resultado)
|
|
|
|
|
|
class CajaRegistradoraListaPedidoMultipleTestCase(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.caja_registradora = CajaRegistradora()
|
|
self.caja_registradora.agregar_a_pedido('003')
|
|
self.caja_registradora.agregar_a_pedido('003')
|
|
self.caja_registradora.agregar_a_pedido('001')
|
|
self.caja_registradora.agregar_a_pedido('002')
|
|
|
|
def test_ver_pedido(self):
|
|
resultado = self.caja_registradora.ver_pedido()
|
|
self.assertEqual([['Café', 2000, 7.0],
|
|
['Café', 2000, 7.0],
|
|
['Leche', 1050, 5.0],
|
|
['Azúcar', 950, 3.0],
|
|
['TOTAL ', 5639]], resultado)
|
|
|
|
def test_subtotal(self):
|
|
resultado = self.caja_registradora.subtotal()
|
|
self.assertEqual(5639, resultado)
|
|
|
|
def test_finalizar_pedido10000(self):
|
|
resultado = self.caja_registradora.finalizar_pedido(10000)
|
|
self.assertEqual(['Vuelto', 4361], resultado)
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|
|
```
|
|
```python
|
|
.........
|
|
----------------------------------------------------------------------
|
|
Ran 9 tests in 0.000s
|
|
|
|
OK
|
|
```
|
|
|
|
----
|
|
|