keyboard_arrow_left

Double tests: Dummies, mocks, spies, fakes y stubs… en JavaScript

El artículo de referencia sobre qué son mocks y stubs, lo escribió Martin Fowler en enero de 2007 y lo tradujo estupendamente al castellano Carlos Ble en julio del 2008. Aunque los ejemplos estén en Java, el artículo tienen sentido en cualquier lenguaje orientado a objetos, y conocer bien estos conceptos nos ayudará a la hora de hacer test también en JavaScript.

Los programadores de backend están muy familiarizados con los conceptos de mock y stub, sin embargo en la parte cliente, todavía es una práctica reciente que no está muy extendida.

Antes de empezar a poner ejemplos de estos objetos en JavaScript, hablaremos de los “Double Test”, que es la forma general de referirnos a objetos que imitan a otro real. El propósito principal de estos objetos es el aislamiento. El aislamiento de un trozo de código no es fácil de conseguir, en el mundo real el código tiene llamadas a colaboradores, y para romper estas dependencias, creamos objetos falsos. Estos objetos falsos nos ayudan a predeterminar cómo se va a comportar el objeto cuando lo utilicemos: devolveremos un valor fijo, o se simulará una funcionalidad. Establecer las condiciones que necesito me ayudará a saber si el objeto que quiero probar se está comportando de una manera correcta.

Podemos diferenciar cuatro tipos de “double test” (a veces traducido como “dobles de prueba”. La diferencia entre uno otro es cómo verificamos el resultado de esas pruebas: un mock sólo verifica el comportamiento del objeto. Si lo que necesitamos es verificar el estado del objeto al final del test, usaremos un stub. Esto también influye en la complejidad, o en la cantidad de código que tenemos que ponerle al objeto.

Veamos un ejemplo de cada uno de ellos:

Dummy: es un objeto doble que no tiene comportamiento, que reemplaza un colaborador pero que no lo vamos a usar durante la especificación. Por ejemplo en un constructor, o un objeto que pasas como parámetro para no pasarle un “null” desde una prueba.

Por ejemplo, imaginemos que tenemos un objeto “Agenda” y queremos probar un método “addMeeting”.

function Agenda() {
this.meetings = new Array();

this.addMeeting = function(meeting) {
this.meetings.push(meeting);
    }
};

El objeto dummy puede estar vacío, o puede inicializarse, si por ejemplo la función addMeeting tuviera algún validador que comprobara que una reunión tuviera una serie de propiedades y que estuvieran informadas.

En nuestro test queremos comprobar si efectivamente añade un objeto a la variable meetings, aunque no entraremos los detalles de la reunión.

test("AddMeetingTest", function () {

var dummy_meeting = {};

var agenda = new Agenda();
    agenda.addMeeting(dummy_meeting);

    equal(agenda.meetings.length, 1, "Meeting added");
});

 

Stubs: son objetos que proporciona valores de entrada al método que estamos probando, ya sean valores determinados, excepciones, etc. Se trata de devolver respuestas específicas predefinidas sin importar las condiciones en las que los estamos llamando. Normalmente este objeto se llama para probar un test positivo, un test negativo y un test para los valores inesperados.

Por ejemplo, complementamos el objeto Agenda, de forma que realiza una validación antes de añadir una reunión: si la reunión tiene lugar en el futuro sí que podremos programarla, si es anterior a hoy, no la añadirá:

function Agenda() {
this.meetings = new Array();

this.addMeeting = function(meeting) {
var added = false;
if (meeting.isFuture()) {
this.meetings.push(meeting);
            added = true;
        }
return added;
    }
};

Ahora queremos probar que nuestro método es correcto, tanto cuando añade la reunión, como cuando no. El test positivo sería el siguiente:

QUnit.test("AddFutureMeeting", function () {

varstubMeeting = {
        isFuture: function() { returntrue; }
    };

var agenda = new Agenda();
var added = agenda.addMeeting(stubMeeting);

    equal(agenda.meetings.length, 1, "Meeting added");
    QUnit.ok(added, "Future meeting added");

});

La clase Meeting real comparará la fecha de la reunión con el momento actual. Pero de esta forma, sea cual sea el contexto (fecha actual) en el que estemos llamando a la función, la respuesta será la esperada.
Este sería el test negativo:

QUnit.test("AddPastMeeting", function () {

var stubMeeting = {
        isFuture: function () { returnfalse; }
    };

var agenda = new Agenda();
var added = agenda.addMeeting(stubMeeting);

    equal(agenda.meetings.length, 0, "Meeting not added");
    QUnit.ok(!added, "Past meeting not added");

});

 

Fakes: objetos que aparentan un funcionamiento correcto, una implementación que funciona, alternativa a la original. Aunque esta implementación está simplificada, por lo que no pueden ser usados en producción. No sólo devuelven siempre los mismos valores, de hecho funciona como un colaborador real lo haría: por ejemplo, en vez de guardar las reuniones haciendo una llamada al backend, se recogen de un array en memoria, pero de igual modo se almacenan. Se diferencian con el mock (que veremos a continuación) en que el test no tiene control sobre estos, y se diferencian de los stubs en que estos últimos son creados e inyectados en el sistema para test individuales para una necesidad básica, pero los fakes son más completos, añadidos en el sistema antes de ejecutar las pruebas, ya que posiblemente se utilicen en más de una, esto los hace un poco más independientes del test en sí.

Siguiendo el ejemplo que ya lo hemos utilizado anteriormente. Si quisiéramos inicializar en la agenda las reuniones que ya hay guardadas en base de datos, podríamos extender la clase Agenda añadiéndole un método loadMeetings que añada las reuniones que inicialmente haya guardadas en la agenda.

Para comprobar que loadMeetings funciona, hacemos un test, que recuperará las reuniones y las cargará.

QUnit.test("Load Meetings", function (assert) {
var done = assert.async(); //asíncrona
var agenda = new Agenda();
var loaded = false;


loaded = agenda.loadMeetings(MeetingManager.getAllMeetings);

    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 3, "Three meetings loaded");
        done();
    });


});

 

La función MeetingManager.getAllMeetings() en principio hace una llamada AJAX y recupera las reuniones.

var MeetingManager = {
    getAllMeetings: function (callback) {
var backendUrl = 'http://localhost:31938';
var uri = '/api/meetings';

// Send an AJAX request
        $.getJSON(backendUrl + uri)
            .done(function (json) {
if ((typeof callback) === 'function') {
                    callback(json);
                }
            }).fail(function (jqXHR, textStatus, errorThrown) {
if ((typeof callback) === 'function') {
                    callback(jqXHR, new Error(errorThrown));
                }
            });
    }
};

Pero tenemos dos problemas 1) ralentización de los test (puede tardar varios segundos más en hacer la llamada) lo que provoca que no se lancen tan continuamente como debería 2) no siempre, en todo el desarrollo de la aplicación podemos asegurar que haya tres reuniones en la base de datos.
Así que lo solucionamos haciendo un objeto fake que sustituya a getAllMeetings y que nos devuelva unas reuniones prefijadas:

getAllMeetings_fake: function(callback) {
   var json = "[{\"Id\":1,\"Name\":\"Pre Christmas Lunch with
Friends\",\"Date\":\"2014-12-23T12:00:00\"},{\"Id\":2,\"Name\":\"Christmas Dinner\",\"Date\":\"2014-12-24T22:30:00\"},{\"Id\":3,\"Name\":\"Christmas
	Launch\",\"Date\":\"2014-12-25T13:45:00\"}]";
if ((typeof callback) === 'function') {
            callback(JSON.parse(json));
        }
    }

Y en el test sólo he cambiado la llamada:
QUnit.test("Load Meetings", function (assert) {
var done = assert.async();
var agenda = new Agenda();
var loaded = false;


    agenda.loadMeetings(MeetingManager.getAllMeetings_fake());


    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 3, "Three meetings loaded");
        done();
    });

});

Aunque ahora sí que es síncrono, no he cambiado ese trozo de código.
Podemos ver que el tiempo se ha reducido notablemente:

En este ejemplo, hemos “falsificado” una llamada a Web API, pero también es muy común hacerlo con acceso a base de datos (en el caso de código en servidor), o crear una base de datos en memoria, o la capa de servicios (como en una interfaz de usuario, para aislarnos de los datos o del comportamiento).

 

Mocks: objetos preprogramados que se usan para comprobar que se ejecuta correctamente una llamada externa a una API, pero sin llegar a hacerla. Los test comprueban que la llamada recibe los parámetros correctos añadiéndole más condiciones o validadores para comprobar la correcta utilización de la clase y aportando un juego de pruebas más completo.

Se parece al stub a que se “intercepta” y reemplaza un objeto/llamada alternando el flujo natural de la aplicación. La diferencia es que el stub siempre devuelve una respuesta “fija” y el mock imita de una forma más completa el comportamiento del objeto, proporcionando una respuesta similar al que éste daría.

La respuesta del mock no se verifica con un estado, sino con un comportamiento.

En la web de xUnitPatterns hay una tabla en la que podemos observar muy visualmente la diferencia entre fakes, mock y stubs. También podemos ver, que cada autor de los libros de referencia de testing hacen pequeñas variaciones en la terminología y en la descripción de los mismos.

http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html
Los que podemos ver es que las diferencias más notables es que mientras que Fake no recoge entradas del objeto que se está probando, el mock sí que podría recogerlas, y usarlas como parte de su comportamiento. Además se encargará de verificar estas entradas

Pattern Purpose Has Behavior Injects indirect inputs into SUT Handles indirect outputs of SUT Values provided by test(er)
Mock Object Verify indirect outputs of SUT yes Optional verifies correctness against expectations outputs & inputs (optional)
Fake Object Run (unrunnable) tests (faster) yes no uses them none

 

Como ejemplo, hemos ampliado el anterior stub, pero haciendo que los datos devueltos por el mock sean restringidos por una entrada proporcionada: sólo devolverá las reuniones que están fijadas el día que se le pase por parámetro:

QUnit.test("Load Meetings", function (assert) {
var done = assert.async();
var agenda = new Agenda();
var loaded = false;

    varday = new Date("December 25, 2014");
    agenda.loadMeetings(getMeetings_mock, day);




    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 1, "One meeting 25/12/2014");
        QUnit.equal(agenda.meetings[0].Date.getDate(), 25, "Meeting returned 25th");
        QUnit.equal(agenda.meetings[0].Date.getMonth(), 11, "Meeting returned Dec");
        QUnit.equal(agenda.meetings[0].Date.getFullYear(), 2014, "Meeting returned 2014");
        done();
    });

});

El Mock simplemente filtrará el anterior JSON:

getMeetings_mock: function(date, callback) {
var json = JSON.parse(...);
var selectedMeetings = [];
        json.forEach(function (meeting) {
            meeting.Date = new Date(meeting.Date);
if (meeting.Date.getDate() == date.getDate() &&
                meeting.Date.getMonth() == date.getMonth() &&
                meeting.Date.getYear() == date.getYear()) {
                selectedMeetings.push(meeting);
            }
        });
if ((typeof callback) === 'function') {
            callback(selectedMeetings);
        }
    }

La diferencia es tan sutil que puede crear confusión, en realidad, el mock no se convierte en un mock hasta la verificación final, con el stub sólo comprobamos el estado del objeto, pero en el mock se comprueban el orden y el que los resultados sí que sean correctos.

 

Spies: son objetos muy parecidos a los stubs, pero que además de cumplir su función, almacenan información sobre qué métodos son llamados, o el número de veces que se utilizan. Para comprobar que un fragmento de código pasa (o llama) a una función. El spy no interfiere en el resultado de la llamada, sólo se limita a hacer un registro. El error de la verificación se dará si la función no existe, o no sea llamada, o falle.

Un ejemplo clásico es comprobar el contenido de un parámetro, o el número de veces que un método es llamado.

En el ejemplo, crearemos un objeto spy “loadingMeetings_spy” que registre si el método “loadMeeting” es llamado. El registro se realiza en una variable “loadMeetingCalled” (booleana) a la que se le asignará “true” dentro del método “loadMeeting”.

La verificación comprobará si “loadMeetingCalled” es positivo.

QUnit.test("AddFutureMeeting", function () {
var loadingMeetings_spy= {
        loadMeetingCalled : false,

        loadMeeting: function() {
this.loadMeetingCalled = true;
return JSON.parse("[{\"Id\":1,\"Name\":\"Pre Christmas Lunch with Friends\",\"Date\":\"2014-12-23T12:00:00\"}]");
        }
    }

var agenda = new Agenda();
    agenda.addMeeting(loadingMeetings_spy);

    QUnit.ok(loadingMeetings_spy.loadMeetingCalled, "loadMeeting Called");

});

El objeto spy se inyecta exactamente igual que el stub, y en la verificación, se comprueba las interacciones registradas por el mismo.

Por último, tendremos en cuenta, que si se utiliza más de una vez el spy, tendremos que ir reiniciando la variable que hace el registro (habitualmente los frameworks que usamos para crear los Double Test tienen una herramienta para esto),

 

Conclusiones y otros pensamientos.

Personalmente creo, que más que conceptos difíciles, en el tema de los mocks, nos rodeamos de una nomenclatura no muy definida. Si bien la web http://xunitpatterns.com/hace un excelente trabajo intentando consensuar los términos, en cuanto navegamos entre diferentes blog, foros o leemos algún libro sobre el tema, nos damos cuenta de que los nombres bailan.Por ejemplo, hay para quien no existen los fakes puros, sino que son simplemente la forma de categorizar stubs y mocks (y también llevan razón, porque unos engloban a otros), o no existen tantos tipos de Double Test, con Stubs, Mocks y Spys, podemos cubrir todas las necesidades (también es cierto).

De momento, lo que publico es una primera versión del artículo. Espero reescribirlo en el futuro desde un escalón más arriba en cuanto a conocimiento (si M. Fowler puede rectificar una vez, los demás tenemos derecho a hacerlo mil veces, no?),

Por otro lado, tenemos a nuestra disposición, de manera totalmente gratuita muchas herramientas que nos ayudan a crear Double Test, para aplicar tanto en nuestro JavaScript de front-end como de back-end. Merece la pena dedicar un poco de tiempo a estudiar algunas de ellas y ver cual se adapta mejor a nuestras necesidades, yo personalmente las que he utilizado son sinon.js que está muy bien integrado con Qunit y mockjax: https://github.com/jakerella/jquery-mockjax que aunque sea sólo para los Mocks me resulta sencilla y cómoda (intentaré dedicarles un post en un futuro). Pero existen muchas más.

 

Enfrentarse a los double test es un paso natural a la hora de hacer test en nuestras aplicaciones. Es la forma de hacer implementaciones (de los propios test) más sólidas y mantenibles. Y además, creo que lo esencial no es diferenciar perfectamente si lo que estamos escribiendo es un objeto stub o un fake, es utilizarlos correctamente, de forma que el resultado final sea un buen test, útil, que cumpla su misión: probar un trozo de código de la forma más completa posible para evitar posibles bugs.

El propósito del artículo era personal, quería una chuleta y ayudarme a clarificar las ideas. Y después de todo, sigo teniendo dudas y más dudas
Así que, si después de todo encuentras un ejemplo mejor, una defición mejor o algo que pueda ayudar a mejorarlo, no dudes en dejar un comentario.

Más Información

Mocking JavaScript a little – test doubles with sinon.js, mocha and chai

Dummy vs. Stub vs. Spy vs. Fake vs. Mock

Fake Object 

Mocks, Stubs, Expectations, Fakes, Stubs and so on

Dummy vs. Stub vs. Spy vs. Fake vs. Mock

Test Double

Terminología

Mocks, Fakes, Stubs and Dummies

Mocks and Stubs aren’t Spies

 

_comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

techdencias

Double tests: Dummies, mocks, spies, fakes y stubs… en JavaScript

29-Dic-2014

por

Fundación Techdencias

El artículo de referencia sobre qué son mocks y stubs, lo escribió Martin Fowler en enero de 2007 y lo tradujo estupendamente al castellano Carlos Ble en julio del 2008. Aunque los ejemplos estén en Java, el artículo tienen sentido en cualquier lenguaje orientado a objetos, y conocer bien estos conceptos nos ayudará a la hora de hacer test también en JavaScript.

Los programadores de backend están muy familiarizados con los conceptos de mock y stub, sin embargo en la parte cliente, todavía es una práctica reciente que no está muy extendida.

Antes de empezar a poner ejemplos de estos objetos en JavaScript, hablaremos de los “Double Test”, que es la forma general de referirnos a objetos que imitan a otro real. El propósito principal de estos objetos es el aislamiento. El aislamiento de un trozo de código no es fácil de conseguir, en el mundo real el código tiene llamadas a colaboradores, y para romper estas dependencias, creamos objetos falsos. Estos objetos falsos nos ayudan a predeterminar cómo se va a comportar el objeto cuando lo utilicemos: devolveremos un valor fijo, o se simulará una funcionalidad. Establecer las condiciones que necesito me ayudará a saber si el objeto que quiero probar se está comportando de una manera correcta.

Podemos diferenciar cuatro tipos de “double test” (a veces traducido como “dobles de prueba”. La diferencia entre uno otro es cómo verificamos el resultado de esas pruebas: un mock sólo verifica el comportamiento del objeto. Si lo que necesitamos es verificar el estado del objeto al final del test, usaremos un stub. Esto también influye en la complejidad, o en la cantidad de código que tenemos que ponerle al objeto.

Veamos un ejemplo de cada uno de ellos:

Dummy: es un objeto doble que no tiene comportamiento, que reemplaza un colaborador pero que no lo vamos a usar durante la especificación. Por ejemplo en un constructor, o un objeto que pasas como parámetro para no pasarle un “null” desde una prueba.

Por ejemplo, imaginemos que tenemos un objeto “Agenda” y queremos probar un método “addMeeting”.

function Agenda() {
this.meetings = new Array();

this.addMeeting = function(meeting) {
this.meetings.push(meeting);
    }
};

El objeto dummy puede estar vacío, o puede inicializarse, si por ejemplo la función addMeeting tuviera algún validador que comprobara que una reunión tuviera una serie de propiedades y que estuvieran informadas.

En nuestro test queremos comprobar si efectivamente añade un objeto a la variable meetings, aunque no entraremos los detalles de la reunión.

test("AddMeetingTest", function () {

var dummy_meeting = {};

var agenda = new Agenda();
    agenda.addMeeting(dummy_meeting);

    equal(agenda.meetings.length, 1, "Meeting added");
});

 

Stubs: son objetos que proporciona valores de entrada al método que estamos probando, ya sean valores determinados, excepciones, etc. Se trata de devolver respuestas específicas predefinidas sin importar las condiciones en las que los estamos llamando. Normalmente este objeto se llama para probar un test positivo, un test negativo y un test para los valores inesperados.

Por ejemplo, complementamos el objeto Agenda, de forma que realiza una validación antes de añadir una reunión: si la reunión tiene lugar en el futuro sí que podremos programarla, si es anterior a hoy, no la añadirá:

function Agenda() {
this.meetings = new Array();

this.addMeeting = function(meeting) {
var added = false;
if (meeting.isFuture()) {
this.meetings.push(meeting);
            added = true;
        }
return added;
    }
};

Ahora queremos probar que nuestro método es correcto, tanto cuando añade la reunión, como cuando no. El test positivo sería el siguiente:

QUnit.test("AddFutureMeeting", function () {

varstubMeeting = {
        isFuture: function() { returntrue; }
    };

var agenda = new Agenda();
var added = agenda.addMeeting(stubMeeting);

    equal(agenda.meetings.length, 1, "Meeting added");
    QUnit.ok(added, "Future meeting added");

});

La clase Meeting real comparará la fecha de la reunión con el momento actual. Pero de esta forma, sea cual sea el contexto (fecha actual) en el que estemos llamando a la función, la respuesta será la esperada.
Este sería el test negativo:

QUnit.test("AddPastMeeting", function () {

var stubMeeting = {
        isFuture: function () { returnfalse; }
    };

var agenda = new Agenda();
var added = agenda.addMeeting(stubMeeting);

    equal(agenda.meetings.length, 0, "Meeting not added");
    QUnit.ok(!added, "Past meeting not added");

});

 

Fakes: objetos que aparentan un funcionamiento correcto, una implementación que funciona, alternativa a la original. Aunque esta implementación está simplificada, por lo que no pueden ser usados en producción. No sólo devuelven siempre los mismos valores, de hecho funciona como un colaborador real lo haría: por ejemplo, en vez de guardar las reuniones haciendo una llamada al backend, se recogen de un array en memoria, pero de igual modo se almacenan. Se diferencian con el mock (que veremos a continuación) en que el test no tiene control sobre estos, y se diferencian de los stubs en que estos últimos son creados e inyectados en el sistema para test individuales para una necesidad básica, pero los fakes son más completos, añadidos en el sistema antes de ejecutar las pruebas, ya que posiblemente se utilicen en más de una, esto los hace un poco más independientes del test en sí.

Siguiendo el ejemplo que ya lo hemos utilizado anteriormente. Si quisiéramos inicializar en la agenda las reuniones que ya hay guardadas en base de datos, podríamos extender la clase Agenda añadiéndole un método loadMeetings que añada las reuniones que inicialmente haya guardadas en la agenda.

Para comprobar que loadMeetings funciona, hacemos un test, que recuperará las reuniones y las cargará.

QUnit.test("Load Meetings", function (assert) {
var done = assert.async(); //asíncrona
var agenda = new Agenda();
var loaded = false;


loaded = agenda.loadMeetings(MeetingManager.getAllMeetings);

    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 3, "Three meetings loaded");
        done();
    });


});

 

La función MeetingManager.getAllMeetings() en principio hace una llamada AJAX y recupera las reuniones.

var MeetingManager = {
    getAllMeetings: function (callback) {
var backendUrl = 'http://localhost:31938';
var uri = '/api/meetings';

// Send an AJAX request
        $.getJSON(backendUrl + uri)
            .done(function (json) {
if ((typeof callback) === 'function') {
                    callback(json);
                }
            }).fail(function (jqXHR, textStatus, errorThrown) {
if ((typeof callback) === 'function') {
                    callback(jqXHR, new Error(errorThrown));
                }
            });
    }
};

Pero tenemos dos problemas 1) ralentización de los test (puede tardar varios segundos más en hacer la llamada) lo que provoca que no se lancen tan continuamente como debería 2) no siempre, en todo el desarrollo de la aplicación podemos asegurar que haya tres reuniones en la base de datos.
Así que lo solucionamos haciendo un objeto fake que sustituya a getAllMeetings y que nos devuelva unas reuniones prefijadas:

getAllMeetings_fake: function(callback) {
   var json = "[{\"Id\":1,\"Name\":\"Pre Christmas Lunch with
Friends\",\"Date\":\"2014-12-23T12:00:00\"},{\"Id\":2,\"Name\":\"Christmas Dinner\",\"Date\":\"2014-12-24T22:30:00\"},{\"Id\":3,\"Name\":\"Christmas
	Launch\",\"Date\":\"2014-12-25T13:45:00\"}]";
if ((typeof callback) === 'function') {
            callback(JSON.parse(json));
        }
    }

Y en el test sólo he cambiado la llamada:
QUnit.test("Load Meetings", function (assert) {
var done = assert.async();
var agenda = new Agenda();
var loaded = false;


    agenda.loadMeetings(MeetingManager.getAllMeetings_fake());


    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 3, "Three meetings loaded");
        done();
    });

});

Aunque ahora sí que es síncrono, no he cambiado ese trozo de código.
Podemos ver que el tiempo se ha reducido notablemente:

En este ejemplo, hemos “falsificado” una llamada a Web API, pero también es muy común hacerlo con acceso a base de datos (en el caso de código en servidor), o crear una base de datos en memoria, o la capa de servicios (como en una interfaz de usuario, para aislarnos de los datos o del comportamiento).

 

Mocks: objetos preprogramados que se usan para comprobar que se ejecuta correctamente una llamada externa a una API, pero sin llegar a hacerla. Los test comprueban que la llamada recibe los parámetros correctos añadiéndole más condiciones o validadores para comprobar la correcta utilización de la clase y aportando un juego de pruebas más completo.

Se parece al stub a que se “intercepta” y reemplaza un objeto/llamada alternando el flujo natural de la aplicación. La diferencia es que el stub siempre devuelve una respuesta “fija” y el mock imita de una forma más completa el comportamiento del objeto, proporcionando una respuesta similar al que éste daría.

La respuesta del mock no se verifica con un estado, sino con un comportamiento.

En la web de xUnitPatterns hay una tabla en la que podemos observar muy visualmente la diferencia entre fakes, mock y stubs. También podemos ver, que cada autor de los libros de referencia de testing hacen pequeñas variaciones en la terminología y en la descripción de los mismos.

http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html
Los que podemos ver es que las diferencias más notables es que mientras que Fake no recoge entradas del objeto que se está probando, el mock sí que podría recogerlas, y usarlas como parte de su comportamiento. Además se encargará de verificar estas entradas

Pattern Purpose Has Behavior Injects indirect inputs into SUT Handles indirect outputs of SUT Values provided by test(er)
Mock Object Verify indirect outputs of SUT yes Optional verifies correctness against expectations outputs & inputs (optional)
Fake Object Run (unrunnable) tests (faster) yes no uses them none

 

Como ejemplo, hemos ampliado el anterior stub, pero haciendo que los datos devueltos por el mock sean restringidos por una entrada proporcionada: sólo devolverá las reuniones que están fijadas el día que se le pase por parámetro:

QUnit.test("Load Meetings", function (assert) {
var done = assert.async();
var agenda = new Agenda();
var loaded = false;

    varday = new Date("December 25, 2014");
    agenda.loadMeetings(getMeetings_mock, day);




    setTimeout(function () {
        QUnit.ok(loaded, "Meetings loaded");
        QUnit.equal(agenda.meetings.length, 1, "One meeting 25/12/2014");
        QUnit.equal(agenda.meetings[0].Date.getDate(), 25, "Meeting returned 25th");
        QUnit.equal(agenda.meetings[0].Date.getMonth(), 11, "Meeting returned Dec");
        QUnit.equal(agenda.meetings[0].Date.getFullYear(), 2014, "Meeting returned 2014");
        done();
    });

});

El Mock simplemente filtrará el anterior JSON:

getMeetings_mock: function(date, callback) {
var json = JSON.parse(...);
var selectedMeetings = [];
        json.forEach(function (meeting) {
            meeting.Date = new Date(meeting.Date);
if (meeting.Date.getDate() == date.getDate() &&
                meeting.Date.getMonth() == date.getMonth() &&
                meeting.Date.getYear() == date.getYear()) {
                selectedMeetings.push(meeting);
            }
        });
if ((typeof callback) === 'function') {
            callback(selectedMeetings);
        }
    }

La diferencia es tan sutil que puede crear confusión, en realidad, el mock no se convierte en un mock hasta la verificación final, con el stub sólo comprobamos el estado del objeto, pero en el mock se comprueban el orden y el que los resultados sí que sean correctos.

 

Spies: son objetos muy parecidos a los stubs, pero que además de cumplir su función, almacenan información sobre qué métodos son llamados, o el número de veces que se utilizan. Para comprobar que un fragmento de código pasa (o llama) a una función. El spy no interfiere en el resultado de la llamada, sólo se limita a hacer un registro. El error de la verificación se dará si la función no existe, o no sea llamada, o falle.

Un ejemplo clásico es comprobar el contenido de un parámetro, o el número de veces que un método es llamado.

En el ejemplo, crearemos un objeto spy “loadingMeetings_spy” que registre si el método “loadMeeting” es llamado. El registro se realiza en una variable “loadMeetingCalled” (booleana) a la que se le asignará “true” dentro del método “loadMeeting”.

La verificación comprobará si “loadMeetingCalled” es positivo.

QUnit.test("AddFutureMeeting", function () {
var loadingMeetings_spy= {
        loadMeetingCalled : false,

        loadMeeting: function() {
this.loadMeetingCalled = true;
return JSON.parse("[{\"Id\":1,\"Name\":\"Pre Christmas Lunch with Friends\",\"Date\":\"2014-12-23T12:00:00\"}]");
        }
    }

var agenda = new Agenda();
    agenda.addMeeting(loadingMeetings_spy);

    QUnit.ok(loadingMeetings_spy.loadMeetingCalled, "loadMeeting Called");

});

El objeto spy se inyecta exactamente igual que el stub, y en la verificación, se comprueba las interacciones registradas por el mismo.

Por último, tendremos en cuenta, que si se utiliza más de una vez el spy, tendremos que ir reiniciando la variable que hace el registro (habitualmente los frameworks que usamos para crear los Double Test tienen una herramienta para esto),

 

Conclusiones y otros pensamientos.

Personalmente creo, que más que conceptos difíciles, en el tema de los mocks, nos rodeamos de una nomenclatura no muy definida. Si bien la web http://xunitpatterns.com/hace un excelente trabajo intentando consensuar los términos, en cuanto navegamos entre diferentes blog, foros o leemos algún libro sobre el tema, nos damos cuenta de que los nombres bailan.Por ejemplo, hay para quien no existen los fakes puros, sino que son simplemente la forma de categorizar stubs y mocks (y también llevan razón, porque unos engloban a otros), o no existen tantos tipos de Double Test, con Stubs, Mocks y Spys, podemos cubrir todas las necesidades (también es cierto).

De momento, lo que publico es una primera versión del artículo. Espero reescribirlo en el futuro desde un escalón más arriba en cuanto a conocimiento (si M. Fowler puede rectificar una vez, los demás tenemos derecho a hacerlo mil veces, no?),

Por otro lado, tenemos a nuestra disposición, de manera totalmente gratuita muchas herramientas que nos ayudan a crear Double Test, para aplicar tanto en nuestro JavaScript de front-end como de back-end. Merece la pena dedicar un poco de tiempo a estudiar algunas de ellas y ver cual se adapta mejor a nuestras necesidades, yo personalmente las que he utilizado son sinon.js que está muy bien integrado con Qunit y mockjax: https://github.com/jakerella/jquery-mockjax que aunque sea sólo para los Mocks me resulta sencilla y cómoda (intentaré dedicarles un post en un futuro). Pero existen muchas más.

 

Enfrentarse a los double test es un paso natural a la hora de hacer test en nuestras aplicaciones. Es la forma de hacer implementaciones (de los propios test) más sólidas y mantenibles. Y además, creo que lo esencial no es diferenciar perfectamente si lo que estamos escribiendo es un objeto stub o un fake, es utilizarlos correctamente, de forma que el resultado final sea un buen test, útil, que cumpla su misión: probar un trozo de código de la forma más completa posible para evitar posibles bugs.

El propósito del artículo era personal, quería una chuleta y ayudarme a clarificar las ideas. Y después de todo, sigo teniendo dudas y más dudas
Así que, si después de todo encuentras un ejemplo mejor, una defición mejor o algo que pueda ayudar a mejorarlo, no dudes en dejar un comentario.

Más Información

Mocking JavaScript a little – test doubles with sinon.js, mocha and chai

Dummy vs. Stub vs. Spy vs. Fake vs. Mock

Fake Object 

Mocks, Stubs, Expectations, Fakes, Stubs and so on

Dummy vs. Stub vs. Spy vs. Fake vs. Mock

Test Double

Terminología

Mocks, Fakes, Stubs and Dummies

Mocks and Stubs aren’t Spies

 

_comentarios

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *