- le dipendenze dell'oggetto da testare devono essere iniettate;
- l'oggetto deve dipendere da interfacce e non da altri oggetti;
- suddividere le responsabilità;
- utilizzare un framework di mocking;
Facciamo
qualche esempio:
public
class
Foo
{
private
readonly
IRepository
_repository;
private
readonly
IOutput
_output;
public
Foo(IRepository
repository, IOutput
output)
{
_repository
= repository;
_output
= output;
}
public
void
DoSomething(int
value)
{
IEnumerable<Person>
items = _repository.Load(value);
foreach
(Person
item in
items)
{
if
(item.Age < 10)
continue;
_output.Display(item.Age);
}
}
}
Prima
di scrivere i test per verificare i comportamenti, impostiamo il
setup:
[SetUp]
public
void
Setup()
{
_repositoryMock
= new
Mock<IRepository>();
_outputMock
= new
Mock<IOutput>();
_sut
= new
Foo(_repositoryMock.Object,
_outputMock.Object);
}
Scriviamo
un test per verificare di chiamare il metodo load.
[Test]
public
void
When_call_DoSomething_should_load_list_of_persons()
{
_repositoryMock.Setup(m
=> m.Load(It.IsAny<int>()))
.Returns(new
Person[0]);
_sut.DoSomething(1);
_repositoryMock.Verify(m
=> m.Load(It.Is<int>(p=>p
== 1)));
}
Scriviamo
un test per verificare che viene chiamato il metodo Display perchè
Load restituisce un item con la property Age maggiore di 9:
[Test]
public
void
When_age_is_greater_than_nine_years_should_create_output()
{
_repositoryMock
.Setup(m
=> m.Load(
It.IsAny<int>() ))
.Returns(new[]
{ new
Person
{ Age = 10 } });
_sut.DoSomething(
It.IsAny<int>() );
_outputMock.Verify(m
=> m.Display(
It.Is<int>(p=>p == 10)));
}
Scriviamo
un test per verificare che non viene chiamato il metodo Display
perché Load restituisce un solo item con la property Age minore di
10:
[Test]
public
void
When_age_is_less_than_ten_years_should_not_create_output()
{
_repositoryMock
.Setup(m
=> m.Load(It.IsAny<int>()))
.Returns(new[]
{new
Person
{Age = 9}});
_outputMock
.Setup(m
=> m.Display(It.IsAny<int>()))
.Throws(new
AssertionException("Cannot
call Display"));
_sut.DoSomething(It.IsAny<int>());
}
Rispondiamo
alla domanda con delle considerazioni. Se scriviamo un test pensando
ai comportamenti che dobbiamo verificare, risulterà piú naturale
scrivere le interfacce che esprimono quei comportamenti. Le
responsabilità saranno suddivise senza necessariamente scrivere le
classi che implementano le interfacce. Il codice risulterà più
semplice e leggibile (sia per la classe che per il test).