¿Cuál es la diferencia entre fingir, burlarse y tropezar?

706

Sé cómo uso estos términos, pero me pregunto si hay definiciones aceptadas para falsificar , burlarse y tropezar para las pruebas unitarias. ¿Cómo los define para sus pruebas? Describa situaciones en las que podría usar cada una.

Así es como los uso:

Falso : una clase que implementa una interfaz pero contiene datos fijos y no tiene lógica. Simplemente devuelve datos "buenos" o "malos" dependiendo de la implementación.

Mock : una clase que implementa una interfaz y permite la capacidad de establecer dinámicamente los valores para devolver / excepciones para lanzar desde métodos particulares y proporciona la capacidad de verificar si se han llamado / no llamados a métodos particulares.

Talón : como una clase simulada, excepto que no proporciona la capacidad de verificar que los métodos hayan sido llamados / no llamados.

Los simulacros y los talones pueden generarse a mano o generarse mediante un marco de imitación. Las clases falsas se generan a mano. Utilizo simulacros principalmente para verificar las interacciones entre mi clase y las clases dependientes. Utilizo stubs una vez que he verificado las interacciones y estoy probando rutas alternativas a través de mi código. Utilizo clases falsas principalmente para abstraer dependencias de datos o cuando los simulacros / stubs son demasiado tediosos para configurar cada vez.

tvanfosson
fuente
66
Bueno, básicamente lo dijiste todo en tu "pregunta" :) Creo que esas son definiciones bastante aceptadas de esos términos
Eran Galperin
2
La definición de Wikipedia de Fake difiere de esto, afirmando que un Fake "se usa como una implementación más simple, por ejemplo, usando una base de datos en memoria en las pruebas en lugar de hacer un acceso real a la base de datos)" Ver en.wikipedia.org/wiki/Test_double
zumalifeguard
2
Aprendí mucho del siguiente recurso, con una excelente explicación de Robert C. Martin (tío Bob): The Little Mocker en The Clean Code Blog . Explica las diferencias y sutilezas de los maniquíes, dobles de prueba, trozos, espías, simulacros (verdaderos) y falsificaciones. También menciona a Martin Fowler y explica un poco del historial de pruebas de software.
Erik
testing.googleblog.com/2013/07/… (un breve resumen de una página).
ShreevatsaR
Aquí está mi opinión para explicar eso: Prueba Dobles: Fakes, Stubs y Mocks (publicación de blog con ejemplos)
michal-lipski

Respuestas:

548

Puedes obtener alguna información:

De Martin Fowler sobre Mock and Stub

Los objetos falsos en realidad tienen implementaciones que funcionan, pero generalmente toman un atajo que los hace no adecuados para la producción

Los talones proporcionan respuestas enlatadas a las llamadas realizadas durante la prueba, por lo general no responden en absoluto a nada fuera de lo programado para la prueba. Los resguardos también pueden registrar información sobre llamadas, como un resguardo de puerta de enlace de correo electrónico que recuerda los mensajes que 'envió', o tal vez solo la cantidad de mensajes que 'envió'.

Aquí estamos hablando de simulacros : objetos preprogramados con expectativas que forman una especificación de las llamadas que se espera que reciban.

Desde xunitpattern :

Falso : Adquirimos o construimos una implementación muy ligera de la misma funcionalidad proporcionada por un componente del que depende el SUT e instruimos al SUT para que lo use en lugar del real.

Stub : esta implementación está configurada para responder a llamadas del SUT con los valores (o excepciones) que ejercerán el Código no probado (ver Errores de producción en la página X) dentro del SUT. Una indicación clave para usar un Stub de prueba es tener un código no probado causado por la incapacidad de controlar las entradas indirectas del SUT

Objeto simulado que implementa la misma interfaz que un objeto del que depende el SUT (Sistema bajo prueba). Podemos usar un objeto simulado como punto de observación cuando necesitamos hacer una verificación de comportamiento para evitar tener un requisito no probado (ver Errores de producción en la página X) causado por la incapacidad de observar los efectos secundarios de invocar métodos en el SUT.

Personalmente

Intento simplificar usando: Mock and Stub. Uso Mock cuando es un objeto que devuelve un valor que se establece en la clase probada. Uso Stub para imitar una interfaz o una clase abstracta para probar. De hecho, no importa cómo lo llames, son todas las clases que no se usan en producción y se usan como clases de utilidad para pruebas.

Patrick Desjardins
fuente
99
Me parece que las definiciones de Stub y Fake se invierten en la cita xUnitPattern en comparación con la cita de Martin Fowler. Además, las definiciones de Stub y Fake de Martin Fowler se invierten en comparación con las definiciones de la pregunta original de tvanfosson. En realidad, ¿hay alguna definición generalmente aceptada de esos dos términos o solo depende de con quién estás hablando?
Simon Tewsi
3
+1 para "Intento simplificar usando: Mock and Stub". ¡Es una gran idea!
Brad Cupit
44
No puedo ver cómo usar solo Mock and Stub es una gran idea. Cada prueba doble tiene sus propósitos y, por lo tanto, sus usos.
Héctor Ordóñez
1
No puedo ver la diferencia entre Fake y Mock en la definición de MF.
IdontCareAboutReputationPoints
2
@MusuNaji: En la definición de MF no hay "expectativas" con respecto a la conversación para un Fake, aparte de que tiene una implementación para su interfaz. Por otro lado, el Mock será desafiado (¿se llamó este método?).
dbalakirev
205

Stub : un objeto que proporciona respuestas predefinidas a llamadas a métodos.

Mock : un objeto en el que estableces expectativas.

Falso : un objeto con capacidades limitadas (para fines de prueba), por ejemplo, un servicio web falso.

Test Double es el término general para talones, simulacros y falsificaciones. Pero informalmente, a menudo escuchará a la gente simplemente llamarlos simulacros.

Miguel
fuente
44
¿Alguien podría explicarme y definirme qué es una "respuesta enlatada" en este contexto?
MasterMastic
14
Un valor explícito, en lugar de un valor que se calcula.
Mike
¡Finalmente! ¡Algunas definiciones que puedo entender! Basado en estas definiciones, entonces, googletest (gtest) / googlemock (gmock) permite que los objetos simulados también sean stubs, ya que puede crear EXPECT_CALL()s en un método simulado que fuerce ciertas salidas basadas en ciertas entradas, usando el tipo .WillOnce(Invoke(my_func_or_lambda_func))(o con .WillRepeatedly()) sintaxis adjunta a un EXPECT_CALL(). Algunos ejemplos de uso Invoke()se pueden ver en un contexto diferente al final de mi larga respuesta aquí: stackoverflow.com/a/60905880/4561887 .
Gabriel Staples
La documentación de Gmock Invoke()está aquí: github.com/google/googletest/blob/master/googlemock/docs/… . De todos modos, la conclusión es: el simulacro de Google (gmock) permite crear fácilmente simulacros y talones , aunque la mayoría de los simulacros no son talones.
Gabriel Staples
Los simulacros son un superconjunto de Stubs, aún pueden devolver respuestas predefinidas, pero también permiten al desarrollador establecer expectativas. OMI ciertas bibliotecas por ahí difuminan las líneas de todos los maniquíes de prueba.
Lucas
94

Me sorprende que esta pregunta haya existido durante tanto tiempo y que nadie haya brindado una respuesta basada en "The Art of Unit Testing" de Roy Osherove. .

En "3.1 Introducción de apéndices" se define un apéndice como:

Un stub es un reemplazo controlable para una dependencia existente (o colaborador) en el sistema. Al usar un código auxiliar, puede probar su código sin tratar directamente con la dependencia.

Y define la diferencia entre stubs y simulacros como:

Lo principal que debe recordar acerca de los simulacros frente a los apéndices es que los simulacros son como apéndices, pero usted afirma contra el objeto simulado, mientras que no lo hace contra un apéndice.

Falso es solo el nombre que se usa tanto para los trozos como para las simulaciones. Por ejemplo, cuando no te importa la distinción entre trozos y simulacros.

La forma en que Osherove distingue entre trozos y simulacros significa que cualquier clase utilizada como falso para la prueba puede ser tanto un trozo como un simulacro. Lo que es para una prueba específica depende completamente de cómo escriba los cheques en su prueba.

  • Cuando su prueba verifica los valores en la clase bajo prueba, o en cualquier otro lugar que no sea falso, el falso se usó como un trozo. Solo proporcionó valores para que la clase bajo prueba los use, ya sea directamente a través de valores devueltos por llamadas en él o indirectamente a través de causar efectos secundarios (en algún estado) como resultado de llamadas en él.
  • Cuando su prueba verifica los valores de la falsificación, se usaba como un simulacro.

Ejemplo de una prueba donde la clase FakeX se usa como un trozo:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, cut.SomeProperty);

La fakeinstancia se usa como un código auxiliar porque Assertno se usa fakeen absoluto.

Ejemplo de una prueba donde la clase de prueba X se usa como un simulacro:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

En este caso, Assertcomprueba un valor fake, lo que hace que sea falso.

Ahora, por supuesto, estos ejemplos son muy artificiales, pero veo un gran mérito en esta distinción. Te hace saber cómo estás probando tus cosas y dónde están las dependencias de tu prueba.

Estoy de acuerdo con Osherove que

desde una perspectiva pura de mantenibilidad, en mis pruebas el uso de simulacros crea más problemas que no usarlos. Esa ha sido mi experiencia, pero siempre estoy aprendiendo algo nuevo.

Afirmar lo falso es algo que realmente desea evitar, ya que sus pruebas dependen en gran medida de la implementación de una clase que no es la que se está probando en absoluto. Lo que significa que las pruebas para la clase ActualClassUnderTestpueden comenzar a romperse porque la implementación de ClassUsedAsMockcambió. Y eso me envía un olor desagradable. Las pruebas para ActualClassUnderTestpreferiblemente solo deben romperse cuandoActualClassUnderTest se cambia.

Me doy cuenta de que escribir afirmaciones falsas es una práctica común, especialmente cuando eres un suscriptor de TDD falso. Supongo que estoy firmemente con Martin Fowler en el campo clasicista (Ver "Las burlas de Martin Fowler no son trozos" ) y, al igual que Osherove, evito las pruebas de interacción (que solo se pueden hacer afirmando contra lo falso) tanto como sea posible.

Para divertirse leyendo sobre por qué debería evitar los simulacros como se define aquí, busque "clasicista simulador de cazadores". Encontrarás una gran cantidad de opiniones.

Marjan Venema
fuente
30

Como se menciona en la respuesta más votada, Martin Fowler analiza estas distinciones en Mocks An't Stubs , y en particular el subtítulo La diferencia entre simulacros y stubs , así que asegúrese de leer ese artículo.

En lugar de centrarnos en cómo estas cosas son diferentes, creo que es más esclarecedor centrarse en por qué estos son conceptos distintos. Cada uno existe para un propósito diferente.

Falsificaciones

Un falso es una implementación que se comporta "naturalmente", pero no es "real". Estos son conceptos confusos, por lo que diferentes personas tienen diferentes interpretaciones de lo que hace que las cosas sean falsas.

Un ejemplo de una falsificación es una base de datos en memoria (por ejemplo, usando sqlite con la :memory:tienda). Nunca usaría esto para la producción (ya que los datos no son persistentes), pero es perfectamente adecuado como base de datos para usar en un entorno de prueba. También es mucho más ligero que una base de datos "real".

Como otro ejemplo, quizás use algún tipo de almacén de objetos (por ejemplo, Amazon S3) en producción, pero en una prueba simplemente puede guardar objetos en archivos en el disco; entonces su implementación de "guardar en disco" sería falsa. (O incluso podría simular la operación "guardar en disco" utilizando un sistema de archivos en memoria).

Como tercer ejemplo, imagine un objeto que proporciona una API de caché; un objeto que implementa la interfaz correcta pero que simplemente no realiza almacenamiento en caché pero siempre devuelve un error de caché sería una especie de falso.

El propósito de una falsificación no es afectar el comportamiento del sistema bajo prueba , sino más bien simplificar la implementación de la prueba (eliminando las dependencias innecesarias o pesadas).

Trozos

Un trozo es una implementación que se comporta "de forma no natural". Está preconfigurado (generalmente por la configuración de prueba) para responder a entradas específicas con salidas específicas.

El propósito de un código auxiliar es poner su sistema a prueba en un estado específico. Por ejemplo, si está escribiendo una prueba para algún código que interactúa con una API REST, puede desactivar la API REST con una API que siempre devuelve una respuesta fija, o que responde a una solicitud de API con un error específico. De esta manera, podría escribir pruebas que hagan afirmaciones sobre cómo reacciona el sistema a estos estados; por ejemplo, probando la respuesta que obtienen sus usuarios si la API devuelve un error 404.

Un trozo generalmente se implementa para responder solo a las interacciones exactas a las que le has dicho que responda. Pero la característica clave que hace que algo sea un trozo es su propósito : un trozo se trata de configurar su caso de prueba.

Simulacros

Un simulacro es similar a un código auxiliar, pero con la verificación agregada. El propósito de un simulacro es hacer afirmaciones sobre cómo su sistema bajo prueba interactúa con la dependencia .

Por ejemplo, si está escribiendo una prueba para un sistema que carga archivos en un sitio web, puede crear un simulacro que acepte un archivo y que pueda usar para afirmar que el archivo cargado es correcto. O, en una escala más pequeña, es común usar una simulación de un objeto para verificar que el sistema bajo prueba llama a métodos específicos del objeto simulado.

Los simulacros están vinculados a las pruebas de interacción , que es una metodología de prueba específica. Las personas que prefieren probar el estado del sistema en lugar de las interacciones del sistema usarán simulacros con moderación si es que lo hacen.

Prueba doble

Falsificaciones, talones y simulacros pertenecen a la categoría de dobles de prueba . Una prueba doble es cualquier objeto o sistema que usa en una prueba en lugar de otra cosa. La mayoría de las pruebas de software automatizadas implican el uso de dobles de prueba de algún tipo u otro. Algunos otros tipos de dobles prueba incluyen valores ficticios , espías , y de E / S de los agujeros negros .

Daniel Pryden
fuente
11

Para ilustrar el uso de trozos y simulacros, me gustaría incluir también un ejemplo basado en " The Art of Unit Testing " de Roy Osherove .

Imagínese, tenemos una aplicación LogAnalyzer que tiene la única funcionalidad de imprimir registros. No solo necesita hablar con un servicio web, sino que si el servicio web arroja un error, LogAnalyzer debe registrar el error en una dependencia externa diferente, enviándolo por correo electrónico al administrador del servicio web.

Aquí está la lógica que nos gustaría probar dentro de LogAnalyzer:

if(fileName.Length<8)
{
 try
  {
    service.LogError("Filename too short:" + fileName);
  }
 catch (Exception e)
  {
    email.SendEmail("a","subject",e.Message);
  }
}

¿Cómo prueba que LogAnalyzer llama al servicio de correo electrónico correctamente cuando el servicio web arroja una excepción? Estas son las preguntas que enfrentamos:

  • ¿Cómo podemos reemplazar el servicio web?

  • ¿Cómo podemos simular una excepción del servicio web para que podamos probar la llamada al servicio de correo electrónico?

  • ¿Cómo sabremos que el servicio de correo electrónico fue llamado correctamente o en absoluto?

Podemos ocuparnos de las dos primeras preguntas utilizando un código auxiliar para el servicio web . Para resolver el tercer problema, podemos usar un objeto simulado para el servicio de correo electrónico .

Un falso es un término genérico que se puede usar para describir un trozo o un simulacro. En nuestra prueba, tendremos dos falsificaciones. Uno será el simulacro del servicio de correo electrónico, que usaremos para verificar que se enviaron los parámetros correctos al servicio de correo electrónico. El otro será un trozo que usaremos para simular una excepción lanzada desde el servicio web. Es un trozo porque no utilizaremos el servicio web falso para verificar el resultado de la prueba, solo para asegurarnos de que la prueba se ejecute correctamente. El servicio de correo electrónico es un simulacro porque afirmaremos en su contra que se llamó correctamente.

[TestFixture]
public class LogAnalyzer2Tests
{
[Test]
 public void Analyze_WebServiceThrows_SendsEmail()
 {
   StubService stubService = new StubService();
   stubService.ToThrow= new Exception("fake exception");
   MockEmailService mockEmail = new MockEmailService();

   LogAnalyzer2 log = new LogAnalyzer2();
   log.Service = stubService
   log.Email=mockEmail;
   string tooShortFileName="abc.ext";
   log.Analyze(tooShortFileName);

   Assert.AreEqual("a",mockEmail.To); //MOCKING USED
   Assert.AreEqual("fake exception",mockEmail.Body); //MOCKING USED
   Assert.AreEqual("subject",mockEmail.Subject);
 }
}
nanospeck
fuente
9

lo que afirmas sobre él, se llama un objeto simulado y todo lo demás que solo ayudó a ejecutar la prueba, es un trozo .

Arezoo Bagherzadi
fuente
1
mientras que otras respuestas tienen gran detalle y son realmente buenas. este hace que sea tan claro y fácil hacer la diferencia, es difícil no votar. gj!
Mario Garcia
6

Se trata de hacer que las pruebas sean expresivas. Establezco expectativas en un simulacro si quiero que la prueba describa una relación entre dos objetos. Resto los valores de retorno si estoy configurando un objeto de soporte para llevarme al comportamiento interesante en la prueba.

Steve Freeman
fuente
6

Si está familiarizado con Arrange-Act-Assert, entonces una forma de explicar la diferencia entre stub y simulacro que podría ser útil para usted es que los stubs pertenecen a la sección de arreglos, como lo son para organizar el estado de entrada, y los simulacros pertenecen a la sección de afirmación como lo son para afirmar resultados contra.

Los tontos no hacen nada. Son solo para llenar listas de parámetros, para que no obtenga errores indefinidos o nulos. También existen para satisfacer el verificador de tipo en idiomas estrictamente escritos, de modo que se le permita compilar y ejecutar.

Sammi
fuente
3

Stub, Fakes y Mocks tienen diferentes significados en diferentes fuentes. Le sugiero que presente los términos internos de su equipo y acuerde su significado.

Creo que es importante distinguir entre dos enfoques: - validación de comportamiento (implica sustitución de comportamiento) - validación de estado final (implica emulación de comportamiento)

Considere enviar correos electrónicos en caso de error. Cuando se realiza la validación de la conducta - comprobar que el método Sendde IEmailSenderfue ejecutado una vez. Y debe emular el resultado de retorno de este método, el Id de retorno del mensaje enviado. Entonces usted dice: "Espero que Sendse llame. Y simplemente devolveré una identificación ficticia (o aleatoria) para cualquier llamada" . Esta es la validación del comportamiento: emailSender.Expect(es=>es.Send(anyThing)).Return((subject,body) => "dummyId")

Al realizar la validación de estado, deberá crear TestEmailSenderesos implementos IEmailSender. E implemente el Sendmétodo: guardando la entrada en alguna estructura de datos que se utilizará para la verificación del estado futuro como la matriz de algunos objetos SentEmailsy luego comprueba que verificará que SentEmailscontiene el correo electrónico esperado. Esta es la validación de estado: Assert.AreEqual(1, emailSender.SentEmails.Count)

De mis lecturas entendí que la validación de comportamiento generalmente se llamaba simulacros . Y la validación estatal generalmente se llama Stubs o Fakes .

Marat Gallyamov
fuente
Realmente bien definido y definición nítida.
shyam sundar singh tomar
2

stub y fake son objetos en el sentido de que pueden variar su respuesta en función de los parámetros de entrada. La principal diferencia entre ellos es que un Fake está más cerca de una implementación en el mundo real que un trozo. Los apéndices contienen básicamente respuestas codificadas a una solicitud esperada. Veamos un ejemplo:

public class MyUnitTest {

 @Test
 public void testConcatenate() {
  StubDependency stubDependency = new StubDependency();
  int result = stubDependency.toNumber("one", "two");
  assertEquals("onetwo", result);
 }
}

public class StubDependency() {
 public int toNumber(string param) {
  if (param == “one”) {
   return 1;
  }
  if (param == “two”) {
   return 2;
  }
 }
}

Un simulacro es un paso adelante de las falsificaciones y los trozos. Los simulacros proporcionan la misma funcionalidad que los apéndices, pero son más complejos. Pueden tener reglas definidas para ellos que dictan en qué orden deben llamarse los métodos en su API. La mayoría de los simulacros pueden rastrear cuántas veces se llamó a un método y pueden reaccionar en función de esa información. Los simulacros generalmente conocen el contexto de cada llamada y pueden reaccionar de manera diferente en diferentes situaciones. Debido a esto, los simulacros requieren algún conocimiento de la clase de la que se burlan. un trozo generalmente no puede rastrear cuántas veces se llamó a un método o en qué orden se llamó a una secuencia de métodos. Un simulacro se parece a:

public class MockADependency {

 private int ShouldCallTwice;
 private boolean ShouldCallAtEnd;
 private boolean ShouldCallFirst;

 public int StringToInteger(String s) {
  if (s == "abc") {
   return 1;
  }
  if (s == "xyz") {
   return 2;
  }
  return 0;
 }

 public void ShouldCallFirst() {
  if ((ShouldCallTwice > 0) || ShouldCallAtEnd)
   throw new AssertionException("ShouldCallFirst not first thod called");
  ShouldCallFirst = true;
 }

 public int ShouldCallTwice(string s) {
  if (!ShouldCallFirst)
   throw new AssertionException("ShouldCallTwice called before ShouldCallFirst");
  if (ShouldCallAtEnd)
   throw new AssertionException("ShouldCallTwice called after ShouldCallAtEnd");
  if (ShouldCallTwice >= 2)
   throw new AssertionException("ShouldCallTwice called more than twice");
  ShouldCallTwice++;
  return StringToInteger(s);
 }

 public void ShouldCallAtEnd() {
  if (!ShouldCallFirst)
   throw new AssertionException("ShouldCallAtEnd called before ShouldCallFirst");
  if (ShouldCallTwice != 2) throw new AssertionException("ShouldCallTwice not called twice");
  ShouldCallAtEnd = true;
 }

}
Alireza Rahmani Khalili
fuente
1

fake objectes una implementación real de interfaz (protocolo) o una extensión que usa herencia u otros enfoques que se pueden usar para crear, es la dependencia. Por lo general, el desarrollador lo crea como una solución más simple para sustituir alguna dependencia

stub objectEs un objeto desnudo (0, nil y métodos sin lógica) con y extra y predefinida (por desarrollador) estado para definir valores devueltos. Por lo general, es creado por framework

mock objectes muy similar stub objectpero se cambia el estado adicional durante la ejecución del programa para verificar si sucedió algo (se llamó al método).

yoAlex5
fuente