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