Diciamo che stiamo testando FooClass
con il seguente metodo:
public void Foo(string stringParameter, int intParameter, Action<Bar> successCallback, Action<Exception> errorCallback);
Se la chiamata a Foo
ha esito positivo, il successCallback
verrà chiamato con il risultato di Foo
nella forma di un oggetto Bar
. Se fallisce, il errorCallback
verrà chiamato con Exception
.
Quindi i test avranno un aspetto simile a questo:
[TestMethod]
public void Foo_UnderGivenConditions_WeExpectAGivenResult()
{
//Arrange
var fooObject = CreateFooObjectWithGivenConditions( ... );
//Act
fooObject.Foo(String.Empty, 0, (bar) => { ... }, (error) => { ... });
//Assert
Assert.AreEqual(..., ...);
}
Ora ci sono molti test su questo Foo
-method, tutti contenenti una chiamata a Foo
, ma non tutti i test si prenderanno cura di tutti i parametri. Alcuni possono fornire valori di stringa diversi, ma non si preoccupano del parametro int, e alcuni dovranno fornire un callback di errore per affermare che viene generata l'eccezione giusta e così via.
Ora, poiché tutti i parametri sono obbligatori, dobbiamo passare un valore stringa al metodo Foo
, anche se il valore non ha significato per il test. Ci sarà un sacco di "I don't care"
o "some text"
. Quando arriva qualcun altro e legge il test, deve considerare se il valore dato ha effettivamente un significato per il risultato del test o meno. Lo stesso vale per i callback. A volte abbiamo bisogno dei callback per ottenere il valore del risultato o l'eccezione, ma il più delle volte no.
Quindi, implementa un metodo di estensione, PerformFoo
, che imposta automaticamente tutti i parametri:
public static Bar PerformBar(this FooClass fooObject, string stringParameter = "some text", int intParameter = 0, Action<Bar> successCallback = null, Action<Exception> errorCallback = null);
{
Bar result = null;
var ourCallback = (bar) =>
{
result = bar;
if (successCallback != null)
sucessCallback.invoke();
}
fooObject.Foo(stringParameter, intParameter, ourCallback, errorCallback);
return result;
}
Il metodo di estensione chiamerà Foo
con i parametri predefiniti e restituirà anche il risultato se viene chiamato successCallback. Questo ci permette di cambiare la parte del test Act in qualcosa del tipo:
//Act, all we care about is the string parameter:
fooObject.PerformFoo("A string that we care about");
//Act, we need the resulting bar when the int parameter is 10:
var bar = fooObject.PerformFoo(intParameter: 10);
//Act, we still needs to provide a callback to get the exception
fooObject.PerformFoo("SomeInvalidValueCausingAnException", errorCallback: (error) => { exceptionThrown = error; });
Quindi le domande sarebbero:
- Questo rende i test più leggibili?
- È più facile ottenere ciò che il test verifica realmente?
- Il fatto che chiamiamo
PerformFoo
, che non esiste realmente nella classe in esame, rende il test meno utile come documentazione? - Abbandona il metodo di estensione per un metodo normale prendendo fooObject come primo parametro meno di una "bugia"? (ad esempio
PerformFoo(fooObject, intParameter: 10)
)
Quanto lontano farai per rendere i tuoi test puliti e chiari?