SOLID, testing and javascript
by: Stiven Cardona Monsalve

Hola

SOLID, test integrales, test unitarios, test end to end.
Que significa esto ?
Estan preparados para aprender un poco de SOLID y testing?


SOLID al rescate

Desarrollo Inicial
Mantenimiento
Costo

Ecuación SOLID
intuitivo + testeable + tolera cambios = SOLID
Singleton
Tight Coupling
Untestability
Premature Optimización
Indescriptive
Duplication
SOLID
Single Responsability
Open/Closed
Liskov substitution
Interface segregation
Dependencie Inversion

No es mi intención herir sentimientos

STUPID

SINGLETON
class Singleton {
constructor(){
if(Singleton.instance){
return Singleton.instance;
}
this.title = "my singleton";
Singleton.instance = this;
}
}
let mySingleton = new Singleton()
let mySingleton2 = new Singleton()
console.log("Singleton 1: ", mySingleton.title);
mySingleton.title = "modified in instance 1"
console.log("Singleton 2: ", mySingleton2.title);PERDIDA DE CONTROL
TIGHT COUPLING
UNTESTABILITY
generate

PREMATURE OPTIMIZATION
"Si ni si quiera hemos empezado a escalar la montaña porque preguntarnos que haremos en la cima"

No significa que debamos escribir código poco optimo
Esencial vs Accidental (Idea Principal)
INDESCRIPTIVE NAMING
//bad
const fruit = ['manzana', 'mango', 'fresa'];
//regular
const fruitList = ['manzana', 'mango', 'fresa'];
//good
const fruits = ['manzana', 'mango', 'fresa'];
//better
const fruitsNames = ['manzana', 'mango', 'fresa'];
//bad
const open = true;
const write = true;
const fruit = true;
//better
const isOpen = true;
const canWrite = true;
const hasFruit = true;
DUPLICATION
-
Duplicidad Real
-
Duplicidad Accidental
please DRY (Don't Repeat Yourself)
//worse
const reportData = {
name: "Software Crafters",
createdAt: new Date(),
purchases: 100,
conversionRate: 10,
}
function withOutDRY(){
function showReport(reportData) {
const reportFormatted = `
Name: ${reportData.name}
Created at: ${reportData.createdAt}
Purchases: ${reportData.purchases}
Conversion Rate: ${reportData.conversionRate}%
`
console.log("Showing report", reportFormatted)
}
function saveReport(reportData) {
const reportFormatted = `
Name: ${reportData.name}
Created at: ${reportData.createdAt}
Purchases: ${reportData.purchases}
Conversion Rate: ${reportData.conversionRate}%
`
console.log("Saving report...", reportFormatted)
}
showReport(reportData)
saveReport(reportData)
}//better
const reportData = {
name: "Software Crafters",
createdAt: new Date(),
purchases: 100,
conversionRate: 10,
}
function withDRY(){
function formatReport(reportData){
return `
Name: ${reportData.name}
Created at: ${reportData.createdAt}
Purchases: ${reportData.purchases}
Conversion Rate: ${reportData.conversionRate}%
`
}
function showReport(reportData) {
console.log("Showing report...", formatReport(reportData));
}
function saveReport(reportData) {
console.log("Saving report...", formatReport(reportData));
}
showReport(reportData)
saveReport(reportData)
}SOLID

Ok, vale se lo que estoy haciendo mal.

Pero hmmmm.

Cómo puedo organizar mis funciones y estructuras de datos en componentes ?
y cómo los conecto?

SINGLE RESPONSABILITY
Mis clases deben tener una única responsabilidad

Pero ojo tener una única responsabilidad
no implica hacer una única cosa
class UseCase{
doSomethingWithTaxes(){
console.log("Do something related with taxes ...")
}
saveChangesInDatabase(){
console.log("Saving in database ...")
}
sendEmail(){
console.log("Sending email ...")
}
}class UseCase{
constructor(repo, notifier){
this.repo = repo;
this.notifier = notifier;
}
doSomethingWithTaxes(){
console.log("Do something related with taxes ...")
}
saveChanges(){
this.repo.update();
}
notify(){
this.notifier.notify("Hi!")
}
}
class Repository{
add(){
console.log("Adding in database");
}
update(){
console.log("Updating in database...");
}
remove(){
console.log("Deleting from database ...");
}
find(){
console.log("Finding from database ...");
}
}
class NotificationService{
notify(message){
console.log("Sending message ...");
console.log(message);
}
}
OPEN / CLOSED
"Todas las entidades de software deberían estar abiertas a extensión, pero cerradas a modificación "
Bertrand Meyer

var axios = require('axios');
class TodoExternalService{
requestTodoItems(callback){
const url = 'https://jsonplaceholder.typicode.com/todos/';
axios
.get(url)
.then(callback)
}
}
new TodoExternalService().requestTodoItems(response => console.log(response.data))//infraestructure/ClientWrapper
const axios = require('axios');
export class ClientWrapper{
makeGetRequest(url, callback){
return axios.get(url).then(callback);
}
}
//domain/TodoService
export class TodoService{
client;
constructor(client){
this.client = client;
}
requestTodoItems(callback){
const url = 'https://jsonplaceholder.typicode.com/todos/';
this.client.makeGetRequest(url, callback)
}
}
//idex
import {ClientWrapper} from './infrastructure/ClientWrapper'
import {TodoService} from './domain/TodoService'
const start = () => {
const client = new ClientWrapper();
const todoService = new TodoService(client);
todoService.requestTodoItems(response => console.log(response.data))
}
start();//infraestructure/ClientWrapper
export class ClientWrapper{
makeGetRequest(url, callback){
return fetch(url)
.then(reponse => response.json())
.then(callback)
}
}
//domain/TodoService
export class TodoService{
client;
constructor(client){
this.client = client;
}
requestTodoItems(callback){
const url = 'https://jsonplaceholder.typicode.com/todos/';
this.client.makeGetRequest(url, callback)
}
}
//idex
import {ClientWrapper} from './infrastructure/ClientWrapper'
import {TodoService} from './domain/TodoService'
const start = () => {
const client = new ClientWrapper();
const todoService = new TodoService(client);
todoService.requestTodoItems(response => console.log(response.data))
}
start();LISKOV SUBSTITUTION
Ganadora del premio Turing (el Nobel de la informatica), la primera mujer en los USA en conseguir el doctorado en Ciencias Computacionales



Si una clase A es extendida por una clase B, debemos ser capaces de sutituir cualquier instancia de A por cualquier objeto de B sin que el sistema deje de funcionar o se presenten comportamientos inesperados
OJO MUCHA ATENCIÓN

class Rectangle {
constructor() {
this.width = 0;
this.height = 0;
}
setWidth(width) {
this.width = width;
}
setHeight(height) {
this.height = height;
}
getArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width;
}
setHeight(height) {
this.width = height;
this.height = height;
}
}
test('Should be able to calculate the area for the rectangle', ()=>{
let rectangle = new Rectangle()
rectangle.setHeight(5)
rectangle.setWidth(4)
expect(rectangle.getArea()).toBe(20)
})
test('Should be able to calculate the area for the square', ()=>{
let square = new Square()
square.setHeight(5)
square.setWidth(4)
expect(square.getArea()).toBe(20)
})
class Figure{
constructor() {
this.width = 0;
this.height = 0;
}
getArea() {
return this.width * this.height;
}
}
class Rectangle extends Figure {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
}
class Square extends Figure {
constructor(length) {
super();
this.width = length;
this.height = length;
}
}
test('Should be able to calculate the area for the rectangle', ()=>{
let rectangle = new Rectangle(5, 4)
expect(rectangle.getArea()).toBe(20)
})
test('Should be able to calculate the area for the square', ()=>{
let square = new Square(5)
expect(square.getArea()).toBe(25)
})INTERFACE SEGREGATION
Si nuestras interfaces definen más métodos de los necesarios, entonces en nuestras clases podriamos vernos obligados a crear implementaciones forzadas
interface Car{
accelerate: () => void;
brake: () => void;
startEngine: () => void;
}
class Mustang implements Car {
accelerate(){
console.log("Speeding up...")
}
brake(){
console.log("Stoping...")
}
startEngine(){
console.log("Starting engine...")
}
}interface Car{
accelerate: () => void;
brake: () => void;
startEngine: () => void;
autoPilot: () => void;
}
class ModelS implements Car {
accelerate(){
console.log("Speeding up...")
}
brake(){
console.log("Stoping...")
}
startEngine(){
console.log("Starting engine...")
}
autoPilot(){
console.log("Self drinving");
}
}
class Mustang implements Car {
accelerate(){
console.log("Speeding up...")
}
brake(){
console.log("Stoping...")
}
startEngine(){
console.log("Starting engine...")
}
autoPilot(){
throw new Error("UnSupported operation");
}
}interface Car{
accelerate: () => void;
brake: () => void;
startEngine: () => void;
}
interface Tesla{
autoPilot: () => void;
}
class ModelS implements Car, Tesla {
accelerate(){
console.log("Speeding up...")
}
brake(){
console.log("Stoping...")
}
startEngine(){
console.log("Starting engine...")
}
autoPilot(){
console.log("Self drinving");
}
}
class Mustang implements Car {
accelerate(){
console.log("Speeding up...")
}
brake(){
console.log("Stoping...")
}
startEngine(){
console.log("Starting engine...")
}
}DEPENDENCY INVERSION
Gracias al principio de inversión de dependencias, podemos hacer que el código que es el núcleo de nuestra aplicación no dependa de los detalles de implementación
// hidden dependency
class UseCase{
constructor(){
this.externalService = new ExternalService();
}
doSomething(){
this.externalService.doExternalTask();
}
}
class ExternalService{
doExternalTask(){
console.log("Doing task...")
}
}
// dependency injection
class UseCase{
constructor(externalService: ExternalService){
this.externalService = externalService;
}
doSomething(){
this.externalService.doExternalTask();
}
}
class ExternalService{
doExternalTask(){
console.log("Doing task...")
}
}interface IExternalService{
doExternalTask: () => void;
}
class UseCase {
externalService: IExternalService;
constructor(externalService: IExternalService){
this.externalService = externalService;
}
doSomething(){
this.externalService.doExternalTask();
}
}
class ExternalService implements IExternalService {
doExternalTask(){
console.log("Doing external task...")
}
}
const client = new UseCase(new ExternalService());
client.doSomething();


Cumplimos con el objetivo?
Introducción al testing

Tipos de TEST
-
Test funcionales
-
Test no funcionales

"El testing de software puede verificar la presencia de errores pero no la ausencia de ellos" Edsger Dijkstra
Test funcionales
-
Test unitarios
-
Test integrales
-
Test de sistema (end to end)
Test no funcionales
-
Tests de carga
-
Tests de velocidad
-
Tests de usabilidad
-
Tests de seguridad

- Fase inicial
- Fase de ejecución
- Fase de evaluación




Test Unitarios
-
Rapidos
-
Aislados
-
Atómicos
-
Fáciles de mantener y extender
-
Deterministas
-
Legibles
test('return zero if recieve one'){
//Arrange
const n = 1;
//Atc
const result = fibonacci(n);
// Assert
expect(result).toBe(0);
}
test('return zero if recieve one', () => {
expect(fibonacci(1)).toBe(0);
})Test End to End

SOLID, testing and javascript
By Stiven Cardona
SOLID, testing and javascript
- 273