visual unitarias unit test studio pruebas mvc make integracion how unit-testing mocking

unit-testing - unitarias - unit test c#



Cómo burlarse y probar la unidad (4)

Esencialmente, lo que está probando aquí es que se están llamando a los métodos, no si realmente funcionan o no. Que es lo que se supone que deben hacer los burlones. En lugar de llamar al método, simplemente verifican si se llamó al método y devuelve lo que está en la declaración Return (). Entonces en tu afirmación aquí:

Assert.That(result, Is.False, "error message here");

Esta afirmación SIEMPRE tendrá éxito porque su expectativa SIEMPRE devolverá falsa, debido a la declaración de Devolución:

userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false);

Supongo que esto no es tan útil en este caso.

Donde la burla es útil es cuando desea, por ejemplo, realizar una llamada a la base de datos en algún lugar de su código, pero no desea llamar a la base de datos. Desea pretender que se llamó a la base de datos, pero desea configurar algunos datos falsos para que regrese, y luego (aquí está la parte importante) probar la lógica que hace algo con los datos falsos que devolvió su simulacro. En los ejemplos anteriores, está omitiendo el último paso. Imagine que tiene un método que muestra un mensaje al usuario que dice si se creó el nuevo usuario:

public string displayMessage(bool userWasCreated) { if (userWasCreated) return "User created successfully!"; return "User already exists"; }

entonces tu prueba sería

userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false); Assert.AreEqual("User already exists", displayMessage(userSvc.CreateUser(theUser)))

Ahora esto tiene algún valor, porque estás probando algún comportamiento real. Por supuesto, también puede probar esto directamente pasando "verdadero" o "falso". Ni siquiera necesitas un simulacro para esa prueba. Poner a prueba las expectativas está bien, pero he escrito muchas pruebas como esa, y he llegado a la misma conclusión a la que estás llegando, simplemente no es tan útil.

En resumen, la burla es útil cuando desea abstraer externalidades, como bases de datos, llamadas a servicios web, etc., e inyectar valores conocidos en ese punto. Pero a menudo no es útil probar simulacros directamente.

Básicamente, intento enseñarme a mí mismo cómo codificar y quiero seguir las buenas prácticas. Hay beneficios obvios para las pruebas unitarias. También hay mucho fanatismo cuando se trata de pruebas unitarias y prefiero un enfoque mucho más pragmático a la codificación y la vida en general. Como contexto, actualmente estoy escribiendo mi primera aplicación "real" que es el ubicuo motor de blogs que usa asp.net MVC. Estoy siguiendo la arquitectura MVC Storefront con mis propios ajustes. Como tal, esta es mi primera incursión real en objetos burlones. Pondré el ejemplo del código al final de la pregunta.

Agradecería cualquier idea o recursos externos que pudiera utilizar para aumentar mi comprensión de los fundamentos de las pruebas y las burlas. Los recursos que he encontrado en la red suelen estar orientados al "cómo" de la burla y necesito una mayor comprensión del dónde, el por qué y el cuándo de la burla. Si este no es el mejor lugar para hacer esta pregunta, indíqueme un lugar mejor.

Estoy tratando de comprender el valor que obtengo de las siguientes pruebas. El UserService depende de IUserRepository. El valor de la capa de servicio es separar su lógica de su almacenamiento de datos, pero en este caso la mayoría de las llamadas de UserService se pasan directamente a IUserRepository. El hecho de que no haya mucha lógica real para evaluar podría ser también la fuente de mis preocupaciones. Tengo las siguientes preocupaciones.

  • Parece que el código solo está probando que el marco de burla está funcionando.
  • Para simular las dependencias, mis pruebas tienen demasiado conocimiento de la implementación de IUserRepository. ¿Es esto un mal necesario?
  • ¿Qué valor estoy obteniendo de estas pruebas? ¿La simplicidad del servicio bajo prueba me hace dudar del valor de estas pruebas?

Estoy usando NUnit y Rhino.Mocks, pero debería ser bastante obvio lo que estoy tratando de lograr.

[SetUp] public void Setup() { userRepo = MockRepository.GenerateMock<IUserRepository>(); userSvc = new UserService(userRepo); theUser = new User { ID = null, UserName = "http://joe.myopenid.com", EmailAddress = "[email protected]", DisplayName = "Joe Blow", Website = "http://joeblow.com" }; } [Test] public void UserService_can_create_a_new_user() { // Arrange userRepo.Expect(repo => repo.CreateUser(theUser)).Return(true); // Act bool result = userSvc.CreateUser(theUser); // Assert userRepo.VerifyAllExpectations(); Assert.That(result, Is.True, "UserService.CreateUser(user) failed when it should have succeeded"); } [Test] public void UserService_can_not_create_an_existing_user() { // Arrange userRepo.Stub(repo => repo.IsExistingUser(theUser)).Return(true); userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false); // Act bool result = userSvc.CreateUser(theUser); // Assert userRepo.VerifyAllExpectations(); Assert.That(result, Is.False, "UserService.CreateUser() allowed multiple copies of same user to be created"); }


Si escribe sus pruebas antes de escribir su código, obtendrá mucho más de las pruebas de su unidad. Una de las razones por las que parece que sus pruebas no valen mucho es que no está obteniendo el valor de que sus pruebas impulsen el diseño. Escribir tus exámenes después es más que nada un ejercicio para ver si puedes recordar todo lo que puede salir mal. Escribir sus pruebas primero le hace pensar cómo implementaría realmente la funcionalidad.

Estas pruebas no son tan interesantes porque la funcionalidad que se implementa es bastante básica. La forma en que te estás burlando parece bastante estándar: burlar las cosas de las que depende la clase bajo prueba, no la clase bajo prueba. La capacidad de prueba (o el buen sentido del diseño) ya le ha llevado a implementar interfaces y a utilizar la inyección de dependencia para reducir el acoplamiento. Es posible que desee pensar en cambiar el manejo de errores, como otros han sugerido. Sería bueno saber por qué, aunque solo sea para mejorar la calidad de sus pruebas, CreateUser ha fallado, por ejemplo. Puede hacerlo con excepciones o con un parámetro de out (que es cómo funciona MembershipProvider, si no recuerdo mal).


Tiene razón: la simplicidad del servicio hace que estas pruebas no sean interesantes. No es hasta que obtenga más lógica comercial en el servicio, que obtendrá el valor de las pruebas.

Puede considerar algunas pruebas como estas:

CreateUser_fails_if_email_is_invalid() CreateUser_fails_if_username_is_empty()

Otro comentario: parece un olor de código, que tus métodos devuelven booleanos para indicar éxito o error. Es posible que tenga una buena razón para hacerlo, pero generalmente debe dejar que las excepciones se propaguen. También hace que sea más difícil escribir buenas pruebas, ya que tendrá problemas para detectar si su método falló por la "razón correcta", por ejemplo, podría escribir CreateUser_fails_if_email_is_invalid () - test como este:

[Test] public void CreateUser_fails_if_email_is_invalid() { bool result = userSvc.CreateUser(userWithInvalidEmailAddress); Assert.That(result, Is.False); }

y probablemente funcione con tu código existente. El uso del ciclo de refactorización rojo-verde de TDD mitigaría este problema, pero sería aún mejor poder detectar realmente que el método falló debido al correo electrónico no válido y no a otro problema.


Usted se enfrenta a la cuestión de los enfoques "clásicos" frente a los "simulacros" para las pruebas. O "verificación de estado" vs. "verificación de comportamiento" como lo describe Martin Fowler: http://martinfowler.com/articles/mocksArentStubs.html#ClassicalAndMockistTesting

Otro recurso excelente es el libro de Gerard Meszaros "Patrones de prueba xUnit: Código de prueba de refactorización"