Desarrollo de JavaScript basado en pruebas en la práctica

TDD es un proceso de desarrollo iterativo en el que cada iteración comienza escribiendo una prueba que forma parte de la especificación que estamos implementando. Las iteraciones breves permiten una retroalimentación más instantánea sobre el código que estamos escribiendo, y las malas decisiones de diseño son más fáciles de detectar. Al escribir las pruebas antes de cualquier código de producción, una buena cobertura de prueba unitaria viene con el territorio, pero eso es simplemente un efecto secundario bienvenido.

Tutorial republicado

Cada pocas semanas, revisamos algunas de las publicaciones favoritas de nuestros lectores de toda la historia del sitio. Este tutorial fue publicado por primera vez en noviembre de 2010..


Volviendo el desarrollo al revés

En la programación tradicional, los problemas se resuelven mediante la programación hasta que un concepto está completamente representado en código. Idealmente, el código sigue algunas consideraciones generales de diseño arquitectónico, aunque en muchos casos, tal vez especialmente en el mundo de JavaScript, este no es el caso. Este estilo de programación resuelve problemas al adivinar qué código se requiere para resolverlos, una estrategia que puede llevar fácilmente a soluciones infladas y estrechamente acopladas. Si tampoco hay pruebas unitarias, las soluciones producidas con este enfoque pueden incluso contener código que nunca se ejecuta, como la lógica de manejo de errores y el manejo de argumentos "flexibles", o puede contener casos de borde que no se han probado a fondo, si se probaron en absoluto.

El desarrollo guiado por pruebas invierte el ciclo de desarrollo. En lugar de centrarse en qué código se requiere para resolver un problema, el desarrollo guiado por pruebas comienza definiendo el objetivo. Las pruebas unitarias forman tanto la especificación como la documentación de las acciones que son compatibles y se tienen en cuenta. Por supuesto, el objetivo de TDD no es probar, por lo que no hay garantía de que lo maneje, por ejemplo. casos de borde mejor. Sin embargo, dado que cada línea de código se prueba con una pieza representativa del código de muestra, es probable que TDD produzca menos código en exceso y que la funcionalidad que se cuenta sea más robusta. El desarrollo adecuado basado en pruebas asegura que un sistema nunca contendrá código que no se está ejecutando.


El proceso

El proceso de desarrollo dirigido por pruebas es un proceso iterativo en el que cada iteración consta de los siguientes cuatro pasos:

  • Escribe una prueba
  • Ejecutar pruebas, ver fallar la nueva prueba
  • Hacer pasar la prueba
  • Refactor para eliminar la duplicación

En cada iteración, la prueba es la especificación. Una vez que se haya escrito suficiente código de producción (y no más) para hacer que la prueba pase, hemos terminado, y podemos refactorizar el código para eliminar la duplicación y / o mejorar el diseño, siempre y cuando las pruebas aún se aprueben..


TDD práctica: El patrón observador

El patrón Observer (también conocido como Publicar / Suscribir, o simplemente pubsub) es un patrón de diseño que nos permite observar el estado de un objeto y ser notificado cuando cambia. El patrón puede proporcionar objetos con puntos de extensión potentes al tiempo que mantiene el acoplamiento suelto.

Hay dos roles en el observador: observable y observador. El observador es un objeto o función que se notificará cuando cambie el estado de lo observable. El observable decide cuándo actualizar a sus observadores y qué datos proporcionarles. Lo observable típicamente proporciona al menos dos métodos públicos: pubsub, que notifica a sus observadores de nuevos datos, y pubsub Que suscribe observadores a los eventos..


La biblioteca observable

El desarrollo guiado por pruebas nos permite avanzar en pasos muy pequeños cuando sea necesario. En este primer ejemplo del mundo real, comenzaremos con los pasos más pequeños. A medida que ganemos confianza en nuestro código y en el proceso, iremos aumentando gradualmente el tamaño de nuestros pasos cuando las circunstancias lo permitan (es decir, el código a implementar es lo suficientemente trivial). Escribir código en pequeñas iteraciones frecuentes nos ayudará a diseñar nuestra API pieza por pieza y nos ayudará a cometer menos errores. Cuando ocurran errores, podremos solucionarlos rápidamente, ya que los errores serán fáciles de rastrear cuando ejecutamos pruebas cada vez que agregamos un puñado de líneas de código..


Configurando el medio ambiente

Este ejemplo utiliza JsTestDriver para ejecutar pruebas. Una guía de configuración está disponible en el sitio web oficial.

El diseño inicial del proyecto es el siguiente:

 chris @ laptop: ~ / projects / observable $ tree. | - jsTestDriver.conf | - src | '- observable.js' - prueba '- observable_test.js

El archivo de configuración es solo el mínimo JsTestDriver configuración:

 servidor: http: // localhost: 4224 carga: - lib / *. js - test / *. js

Añadiendo observadores

Iniciaremos el proyecto implementando un medio para agregar observadores a un objeto. Al hacerlo, nos llevará a escribir la primera prueba, verla fallar, pasarla de la manera más sucia posible y, finalmente, refactorizarla en algo más sensato..


La primera prueba

La primera prueba intentará agregar un observador llamando al addObserver método. Para verificar que esto funciona, seremos contundentes y asumiremos que observables almacena sus observadores en una matriz y verificaremos que el observador sea el único elemento en esa matriz. La prueba pertenece a test / observable_test.js y se parece a lo siguiente:

 TestCase ("ObservableAddObserverTest", "test debería almacenar la función": function () var observable = new tddjs.Observable (); var observer = function () ; observable.addObserver (observador); assertEquals (observador, observable. observadores [0]););

Ejecutar la prueba y verla fallar

A primera vista, el resultado de ejecutar nuestra primera prueba es devastador:

 Total de 1 pruebas (aprobadas: 0; fallas: 0; errores: 1) (0,00 ms) Firefox 3.6.12 Linux: ejecutar 1 pruebas (aprobadas: 0; fallas: 0; errores 1) (0,00 ms) ObservableAddObserverTest.test debe almacenar Error de función (0.00 ms): \ tddjs no está definido /test/observable_test.js:3 Pruebas fallidas.

Haciendo el Pase de Prueba

¡No temáis! El fracaso es en realidad algo bueno: nos dice dónde enfocar nuestros esfuerzos. El primer problema serio es que tddjs no existe. Vamos a agregar el objeto de espacio de nombres en src / observable.js:

 var tddjs = ;

Ejecutar las pruebas de nuevo produce un nuevo error:

 E Total de 1 pruebas (aprobadas: 0; fallas: 0; errores: 1) (0,00 ms) Firefox 3.6.12 Linux: ejecutar 1 pruebas (aprobadas: 0; fallas: 0; errores 1) (0,00 ms) ObservableAddObserverTest.test error de función de almacenamiento (0,00 ms): \ tddjs.Observable no es un constructor /test/observable_test.js:3 Pruebas fallidas.

Podemos solucionar este nuevo problema agregando un constructor de Observable vacío:

 var tddjs = ; (function () function Observable ()  tddjs.Observable = Observable; ());

Ejecutar la prueba una vez más nos lleva directamente al siguiente problema:

 E Total de 1 pruebas (aprobadas: 0; fallas: 0; errores: 1) (0,00 ms) Firefox 3.6.12 Linux: ejecutar 1 pruebas (aprobadas: 0; fallas: 0; errores 1) (0,00 ms) ObservableAddObserverTest.test error de función de almacenamiento (0,00 ms): \ observable.addObserver no es una función /test/observable_test.js:6 Pruebas fallidas.

Añadamos el método que falta..

 function addObserver ()  Observable.prototype.addObserver = addObserver;

Con el método en su lugar, la prueba ahora falla en lugar de una matriz de observadores faltantes.

 E Total de 1 pruebas (aprobadas: 0; fallas: 0; errores: 1) (0,00 ms) Firefox 3.6.12 Linux: ejecutar 1 pruebas (aprobadas: 0; fallas: 0; errores 1) (0,00 ms) ObservableAddObserverTest.test error de función de almacenamiento (0.00 ms): \ observable.observers no está definido /test/observable_test.js:8 Pruebas fallidas.

Por extraño que parezca, ahora definiré la matriz de observadores dentro de la pubsub método. Cuando una prueba falla, TDD nos indica que hagamos la cosa más simple que podría funcionar, sin importar lo sucio que se sienta. Tendremos la oportunidad de revisar nuestro trabajo una vez que la prueba esté pasando.

 función addObserver (observador) this.observers = [observador];  ¡Éxito! La prueba ahora pasa:. Total de 1 pruebas (aprobadas: 1; fallas: 0; errores: 0) (1.00 ms) Firefox 3.6.12 Linux: ejecutar 1 pruebas (aprobadas: 1; fallas: 0; errores 0) (1.00 ms)

Refactorización

Mientras desarrollamos la solución actual, hemos tomado la ruta más rápida posible para pasar una prueba. Ahora que la barra está verde, podemos revisar la solución y realizar cualquier refactorización que consideremos necesaria. La única regla en este último paso es mantener la barra verde. Esto significa que también tendremos que refactorizar en pasos pequeños, asegurándonos de no romper nada accidentalmente..

La implementación actual tiene dos problemas que debemos abordar. La prueba hace suposiciones detalladas sobre la implementación de Observable y la addObserver La implementación está codificada para nuestra prueba..

Vamos a abordar la codificación dura primero. Para exponer la solución codificada, aumentaremos la prueba para que agregue dos observadores en lugar de uno..

 "la prueba debe almacenar la función": function () var observable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (observadores [0]); observable.addObserver (observadores [1]); assertEquals (observadores, observadores.observadores); 

Como era de esperar, la prueba ahora falla. La prueba espera que las funciones agregadas como observadores se apilen como cualquier elemento agregado a un pubsub. Para lograr esto, moveremos la instanciación de matriz al constructor y simplemente delegaremos addObserver al formación método de empuje

 función Observable () this.observers = [];  función addObserver (observador) this.observers.push (observador); 

Con esta implementación en su lugar, la prueba pasa otra vez, lo que demuestra que nos hemos ocupado de la solución codificada. Sin embargo, el problema de acceder a una propiedad pública y hacer suposiciones descabelladas sobre la implementación de Observable sigue siendo un problema. Un observable pubsub debe ser observable por cualquier número de objetos, pero no es de interés para los forasteros cómo o dónde el observable los almacena. Idealmente, nos gustaría poder verificar con lo observable si un determinado observador está registrado sin tantear en su interior. Tomamos nota del olor y seguimos adelante. Más tarde, volveremos para mejorar esta prueba..


Comprobación de observadores

Añadiremos otro método a Observable., hasObserver, y úselo para eliminar parte del desorden que agregamos al implementar addObserver.


La prueba

Un nuevo método comienza con una nueva prueba, y el siguiente comportamiento deseado para el hasObserver método.

 TestCase ("ObservableHasObserverTest", "la prueba debería devolver true cuando tiene observador": function () var observable = new tddjs.Observable (); var observer = function () ; observable.addObserver (observer); assertTrue (observable .hasObserver (observador)));

Esperamos que esta prueba falle ante un desaparecido. hasObserver, lo que hace.


Haciendo el Pase de Prueba

Nuevamente, empleamos la solución más simple que posiblemente podría pasar la prueba actual:

 función hasObserver (observador) return true;  Observable.prototype.hasObserver = hasObserver;

Aunque sabemos que esto no solucionará nuestros problemas a largo plazo, mantiene las pruebas en verde. Tratar de revisar y refactorizar nos deja con las manos vacías, ya que no hay puntos obvios en los que podamos mejorar. Las pruebas son nuestros requisitos, y actualmente solo requieren hasObserver para devolver la verdad. Para solucionarlo introduciremos otra prueba que espera. hasObserver a falso retorno para un observador inexistente, lo que puede ayudar a forzar la solución real.

 "la prueba debe devolver falso cuando no hay observadores": function () var observable = new tddjs.Observable (); assertFalse (observable.hasObserver (function () )); 

Esta prueba falla miserablemente, dado que hasObserver siempre devuelve verdadero, obligándonos a producir la implementación real. Verificar si un observador está registrado es una simple cuestión de verificar que la matriz this.observers contiene el objeto que se pasó originalmente a addObserver:

 función hasObserver (observador) return this.observers.indexOf (observador)> = 0; 

los Array.prototype.indexOf método devuelve un número menor que 0 Si el elemento no está presente en el formación, así que comprobando que devuelve un número igual o mayor que 0 nos dirá si el observador existe.


Resolviendo incompatibilidades del navegador

La ejecución de la prueba en más de un navegador produce resultados algo sorprendentes:

 chris @ laptop: ~ / projects / observable $ jstestdriver --tests all… E Total de 4 pruebas (Aprobado: 3; Fallos: 0; Errores: 1) (11.00 ms) Firefox 3.6.12 Linux: Ejecutar 2 pruebas (Aprobado: 2 ; Falla: 0; Errores 0) (2.00 ms) Microsoft Internet Explorer 6.0 Windows: Ejecutar 2 pruebas \ (Aprobado: 1; Falla: 0; Errores 1) (0.00 ms) ObservableHasObserverTest.test debe devolver verdadero cuando tiene un error de observador \ ( 0.00 ms): el objeto no admite esta propiedad o método. Pruebas fallidas.

Las versiones 6 y 7 de Internet Explorer fallaron la prueba con sus mensajes de error más genéricos: "El objeto no soporta esta propiedad o método ". Esto puede indicar cualquier número de problemas:

  • Estamos llamando a un método en un objeto que es nulo.
  • Estamos llamando a un método que no existe.
  • Estamos accediendo a una propiedad que no existe.

Afortunadamente, TDD-ing en pasos pequeños, sabemos que el error tiene que relacionarse con la llamada recientemente agregada a índice de en nuestros observadores formación. Como resultado, IE 6 y 7 no son compatibles con el método JavaScript 1.6 Array.prototype.indexOf (por lo que realmente no podemos culparlo, fue recientemente estandarizado con ECMAScript 5, diciembre 2009). En este punto, tenemos tres opciones:

  • Evite el uso de Array.prototype.indexOf en hasObserver, duplicando efectivamente la funcionalidad nativa en los navegadores compatibles.
  • Implemente Array.prototype.indexOf para los navegadores no compatibles. Alternativamente, implemente una función auxiliar que proporcione la misma funcionalidad..
  • Utilice una biblioteca de terceros que proporcione el método faltante o un método similar.

Cuál de estos enfoques es el más adecuado para resolver un problema determinado dependerá de la situación: todos tienen sus pros y sus contras. Con el fin de mantener observable a sí mismo, simplemente implementaremos hasObserver en términos de un bucle en lugar de la índice de llamada, efectivamente trabajando en torno al problema. Por cierto, eso también parece ser lo más simple que podría funcionar en este punto. Si nos encontramos con una situación similar más adelante, se nos recomendaría que reconsideremos nuestra decisión. El actualizado hasObserver se ve de la siguiente manera:

 función hasObserver (observador) para (var i = 0, l = this.observers.length; i < l; i++)  if (this.observers[i] == observer)  return true;   return false; 

Refactorización

Con la barra de vuelta a verde, es hora de revisar nuestro progreso. Ahora tenemos tres pruebas, pero dos de ellas parecen extrañamente similares. La primera prueba que escribimos para verificar la corrección de addObserver Básicamente pruebas para las mismas cosas que la prueba que escribimos para verificar Refactorización . Hay dos diferencias clave entre las dos pruebas: la primera prueba previamente se ha declarado maloliente, ya que accede directamente a la matriz de observadores dentro del objeto observable. La primera prueba agrega dos observadores, asegurando que ambos se agreguen. Ahora podemos unir las pruebas en una que verifique que todos los observadores agregados al observable en realidad se agreguen:

 "la prueba debe almacenar funciones": function () var observable = new tddjs.Observable (); var observers = [function () , function () ]; observable.addObserver (observadores [0]); observable.addObserver (observadores [1]); assertTrue (observable.hasObserver (observadores [0])); assertTrue (observable.hasObserver (observadores [1])); 

Notificando observadores

Agregar observadores y verificar su existencia es bueno, pero sin la capacidad de notificarles los cambios interesantes, Observable no es muy útil. Es hora de implementar el método de notificación..


Asegurando que los observadores sean llamados

La tarea más importante que realiza la notificación es llamar a todos los observadores. Para hacer esto, necesitamos alguna forma de verificar que un observador ha sido llamado después del hecho. Para verificar que se ha llamado a una función, podemos establecer una propiedad en la función cuando se llama. Para verificar la prueba podemos verificar si la propiedad está establecida. La siguiente prueba usa este concepto en la primera prueba para notificar.

 TestCase ("ObservableNotifyTest", "la prueba debe llamar a todos los observadores": function () var observable = new tddjs.Observable (); var observer1 = function () observer1.called = true;; var observer2 = function () observer2.called = true;; observable.addObserver (observer1); observable.addObserver (observer2); observable.notify (); assertTrue (observer1.called); assertTrue (observer2.called););

Para pasar la prueba necesitamos hacer un bucle en la matriz de observadores y llamar a cada función:

 función notificar () para (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i]();   Observable.prototype.notify = notify;

Pasando Argumentos

Actualmente se llama a los observadores, pero no se les alimenta ningún dato. Saben que algo sucedió, pero no necesariamente qué. Haremos que notifiquemos cualquier número de argumentos, simplemente pasándolos a cada observador:

 "la prueba debe pasar por los argumentos": function () var observable = new tddjs.Observable (); var actual observable.addObserver (function () actual = argumentos;); observable.notify ("String", 1, 32); assertEquals (["String", 1, 32], actual); 

La prueba compara los argumentos recibidos y pasados ​​asignando los argumentos recibidos a una variable local de la prueba. El observador que acabamos de crear es, de hecho, un espía de prueba manual muy simple. La ejecución de la prueba confirma que falla, lo cual no es sorprendente, ya que actualmente no estamos tocando los argumentos que se encuentran dentro de notificar.

Para pasar la prueba podemos usar aplicar al llamar al observador:

 función notificar () para (var i = 0, l = this.observers.length; i < l; i++)  this.observers[i].apply(this, arguments);  

Con esta simple corrección de pruebas volver a verde. Tenga en cuenta que enviamos esto como el primer argumento a aplicar, lo que significa que se llamará a los observadores con lo observable, ya que.


Manejo de errores

En este punto, Observable es funcional y tenemos pruebas que verifican su comportamiento. Sin embargo, las pruebas solo verifican que los observables se comportan correctamente en respuesta a la entrada esperada. ¿Qué sucede si alguien intenta registrar un objeto como un observador en lugar de una función? ¿Qué pasa si uno de los observadores explota? Esas son preguntas que necesitamos nuestras pruebas para responder. Asegurar el comportamiento correcto en las situaciones esperadas es importante: eso es lo que nuestros objetos estarán haciendo la mayor parte del tiempo. Al menos así podríamos esperar. Sin embargo, el comportamiento correcto incluso cuando el cliente se está portando mal es tan importante como para garantizar un sistema estable y predecible..


Añadiendo observadores falsos

La implementación actual acepta ciegamente cualquier tipo de argumento para addObserver. Aunque nuestra implementación puede usar cualquier función como observador, no puede manejar ningún valor. La siguiente prueba espera que el observable lance una excepción cuando intente agregar un observador que no sea llamable.

 "la prueba debe arrojar para el observador que no se puede cantar": function () var observable = new tddjs.Observable (); assertException (function () observable.addObserver ();, "TypeError"); 

Al lanzar una excepción al agregar los observadores, no debemos preocuparnos por los datos no válidos más adelante cuando notifiquemos a los observadores. Si hubiéramos programado por contrato, podríamos decir que una condición previa para la addObserver El método es que la entrada debe ser invocable. los postcondición es que el observador se agrega al observable y se garantiza que se llamará una vez que las llamadas observables notifiquen.

La prueba falla, por lo que cambiamos nuestro enfoque para que la barra vuelva a ser verde lo más rápido posible. Desafortunadamente, no hay manera de falsificar la implementación de esto: lanzar una excepción en cualquier llamada a addObserver fallarán todas las otras pruebas. Afortunadamente, la implementación es bastante trivial:

 function addObserver (observador) if (typeof observer! = "function") lanza un nuevo TypeError ("observador no es función");  this.observers.push (observador); 

addObserver ahora comprueba que el observador es de hecho una función antes de agregarla a la lista. La ejecución de las pruebas produce esa dulce sensación de éxito: todo verde.


Observadores que se portan mal

Lo observable ahora garantiza que cualquier observador agregado a través de addObserver es llamable Aún así, notificar puede fallar horriblemente si un observador lanza una excepción. La siguiente prueba espera que se llame a todos los observadores incluso si uno de ellos lanza una excepción.

 "la prueba debe notificar a todos, incluso cuando algunos fallan": function () var observable = new tddjs.Observable (); var observer1 = function () lanza un nuevo error ("Oops"); ; var observer2 = function () observer2.called = true; ; observable.addObserver (observador1); observable.addObserver (observador2); observable.notificar (); assertTrue (observer2.called); 

La ejecución de la prueba revela que la implementación actual explota junto con el primer observador, lo que hace que no se llame al segundo observador. En efecto, notificar está rompiendo su garantía de que siempre llamará a todos los observadores una vez que se hayan agregado correctamente. Para rectificar la situación, el método debe estar preparado para lo peor:

 función notificar () para (var i = 0, l = this.observers.length; i < l; i++)  try  this.observers[i].apply(this, arguments);  catch (e)   

La excepción se descarta silenciosamente. Es responsabilidad del observador asegurarse de que los errores se manejen correctamente, lo observable es simplemente eludir a los observadores que se comportan mal..


Documentar orden de llamada

Hemos mejorado la robustez del módulo Observable dándole un manejo adecuado de los errores. El módulo ahora puede ofrecer garantías de funcionamiento siempre que obtenga una buena entrada y se pueda recuperar si un observador no cumple con sus requisitos. Sin embargo, la última prueba que agregamos hace una suposición sobre las características no documentadas de lo observable: se supone que se llama a los observadores en el orden en que se agregaron. Actualmente, esta solución funciona porque usamos una matriz para implementar la lista de observadores. Sin embargo, si decidimos cambiar esto, nuestras pruebas pueden romperse. Entonces, debemos decidir: ¿Refactorizamos la prueba para que no asumamos el orden de las llamadas o simplemente agregamos una prueba que espera la orden de las llamadas, documentando así la orden de las llamadas como una característica? El orden de las llamadas parece una característica sensata, por lo que nuestra próxima prueba se asegurará de que Observable mantenga este comportamiento.

 "la prueba debe llamar a los observadores en el orden en que se agregaron": function () var observable = new tddjs.Observable (); llamadas var = []; var observer1 = function () calls.push (observer1); ; var observer2 = function () calls.push (observer2); ; observable.addObserver (observador1); observable.addObserver (observador2); observable.notificar (); assertEquals (observador1, llamadas [0]); assertEquals (observador2, llamadas [1]); 

Dado que la implementación ya utiliza una matriz para los observadores, esta prueba se realiza de inmediato.


Observando objetos arbitrarios

En lenguajes estáticos con herencia clásica, los objetos arbitrarios se hacen observables por subclasificación La clase observable. La motivación para la herencia clásica en estos casos proviene del deseo de definir la mecánica del patrón en un lugar y reutilizar la lógica en vastas cantidades de objetos no relacionados. En JavaScript, tenemos varias opciones para la reutilización de código entre objetos, por lo que no debemos limitarnos a una emulación del modelo de herencia clásico..

Con el fin de liberarse de la emulación clásica que brindan los constructores, considere los siguientes ejemplos que asumen que tddjs.observable es un objeto en lugar de un constructor:

Nota la tddjs.extend El método se introduce en otra parte del libro y simplemente copia las propiedades de un objeto a otro..

 // Creando un solo objeto observable var observable = Object.create (tddjs.util.observable); // Extendiendo un solo objeto tddjs.extend (newspaper, tddjs.util.observable); // Un constructor que crea la función de objetos observables Newspaper () / *… * / Newspaper.prototype = Object.create (tddjs.util.observable); // Extendiendo un prototipo existente tddjs.extend (Newspaper.prototype, tddjs.util.observable);

La simple implementación de lo observable como un solo objeto ofrece una gran flexibilidad. Para llegar allí necesitamos refactorizar la solución existente para deshacernos del constructor.


Haciendo el constructor obsoleto

Para deshacernos del constructor, primero debemos refactorar Observable de modo que el constructor no haga ningún trabajo. Afortunadamente, el constructor solo inicializa la matriz de observadores, que no debería ser demasiado difícil de eliminar. Todos los métodos en Observable.prototype acceden a la matriz, por lo que debemos asegurarnos de que todos puedan manejar el caso en el que no se haya inicializado. Para probar esto, simplemente necesitamos escribir una prueba por método que llame al método en cuestión antes de hacer cualquier otra cosa..

Como ya tenemos pruebas que llaman. addObserver y hasObserver Antes de hacer cualquier otra cosa, nos concentraremos en el método de notificación. Este método solo se prueba después addObserver ha sido llamado. Nuestras próximas pruebas esperan que sea posible llamar a este método antes de agregar observadores.

 "la prueba no debería fallar si no hay observadores": function () var observable = new tddjs.Observable (); assertNoException (function () observable.notify ();); 

Con esta prueba en su lugar podemos vaciar el constructor:

 función observable () 

La ejecución de las pruebas muestra que todos menos uno fallan ahora, todos con el mismo mensaje: "this.observers no está definido". Trataremos un método a la vez. Primero es addObserver método:

función addObserver (observador)
if (! this.observers)
this.observers = [];

/ *… * /

Ejecutando las pruebas de nuevo revela que la actualización addObserver El método corrige todas las pruebas excepto las dos, que no se inicia al llamarlo. A continuación, nos aseguramos de devolver falso directamente desde hasObserver si la matriz no existe.

 función hasObserver (observador) si (! this.observers) return false;  / *… * /

Podemos aplicar la misma solución exacta para notificar:

 la función notificar (observador) si (! this.observers) return;  / *… * /

Reemplazo del constructor con un objeto

Ahora que el constructor No hace nada, puede ser eliminado de forma segura. Luego añadiremos todos los métodos directamente a la tddjs.observable objeto, que luego se puede utilizar con, por ejemplo, Object.create o tddjs.extend Para crear objetos observables. Tenga en cuenta que el nombre ya no está en mayúsculas, ya que ya no es un constructor. La implementación actualizada sigue:

 (function () function addObserver (observador) / *… * / función hasObserver (observador) / *… * / función de notificación () / *… * / tddjs.observable = addObserver: addObserver, hasObserver : hasObserver, notificar: notificar; ());

Seguramente, eliminar el constructor hace que todas las pruebas hasta el momento se rompan. Sin embargo, arreglarlos es fácil. Todo lo que necesitamos hacer es reemplazar la nueva declaración con una llamada a Object.create. Sin embargo, la mayoría de los navegadores no son compatibles Object.create sin embargo, para que podamos calmarlo. Debido a que el método no es posible emular perfectamente, proporcionaremos nuestra propia versión en el tddjs objeto:

 (function () function F ()  tddjs.create = function (object) F.prototype = object; devolver nuevo F ();; / * La implementación observable va aquí ... * / ());

Con el shim en su lugar, podemos actualizar las pruebas en una materia que funcionará incluso en navegadores antiguos. La prueba final sigue:

 TestCase ("ObservableAddObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test debería almacenar funciones": function () var observers = [function () , function () ]; this.observable.addObserver (observers [0]); this.observable.addObserver (observers [1]); assertTrue (this.observable.hasObserver (observers [0])); assertTrue (this.observable .hasObserver (observadores [1]));); TestCase ("ObservableHasObserverTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "test debería devolver false cuando no haya observadores": function () assertFalse (this.observable.hasObserver ( función () ));); TestCase ("ObservableNotifyTest", setUp: function () this.observable = tddjs.create (tddjs.observable);, "la prueba debe llamar a todos los observadores": function () var observer1 = function () observer1.called = verdadero;; var observer2 = function () observer2.called = true;; this.observable.addObserver (observer1); this.observable.addObserver (observer2); this.obse