Ваш вопрос раскрывает одну из самых сложных частей тестирования для разработчиков:
«Какого черта я проверяю?»
Ваш пример не очень интересен, поскольку он просто склеивает некоторые вызовы API, поэтому, если бы вы написали для него модульный тест, вы бы в итоге просто заявили, что методы были вызваны. Подобные тесты тесно связывают детали реализации с тестом. Это плохо, потому что теперь вам приходится менять тест каждый раз, когда вы меняете детали реализации вашего метода, потому что изменение деталей реализации нарушает ваши тесты!
Плохие тесты на самом деле хуже, чем отсутствие тестов вообще.
В вашем примере:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Хотя вы можете передавать макеты, в методе тестирования нет логики. Если вы попытаетесь выполнить юнит-тест для этого, это может выглядеть примерно так:
// 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);
}
Поздравляем, вы в основном скопировали детали реализации вашего DoIt()
метода в тест. Счастливое ведение.
Когда вы пишете тесты, вы хотите проверить ЧТО , а не HOW .
См. Black Box Testing для получения дополнительной информации.
WHAT - это название вашего метода (или, по крайней мере, так должно быть). HOW - это все мелкие детали реализации, которые существуют внутри вашего метода. Хорошие тесты позволяют вам поменять HOW , не нарушая WHAT .
Подумайте об этом так, спросите себя:
«Если я изменю детали реализации этого метода (без изменения государственного контракта), это нарушит мои тесты?»
Если ответ положительный, вы тестируете HOW , а не WHAT .
Чтобы ответить на ваш конкретный вопрос о тестировании кода с зависимостями файловой системы, скажем, у вас было что-то более интересное в отношении файла, и вы хотели сохранить закодированное в Base64 содержимое byte[]
в файл. Вы можете использовать потоки для этого, чтобы проверить, что ваш код работает правильно, без необходимости проверять , как это делает. Одним из примеров может быть что-то вроде этого (на 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");
}
В тесте используется ByteArrayOutputStream
, но в приложении (с использованием внедрения зависимостей) реальный StreamFactory (возможно, называемый FileStreamFactory) возвратил бы FileOutputStream
из outputStream()
и записал бы в File
.
Что было интересно в методе write
, так это то, что он записывал содержимое в кодировке Base64, поэтому мы тестировали его. Для вашего DoIt()
метода это будет более подходящим образом протестировано с помощью интеграционного теста .
.