Se ho un metodo che si chiama in una certa condizione, è possibile scrivere un test per verificare il comportamento? Mi piacerebbe vedere un esempio, non mi interessa la struttura o la lingua finta. Sto usando RhinoMocks in C# quindi sono curioso di sapere se è una caratteristica mancante del framework, o se sto fraintendendo qualcosa di fondamentale, o se è solo un'impossibilità.Come scrivere un test di Mockist di un metodo ricorsivo
risposta
Supponendo di voler fare qualcosa di simile a ottenere il nome del file da un percorso completo, ad esempio:
c:/windows/awesome/lol.cs -> lol.cs
c:/windows/awesome/yeah/lol.cs -> lol.cs
lol.cs -> lol.cs
e si ha:
public getFilename(String original) {
var stripped = original;
while(hasSlashes(stripped)) {
stripped = stripped.substringAfterFirstSlash();
}
return stripped;
}
e si desidera scrivere:
public getFilename(String original) {
if(hasSlashes(original)) {
return getFilename(original.substringAfterFirstSlash());
}
return original;
}
La ricorsione qui è un dettaglio di implementazione e non dovrebbe essere testato. Vuoi davvero essere in grado di passare tra le due implementazioni e verificare che producano lo stesso risultato: entrambi producono lol.cs per i tre esempi sopra.
Detto questo, poiché si ricorre per nome, anziché pronunciare thisMethod.again() ecc., In Ruby è possibile alias il metodo originale con un nuovo nome, ridefinire il metodo con il vecchio nome, richiamare il nuovo nominare e verificare se si finisce nel metodo appena definito.
def blah
puts "in blah"
blah
end
alias blah2 blah
def blah
puts "new blah"
end
blah2
Stai dicendo che in questo caso, un test unitario che verifica lo stato del metodo è abbastanza buono? – JeremyWeir
scusa, non capisco appieno la tua domanda. Nel mio esempio di percorso file, un test unitario che verifica l'output del metodo è sufficiente, o anche migliore di quello che verifica la ricorsione. Tuttavia, non conosco la tua situazione specifica, quindi potrebbe essere diverso. – miaubiz
@jayrdub - In generale, la verifica dello stato è esattamente ciò che vuoi fare il tuo test unitario. Controllare il valore restituito del metodo e/o le proprietà pubbliche dell'oggetto sottoposto a test. Tutto il resto è un dettaglio di implementazione e può cambiare durante il refactoring. – TrueWill
Non c'è nulla che controlli la profondità di stack/il numero di chiamate di funzione (ricorsiva) in qualsiasi framework di simulazione di cui sono a conoscenza. Tuttavia, l'unità che verifica che le precondizioni appropriate simulate forniscano le uscite corrette dovrebbe essere la stessa di una funzione non ricorsiva.
Ricorsione infinita che porta a un sovraccarico di stack, è necessario eseguire il debug separatamente, ma test di unità e mock non si sono mai sbarazzati di quel bisogno in primo luogo.
un metodo che si definisce sotto una certa condizione, è possibile scrivere un test per verificare il comportamento?
Sì. Tuttavia, se è necessario testare la ricorsione, è meglio separare il punto di ingresso nella ricorsione e la fase di ricorsione a scopo di test.
In ogni caso, ecco l'esempio di come testarlo se non è possibile farlo. Non hai proprio bisogno di alcuna derisione:
// 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 );
}
Stai fraintendendo lo scopo degli oggetti finti. I mock (in senso Mockist) sono usati per testare le interazioni comportamentali con le dipendenze del sistema sotto test.
Così, per esempio, si potrebbe avere qualcosa di simile:
interface IMailOrder
{
void OrderExplosives();
}
class Coyote
{
public Coyote(IMailOrder mailOrder) {}
public void CatchDinner() {}
}
Coyote dipende IMailOrder. Nel codice di produzione, un'istanza di Coyote passerebbe un'istanza di Acme, che implementa IMailOrder. (Ciò potrebbe essere fatto tramite Injection dipendenza manuale o tramite un framework DI.)
Si desidera testare il metodo CatchDinner e verificare che chiami OrderExplosives.Per fare ciò, è:
- Creare un oggetto fittizio che implementa IMailOrder e creare un'istanza di Coyote (il sistema in prova) passando l'oggetto fittizio al suo costruttore. (Disponi)
- Chiama CatchDinner. (Legge)
- Chiedere all'oggetto mock di verificare che sia stata soddisfatta una determinata aspettativa (chiamata OrderExplosives). (Asserire)
Quando si impostano le aspettative sull'oggetto fittizio può dipendere dal proprio sistema di derisione (isolamento).
Se la classe o il metodo che si sta testando non ha dipendenze esterne, non è necessario (o si desidera) utilizzare gli oggetti fittizi per quella serie di test. Non importa se il metodo è ricorsivo o meno.
Generalmente si desidera testare le condizioni al contorno, quindi è possibile testare una chiamata che non dovrebbe essere ricorsiva, una chiamata con una singola chiamata ricorsiva e una chiamata ricorsiva profonda. (Miaubiz ha un buon punto su ricorsione essere un dettaglio di implementazione, però.)
EDIT: Con "chiamata" ultimo comma, volevo dire una chiamata con parametri o oggetto di stato che farebbe scattare una determinata profondità di ricorsione. Raccomando anche di leggere The Art of Unit Testing.
EDIT 2: esempio di codice di prova utilizzando Moq:
var mockMailOrder = new Mock<IMailOrder>();
var wily = new Coyote(mockMailOrder.Object);
wily.CatchDinner();
mockMailOrder.Verify(x => x.OrderExplosives());
"Se la classe o il metodo che stai testando non ha dipendenze esterne, non hai bisogno (o vuoi) di usare oggetti mock per quella serie di test, non importa se il metodo è ricorsivo o meno." Questo è il ruolo che dovevo ricordare, grazie. Mi è piaciuta la tua risposta, ma è stata scelta automaticamente prima che potessi. – JeremyWeir
@jayrdub - Grazie! :) – TrueWill
Ecco il mio approccio 'contadino' (in Python, testate, vedere i commenti per la logica)
Nota che l'attuazione dettaglio "esposizione" è fuori questione qui, poiché ciò che si sta testando è l'architettura sottostante che capita di essere utilizzata dal codice "di primo livello". Quindi, testarlo è legittimo e ben educato (spero anche che sia quello che hai in mente).
Il codice (l'idea principale è quella di andare da un singolo ma "verificabile" funzione ricorsiva per una coppia equivalente di funzioni ricorsivamente dipendenti (e quindi testabili)):
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 prova:
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)
L'output:
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
non è chiaro per me. Cosa stanno cercando di testare esattamente? Che il metodo si definisce "in determinate condizioni" (che lo "stack di chiamate" seguirà un certo percorso "in determinate condizioni") o qualcos'altro? – Ando