¿Pruebas unitarias de métodos nulos?

170

¿Cuál es la mejor manera de probar un método que no devuelve nada? Específicamente en c #.

Lo que realmente estoy tratando de probar es un método que toma un archivo de registro y lo analiza para cadenas específicas. Las cadenas se insertan en una base de datos. Nada que no se haya hecho antes, pero al ser MUY nuevo en TDD, me pregunto si es posible probar esto o si es algo que realmente no se prueba.

jdiaz
fuente
55
No utilice el término "TDD" si eso no es lo que está haciendo. Estás haciendo pruebas unitarias, no TDD. Si estuvieras haciendo TDD, nunca tendrías una pregunta como "cómo probar un método". La prueba existiría primero, y luego la pregunta sería, "¿cómo hacer que pase esta prueba?" Pero si estaba haciendo TDD, su código se escribiría para la prueba (no al revés), y esencialmente terminaría respondiendo su propia pregunta. Su código se formateará de manera diferente como resultado de TDD, y este problema nunca ocurrirá. Solo aclarando.
Suamere

Respuestas:

151

Si un método no devuelve nada, es uno de los siguientes

  • imperativo : le está pidiendo al objeto que se haga algo a sí mismo ... por ejemplo, cambiar de estado (sin esperar ninguna confirmación ... se supone que se hará)
  • informativo : simplemente notificar a alguien que algo sucedió (sin esperar acción o respuesta) respectivamente.

Métodos imperativos: puede verificar si la tarea se realizó realmente. Verifique si el cambio de estado realmente tuvo lugar. p.ej

void DeductFromBalance( dAmount ) 

puede probarse verificando si el saldo publicado este mensaje es realmente menor que el valor inicial por dAmount

Métodos informativos: son raros como miembros de la interfaz pública del objeto ... por lo tanto, normalmente no se prueban por unidad. Sin embargo, si debe hacerlo, puede verificar si se realiza el manejo de una notificación. p.ej

void OnAccountDebit( dAmount )  // emails account holder with info

puede probarse verificando si el correo electrónico se está enviando

Publique más detalles sobre su método real y las personas podrán responder mejor.
Actualización : su método está haciendo 2 cosas. De hecho, lo dividí en dos métodos que ahora se pueden probar de forma independiente.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

La cadena [] se puede verificar fácilmente al proporcionar el primer método con un archivo ficticio y las cadenas esperadas. El segundo es un poco complicado ... puede usar un Mock (google o search stackoverflow en marcos de imitación) para imitar el DB o presionar el DB real y verificar si las cadenas se insertaron en la ubicación correcta. Revise este hilo para ver algunos buenos libros ... Recomiendo Pragmatic Unit Testing si tiene problemas.
En el código se usaría como

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
Gishu
fuente
1
hey gishu, buena respuesta. El ejemplo que diste ... ¿no son más pruebas de integración ...? y si es así, la pregunta sigue siendo, ¿cómo se prueban realmente los métodos de vacío ... tal vez es imposible?
andy
2
@andy: depende de su definición de 'pruebas de integración'. Los métodos imperativos generalmente cambian de estado, por lo que puede ser verificado por una prueba de unidad que interroga el estado del objeto. Los métodos informativos se pueden verificar mediante una prueba unitaria que se conecta a un oyente / colaborador simulado para garantizar que el sujeto de la prueba emita la notificación correcta. Creo que ambos pueden ser probados razonablemente a través de pruebas unitarias.
Gishu
@andy la base de datos puede ser burlada / separada por una interfaz de acceso permitiendo así que la acción sea probada por los datos pasados ​​al objeto simulado.
Peter Geiger
62

Prueba sus efectos secundarios. Esto incluye:

  • ¿Lanza alguna excepción? (Si es así, verifique que así sea. Si no es así, pruebe algunos casos de esquina que podrían serlo si no tiene cuidado; los argumentos nulos son lo más obvio).
  • ¿Juega bien con sus parámetros? (Si son mutables, ¿los muta cuando no debería y viceversa?)
  • ¿Tiene el efecto correcto sobre el estado del objeto / tipo al que lo está llamando?

Por supuesto, hay un límite para la cantidad de pruebas que puede realizar. Por lo general, no puede probar con cada entrada posible, por ejemplo. Pruebe de forma pragmática: lo suficiente como para darle la confianza de que su código está diseñado de manera adecuada e implementado correctamente, y lo suficiente como para actuar como documentación complementaria de lo que podría esperar una persona que llama.

Jon Skeet
fuente
31

Como siempre: ¡prueba lo que se supone que debe hacer el método!

¿Debería cambiar el estado global (uuh, código olor!) En alguna parte?

¿Debería llamar a una interfaz?

¿Debería arrojar una excepción cuando se llama con los parámetros incorrectos?

¿No debería arrojar una excepción cuando se llama con los parámetros correctos?

Deberia ...?

David Schmitt
fuente
11

Los tipos de retorno nulo / subrutinas son viejas noticias. No he hecho un tipo de devolución de Vacío (a menos que haya sido extremadamente flojo) en aproximadamente 8 años (desde el momento de esta respuesta, así que un poco antes de que se hiciera esta pregunta).

En lugar de un método como:

public void SendEmailToCustomer()

Cree un método que siga el paradigma int. TryParse () de Microsoft:

public bool TrySendEmailToCustomer()

Tal vez no haya ninguna información que su método necesite devolver para su uso a largo plazo, pero devolver el estado del método después de que realiza su trabajo es de gran utilidad para la persona que llama.

Además, bool no es el único tipo de estado. Hay varias veces en que una Subrutina hecha anteriormente podría devolver tres o más estados diferentes (Bueno, Normal, Malo, etc.). En esos casos, solo usarías

public StateEnum TrySendEmailToCustomer()

Sin embargo, aunque el Try-Paradigm responde de alguna manera a esta pregunta sobre cómo probar un retorno nulo, también hay otras consideraciones. Por ejemplo, durante / después de un ciclo "TDD", usted estaría "Refactorizando" y notaría que está haciendo dos cosas con su método ... rompiendo así el "Principio de responsabilidad única". Entonces eso debe ser atendido primero. En segundo lugar, es posible que haya idenetizado una dependencia ... está tocando datos "persistentes".

Si está haciendo las cosas de acceso a datos en el método en cuestión, necesita refactorizar en una arquitectura de n niveles o capas. Pero podemos suponer que cuando dice "Las cadenas se insertan en una base de datos", en realidad quiere decir que está llamando a una capa de lógica de negocios o algo así. Ya, asumiremos eso.

Cuando se instancia su objeto, ahora comprende que su objeto tiene dependencias. Esto es cuando necesita decidir si va a hacer una inyección de dependencia en el objeto o en el método. Eso significa que su constructor o el método en cuestión necesita un nuevo parámetro:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Ahora que puede aceptar una interfaz de su objeto de negocio / nivel de datos, puede simularla durante las pruebas unitarias y no tener dependencias ni temor a las pruebas de integración "accidentales".

Entonces, en su código en vivo, pasa un IBusinessDataEtcobjeto REAL . Pero en su Prueba de unidad, pasa un IBusinessDataEtcobjeto MOCK . En ese simulacro, puede incluir propiedades que no sean de interfaz int XMethodWasCalledCounto algo cuyos estados se actualicen cuando se invoquen los métodos de interfaz.

Por lo tanto, su Prueba de unidad pasará por su (s) Método (s) en cuestión, realizará cualquier lógica que tengan y llamará a uno o dos, o un conjunto seleccionado de métodos en su IBusinessDataEtcobjeto. Cuando haces tus Afirmaciones al final de tu Prueba de Unidad, tienes un par de cosas que probar ahora.

  1. El estado de la "subrutina", que ahora es un método Try-Paradigm.
  2. El estado de su IBusinessDataEtcobjeto simulado .

Para obtener más información sobre las ideas de inyección de dependencias en el nivel de construcción ... en lo que respecta a las pruebas unitarias ... consulte los patrones de diseño del generador. Agrega una interfaz y clase más para cada interfaz / clase actual que tenga, pero son muy pequeñas y proporcionan ENORMES aumentos de funcionalidad para una mejor Prueba de Unidad.

Suamere
fuente
La primera parte de esta gran respuesta sirve como un increíble consejo general para todos los programadores principiantes / intermedios.
pimbrouwers
¿No debería ser algo así public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad
1
@ A.Emad Buena pregunta, pero no. Controlar el flujo de su código confiando en lanzar excepciones es una mala práctica conocida desde hace mucho tiempo. Sin embargo, en voidmétodos, especialmente en lenguajes orientados a objetos, ha sido la única opción real. La alternativa más antigua de Microsoft es el Try-Paradigm que analizo, y paradigmas de estilo funcional como Monads / Maybes. Por lo tanto, los comandos (en CQS) aún pueden devolver información de estado valiosa en lugar de depender del lanzamiento, que es similar a un GOTO(que sabemos que es malo). Los lanzamientos (y los goto) son lentos, difíciles de depurar y no son una buena práctica.
Suamere
Gracias por aclararlo. ¿Es C # específico o es una mala práctica en general lanzar excepciones incluso en lenguajes como Java y C ++?
A.Emad
9

Prueba esto:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
Nathan Alard
fuente
1
Esto no debería ser necesario, pero se puede hacer como tal
Nathan Alard
¡Bienvenido a StackOverflow! Considere agregar alguna explicación a su código. Gracias.
Aurasphere
2
El ExpectedAttributeestá diseñado para hacer esta prueba más claramente.
Martin Liversage
8

Incluso puedes probarlo de esta manera:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
Reyan Chougle
fuente
1
Esta es la forma más simple, supongo.
Navin Pandit
5

tendrá algún efecto en un objeto ... consulta el resultado del efecto. Si no tiene un efecto visible, no vale la pena probar la unidad.

Keith Nicholas
fuente
4

Presumiblemente, el método hace algo y no regresa simplemente.

Suponiendo que este sea el caso, entonces:

  1. Si modifica el estado de su objeto propietario, entonces debe probar que el estado cambió correctamente.
  2. Si toma algún objeto como parámetro y modifica ese objeto, entonces debe probar que el objeto está modificado correctamente.
  3. Si arroja excepciones en ciertos casos, compruebe que esas excepciones se lanzan correctamente.
  4. Si su comportamiento varía en función del estado de su propio objeto, o de algún otro objeto, preestablezca el estado y pruebe que el método tiene el correcto a través de uno de los tres métodos de prueba anteriores).

Si nos hace saber qué hace el método, podría ser más específico.

David Arno
fuente
3

Use Rhino Mocks para establecer qué llamadas, acciones y excepciones podrían esperarse. Asumiendo que puedes burlarte o tropezar partes de tu método. Difícil de saber sin conocer algunos detalles sobre el método, o incluso el contexto.

paloma
fuente
1
La respuesta a cómo realizar una prueba unitaria nunca debe ser obtener una herramienta de terceros que lo haga por usted. Sin embargo, cuando una persona sabe cómo realizar pruebas unitarias, es aceptable usar una herramienta de terceros para que sea más fácil.
Suamere
2

Depende de lo que esté haciendo. Si tiene parámetros, pase simulacros que podría preguntar más adelante si se han llamado con el conjunto correcto de parámetros.

André
fuente
De acuerdo: verificar el comportamiento de los simulacros que prueban el método sería unidireccional.
Jeff Schumacher
0

Cualquiera que sea la instancia que esté utilizando para llamar al método nulo, simplemente puede usar,Verfiy

Por ejemplo:

En mi caso, esta _Loges la instancia y LogMessagees el método a probar:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

¿Los Verifylanzamientos son una excepción debido a la falla del método que fallará la prueba?

Shreya Kesharkar
fuente