2010-02-11 8 views
8

Si tengo un método que se llama bajo ciertas condiciones, ¿es posible escribir una prueba para verificar el comportamiento? Me encantaría ver un ejemplo, no me importa el marco o el lenguaje falso. Estoy usando RhinoMocks en C#, así que tengo curiosidad si es una característica que falta en el framework, o si estoy entendiendo mal algo fundamental, o si es simplemente una imposibilidad.Cómo escribir una prueba de Mockist de un método recursivo

+0

no es claro para mí. ¿Qué están tratando de probar exactamente? ¿Que el método se llama a sí mismo "bajo ciertas condiciones" (que la "pila de llamadas" seguirá cierta ruta "bajo ciertas condiciones") o algo más? – Ando

Respuesta

3

asumiendo que usted quiere hacer algo como obtener el nombre del archivo desde una ruta completa, por ejemplo:

c:/windows/awesome/lol.cs -> lol.cs 
c:/windows/awesome/yeah/lol.cs -> lol.cs 
lol.cs -> lol.cs 

y usted tiene:

public getFilename(String original) { 
    var stripped = original; 
    while(hasSlashes(stripped)) { 
    stripped = stripped.substringAfterFirstSlash(); 
    } 
    return stripped; 
} 

y que desea escribir:

public getFilename(String original) { 
    if(hasSlashes(original)) { 
    return getFilename(original.substringAfterFirstSlash()); 
    } 
    return original; 
} 

Recursión aquí es un detalle de implementación y no debe ser probado. Realmente desea poder cambiar entre las dos implementaciones y verificar que produzcan el mismo resultado: ambas producen lol.cs para los tres ejemplos anteriores.

Dicho esto, porque está repursando por nombre, en lugar de decir thisMethod.again() etc., en Ruby puede alias el método original a un nuevo nombre, redefinir el método con el nombre anterior, invocar el nuevo nombra y comprueba si terminas en el nuevo método definido.

def blah 
    puts "in blah" 
    blah 
end 

alias blah2 blah 

def blah 
    puts "new blah" 
end 

blah2 
+0

¿Está diciendo que en este caso, una prueba unitaria que verifique el estado del método es lo suficientemente buena? – JeremyWeir

+0

lo siento, no entiendo completamente su pregunta. En mi ejemplo de ruta de archivo, una prueba de unidad que verifica el resultado del método es suficiente, o incluso mejor que una que verifica la recursión. Sin embargo, no conozco su situación específica, por lo que podría ser diferente. – miaubiz

+0

@jayrdub - En general, la verificación de estado es exactamente lo que quiere que hagan las pruebas de su unidad. Compruebe el valor de retorno del método y/o las propiedades públicas del objeto bajo prueba. Todo lo demás es un detalle de implementación y puede cambiar durante la refactorización. – TrueWill

1

No hay nada que vigile la profundidad de la pila/cantidad de llamadas (recursivas) en cualquier marco de burla que conozco. Sin embargo, la prueba unitaria de que las condiciones previas apropiadas burladas proporcionan las salidas correctas debe ser igual que burlarse de una función no recursiva.

Recursión infinita que conduce a un desbordamiento de la pila tendrá que depurar por separado, pero las pruebas unitarias y los simulacros nunca se han librado de esa necesidad en primer lugar.

6

un método que llama a sí mismo bajo ciertas condiciones, es posible escribir una prueba para verificar el comportamiento?

Sí. Sin embargo, si necesita probar la recursión, es mejor separar el punto de entrada en la recursión y el paso de recursión para fines de prueba.

De todos modos, aquí está el ejemplo de cómo probarlo si no puede hacer eso. Realmente no necesitas ninguna burla:

// Class under test 
public class Factorial 
{ 
    public virtual int Calculate(int number) 
    { 
     if (number < 2) 
      return 1 
     return Calculate(number-1) * number; 
    } 
} 

// The helper class to test the recursion 
public class FactorialTester : Factorial 
{ 
    public int NumberOfCalls { get; set; } 

    public override int Calculate(int number) 
    { 
     NumberOfCalls++; 
     return base.Calculate(number) 
    } 
}  

// Testing 
[Test] 
public void IsCalledAtLeastOnce() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(1); 
    Assert.GreaterOrEqual(1, tester.NumberOfCalls ); 
} 
[Test] 
public void IsCalled3TimesForNumber3() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(3); 
    Assert.AreEqual(3, tester.NumberOfCalls ); 
} 
4

Estás malinterpretando el propósito de los objetos simulados. Los simulacros (en el sentido Mockist) se usan para probar interacciones de comportamiento con dependencias del sistema bajo prueba.

Así, por ejemplo, es posible que tenga algo como esto:

interface IMailOrder 
{ 
    void OrderExplosives(); 
} 

class Coyote 
{ 
    public Coyote(IMailOrder mailOrder) {} 

    public void CatchDinner() {} 
} 

Coyote depende de IMailOrder. En el código de producción, a una instancia de Coyote se le pasará una instancia de Acme, que implementa IMailOrder. (Esto se puede hacer mediante Inyección de Dependencia manual o mediante un marco DI.)

Desea probar el método CatchDinner y verificar que llama a OrderExplosives.Para ello, usted:

  1. crear un objeto que implementa simulacro IMailOrder y crear una instancia de Coyote (el sistema bajo prueba) pasando el objeto de burla de su constructor. (Organizar)
  2. Llamar CatchDinner. (Ley)
  3. Solicite al objeto simulado que verifique que se cumplió con una expectativa dada (Se llama a OrderExplosives). (Afirmar)

Cuando configura las expectativas en el objeto simulado puede depender de su marco de burla (aislamiento).

Si la clase o método que está probando no tiene dependencias externas, no necesita (ni desea) utilizar objetos simulados para ese conjunto de pruebas. No importa si el método es recursivo o no.

Por lo general, desea probar las condiciones de contorno, por lo que puede probar una llamada que no debe ser recursiva, una llamada con una sola llamada recursiva y una llamada profundamente recursiva. (Miaubiz tiene un buen punto sobre la recursividad es un detalle de implementación, sin embargo.)

EDIT: Por "llamada" en el último párrafo me refería a una llamada con parámetros o estado que daría lugar a un nivel de recursividad determinado objeto. También recomendaría leer The Art of Unit Testing.

EDIT 2: Ejemplo código de prueba usando Moq:

var mockMailOrder = new Mock<IMailOrder>(); 
var wily = new Coyote(mockMailOrder.Object); 

wily.CatchDinner(); 

mockMailOrder.Verify(x => x.OrderExplosives()); 
+0

"Si la clase o método que está probando no tiene dependencias externas, no necesita (ni desea) utilizar objetos simulados para ese conjunto de pruebas. No importa si el método es recursivo o no". Esa es la parte que necesitaba que me recordaran, gracias. Me gustó tu respuesta mejor, pero la elegí automáticamente antes de que pudiera. – JeremyWeir

+0

@jayrdub - ¡Gracias! :) – TrueWill

0

Aquí está mi enfoque de 'campesino' (en Python, probado, ver los comentarios de la lógica)

en cuenta que la ejecución la "exposición" de detalle está fuera de cuestión aquí, ya que lo que está probando es la arquitectura subyacente que es utilizada por el código de "nivel superior". Por lo tanto, probarlo es legítimo y de buen comportamiento (también espero que sea lo que tienes en mente).

El código (la idea principal es ir desde una sola pero "untestable" función recursiva a un par equivalente de funciones de forma recursiva dependientes (y por tanto comprobables)):

def factorial(n): 
    """Everyone knows this functions contract:) 
    Internally designed to use 'factorial_impl' (hence recursion).""" 
    return factorial_impl(n, factorial_impl) 

def factorial_impl(n, fct=factorial): 
    """This function's contract is 
    to return 'n*fct(n-1)' for n > 1, or '1' otherwise. 

    'fct' must be a function both taking and returning 'int'""" 
    return n*fct(n - 1) if n > 1 else 1 

La prueba:

import unittest 

class TestFactorial(unittest.TestCase): 

    def test_impl(self): 
     """Test the 'factorial_impl' function, 
     'wiring' it to a specially constructed 'fct'""" 

     def fct(n): 
      """To be 'injected' 
      as a 'factorial_impl''s 'fct' parameter""" 
      # Use a simple number, which will 'show' itself 
      # in the 'factorial_impl' return value. 
      return 100 

     # Here we must get '1'. 
     self.assertEqual(factorial_impl(1, fct), 1) 
     # Here we must get 'n*100', note the ease of testing:) 
     self.assertEqual(factorial_impl(2, fct), 2*100) 
     self.assertEqual(factorial_impl(3, fct), 3*100) 

    def test(self): 
     """Test the 'factorial' function""" 
     self.assertEqual(factorial(1), 1) 
     self.assertEqual(factorial(2), 2) 
     self.assertEqual(factorial(3), 6) 

La salida:

Finding files... 
['...py'] ... done 
Importing test modules ... done. 

Test the 'factorial' function ... ok 
Test the 'factorial_impl' function, ... ok 

---------------------------------------------------------------------- 
Ran 2 tests in 0.000s 

OK 
Cuestiones relacionadas