Votre question met en évidence l'une des parties les plus difficiles des tests pour les développeurs qui débutent dans ce domaine :
"Qu'est-ce que je teste, bon sang ?"
Votre exemple n'est pas très intéressant parce qu'il ne fait que coller quelques appels d'API ensemble. Si vous deviez écrire un test unitaire pour cet exemple, vous finiriez par affirmer que les méthodes ont été appelées. Les tests de ce type couplent étroitement les détails de votre implémentation au test. C'est mauvais parce que maintenant vous devez changer le test chaque fois que vous changez les détails de l'implémentation de votre méthode parce que changer les détails de l'implémentation casse votre (vos) test(s) !
Avoir de mauvais tests est en fait pire que de ne pas en avoir du tout.
Dans votre exemple :
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Bien que vous puissiez passer des mocks, il n'y a pas de logique dans la méthode à tester. Si vous deviez tenter un test unitaire pour ceci, il pourrait ressembler à quelque chose comme ceci :
// Assuming that zipper, fileSystem, and runner are mocks
void testDoIt()
{
// mock behavior of the mock objects
when(zipper.Unzip(any(File.class)).thenReturn("some path");
when(fileSystem.Open("some path")).thenReturn(mock(IFakeFile.class));
// run the test
someObject.DoIt(zipper, fileSystem, runner);
// verify things were called
verify(zipper).Unzip(any(File.class));
verify(fileSystem).Open("some path"));
verify(runner).Run(file);
}
Félicitations, vous avez essentiellement copié-collé les détails de l'implémentation de votre DoIt()
dans un test. Bonne maintenance.
Lorsque vous écrivez des tests, vous voulez tester le QUOI et non le COMMENT . Voir Test boîte noire pour plus.
El QUOI est le nom de votre méthode (ou du moins il devrait l'être). Le site COMMENT sont tous les petits détails d'implémentation qui se trouvent dans votre méthode. De bons tests vous permettent d'échanger les COMMENT sans rompre le QUOI .
Pensez-y de cette façon, demandez-vous :
"Si je change les détails de l'implémentation de cette méthode (sans modifier le contrat public), est-ce que cela va casser mon ou mes tests ?".
Si la réponse est oui, vous êtes en train de tester la COMMENT et non le QUOI .
Pour répondre à votre question spécifique sur le test de code avec des dépendances du système de fichiers, disons que vous avez quelque chose d'un peu plus intéressant avec un fichier et que vous voulez sauvegarder le contenu encodé en Base64 d'un fichier de type byte[]
vers un fichier. Vous pouvez utiliser des flux pour tester que votre code fait la bonne chose sans avoir à vérifier les éléments suivants comment il le fait. Un exemple pourrait être quelque chose comme ceci (en Java) :
interface StreamFactory {
OutputStream outStream();
InputStream inStream();
}
class Base64FileWriter {
public void write(byte[] contents, StreamFactory streamFactory) {
OutputStream outputStream = streamFactory.outStream();
outputStream.write(Base64.encodeBase64(contents));
}
}
@Test
public void save_shouldBase64EncodeContents() {
OutputStream outputStream = new ByteArrayOutputStream();
StreamFactory streamFactory = mock(StreamFactory.class);
when(streamFactory.outStream()).thenReturn(outputStream);
// Run the method under test
Base64FileWriter fileWriter = new Base64FileWriter();
fileWriter.write("Man".getBytes(), streamFactory);
// Assert we saved the base64 encoded contents
assertThat(outputStream.toString()).isEqualTo("TWFu");
}
Le test utilise un ByteArrayOutputStream
mais dans l'application (en utilisant l'injection de dépendances), la véritable StreamFactory (peut-être appelée FileStreamFactory) retournerait FileOutputStream
de outputStream()
et écrirait à un File
.
Ce qui était intéressant dans le write
La méthode utilisée ici est qu'elle écrit le contenu en code Base64, c'est donc ce que nous avons testé. Pour votre DoIt()
il serait plus approprié de tester cette méthode avec une test d'intégration .