leanmind logo leanmind text logo

Artículos

Por Jorge Aguiar y Yodra Lópezel 23/01/2020

Afrontando un MVP con impacto social

Hace unos meses se nos presentó la oportunidad de colaborar en un proyecto social con Laborsord, S.L. (Centro Especial de Empleo de iniciativa social y sin ánimo de lucro), dedicada a la integración laboral de personas con discapacidad.

El proyecto que se nos planteó consiste en crear una plataforma para que las personas con discapacidad auditiva pudieran seguir una clase en vivo, de manera que mientras el profesor hablaba, ellos pudieran ver en tiempo real la transcripción de la clase. Esta necesidad surge de la falta de Intérpretes de Lengua de Signos durante todas las horas lectivas. Del mismo modo, la plataforma se planteó como una alternativa a tomar apuntes ya que seguir la clase leyendo y tomando apuntes al mismo tiempo no resulta muy sencillo.

Lo más importante para sacar este proyecto adelante es tener un buen sistema de transcripción, como pueden imaginar para una persona con discapacidad auditiva puede ser muy difícil mantener la atención y seguir una clase. Por lo tanto, nos enfocamos en tener un MVP basado en esta funcionalidad, para obtener feedback de los usuarios lo antes posible y poder mejorar. La aplicación no tendría sentido si la transcripción no es fluida y correcta.

Es por ello que decidimos probar el servicio de transcripción que ofrece AWS que soporta castellano y nos dió buenos resultados de manera que nos evita invertir tiempo en probar diferentes sistemas de transcripción.

Como el título ya adelanta es un proyecto social que hacemos sin ánimo de lucro, lo que supone tener un tiempo limitado para hacer un producto con la mejor calidad posible.

Así que nos pusimos manos a la obra y creamos un proyecto con el stack tecnológico que ya hemos utilizado en otros proyectos.

Para el frontend hemos utilizado React haciendo uso de Funtional Components con Hooks y hemos hecho testing con testing-library. El código está disponible en Github.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import * as React from 'react';
import './AudioButtons.scss';
import { FC, useState } from 'react';
import { AudioService } from '../../services/AudioService';
import { Button } from '../Button';
import { Container } from '../Container';
import { BrowserMediaService } from '../../services/BrowserMediaService';

interface Dependencies {
  audioService: AudioService;
  browserMediaService?: BrowserMediaService;
}

export const AudioButtons: FC<Dependencies> = ({ audioService, browserMediaService }) => {
  const [isListening, setIsListening] = useState(false);
  const startAudio = () => {
    setIsListening(true);
    if ( browserMediaService ) {
      browserMediaService.startAudio({ audio: true, video: false })
        .then((userMediaStream: any) => {
          const now = new Date();
          audioService.streamAudioToWebSocket(userMediaStream);
        });
    }
  };

  const stopAudio = () => {
    setIsListening(false);
    audioService.closeSocket();
  };

  return (
    <Container className="AudioButtons">
      <Button className="start" ariaLabel="start" onClick={startAudio} disabled={isListening}>Empezar clase</Button>
      <Button className="stop" ariaLabel="stop" onClick={stopAudio} disabled={!isListening}>Parar clase</Button>
    </Container>
  );
};

AudioButtons.displayName = 'AudioButtons';

A la hora de crear los componentes hemos decidido utilizar Atomic Design ya que de esta forma podemos realizar un cambio de React a React Native modificando el menor número de líneas de código, puesto que solo sería necesario modificar los componentes creados como átomos.

Algo que destacaríamos de testing-library es la dificultad que tenemos los desarrolladores de adoptar el rol de usuario a la hora de hacer los tests y no testear con el conocimiento que ya tenemos como desarrolladores.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import * as React from 'react';
import { render } from '@testing-library/react';
import { AudioButtons } from './';
import { AudioService } from '../../services/AudioService';
import { BrowserMediaService } from '../../services/BrowserMediaService';

const browserMediaServiceMock: BrowserMediaService = {
  startAudio: jest.fn(() => Promise.resolve()),
};

// @ts-ignore
const audioServiceMock: AudioService = {
  streamAudioToWebSocket: jest.fn(),
};

const renderAudioButton = () => {
  const utils = render(
    <AudioButtons audioService={audioServiceMock} browserMediaService={browserMediaServiceMock}/>);
  const buttonStart = utils.getByLabelText('start');
  const buttonStop = utils.getByLabelText('stop');
  return { buttonStart, buttonStop, ...utils };
};

describe('AudioButtons', () => {
  test('should render a enabled start button and a disabled stop button', () => {
    const { buttonStart, buttonStop } = renderAudioButton();

    expect(buttonStart).not.toHaveAttribute('disabled');
    expect(buttonStop).toHaveAttribute('disabled');
  });

  test('should change to disable start button when clicking in start', () => {
    const { buttonStart, buttonStop } = renderAudioButton();
    buttonStart.click();
    expect(buttonStart).toHaveAttribute('disabled');
    expect(buttonStop).not.toHaveAttribute('disabled');
  });
});

A la hora de realizar los tests nos dimos cuenta que no habíamos implementado la lógica de habilitar y deshabilitar los botones. Por lo que en ese momento aplicamos TDD para añadir esta lógica. De modo que creamos el segundo test, en el cual, como usuarios, al acceder la primera vez a la página, vemos un botón start habilitado y un bóton stop dehabilitado. En este momento el test estaba en rojo, ya que no teníamos el atributo disabled contemplado en la lógica del componente. Añadimos un nuevo estado a nuestro componente, llamado isListening, el cual indicará al componenete Button si debe estar habilitado o no.

Hemos usado un estado para indicar el momento de la acción en la que nos encontramos, de manera que si ya hemos empezado la clase, no podamos iniciar otra puesto que no tendría sentido. Del mismo modo que no deberíamos poder terminar una clase sin haberla empezado. En cuanto a por qué usar un estado y no una propiedad en el componente AudioButtons, se debe a que este se modifica siempre dentro del propio componente.

Para finalizar comprobamos que al pulsar el botón start el comportamiento que espera el usuario es el correcto.

Por otra parte, aunque sabemos que no es buena practica, y no sería la versión final del producto, hemos decido hacer la llamada al servicio de AWS Transcribe desde el frontend, sacrificando la seguridad a favor de mejorar la latencia y reducir el tiempo de desarrollo para el MVP. A futuro habrá que buscar una alternativa para evitar el acceso a estas credenciales, a poder ser, sin poner en riesgo la baja latencia. Esto es deuda técnica planificada para obtener el MVP en el menor tiempo posible.

En el backend hemos usado el framework SpringBoot con Java haciendo uso de WebSockets debido a la necesidad de distribuir el texto de la transcripción, a los distintos alumnos, según lo recibimos de AWS. Para ello hemos utilizado el ejemplo que nos proporciona el framework, por lo que ha quedado bastante sencillo. El código está disponible en Github.

Conclusiones

Creemos que la clave de este MVP, ha sido focalizar los esfuerzos en conseguir lo que considerábamos la parte esencial del proyecto, en nuestro caso, tal y como hemos comentado, la transcripción a texto. Esta manera de enfocarlo nos ha permitido, en una semana y media, hacer pruebas con usuarios reales, hecho que consideramos clave para la viabilidad del mismo.

Cabe destacar también la importancia de saber asumir el rol de usuario a la hora de hacer los tests de los componentes, focalizando así el desarrollo en lo que el usuario necesita y no en como se ha implementado la solución.

¿Quieres más? te invitamos a suscribirte a nuestro boletín para avisarte cada vez que recopilemos contenido de calidad que compartir.

Si disfrutas leyendo nuestro blog, ¿imaginas lo divertido que sería trabajar con nosotros? ¿te gustaría?