Apuntes_Python/01_curso/Modulo_3/README.md
2022-12-24 22:41:20 -03:00

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
```
----