Как проверить класс, который подключается к FTP-серверу?

Я разрабатываю обновление для своего приложения. До сих пор я создал почти все модульные тесты, но я понятия не имею, как протестировать определенный класс, который подключается к FTP-серверу и загружает новые версии.

Чтобы протестировать этот класс, должен ли я создать тестовый сервер FTP и использовать его в своих модульных тестах? Если да, то как я могу убедиться, что этот FTP-сервер всегда соответствует моим тестам? Должен ли я вручную создавать каждый файл, который мне понадобится до начала теста, или я должен автоматизировать его в своем тестовом классе (методы срыва и настройки)?

Этот вопрос также относится к классам тестирования модулей, которые подключаются к любому серверу.

ИЗМЕНИТЬ

Я уже высмеиваю свой ftp-класс, поэтому мне не всегда нужно подключаться к ftp-серверу в других тестах.

Позвольте мне посмотреть, правильно ли я понял, что сказал Уоррен в своем комментарии:

Я бы сказал, что как только вы разговариваете с отдельным приложением по TCP/IP мы должны назвать это "интеграционными тестами". Один больше не тестирует единица или метод, но система.

Когда unit test должен взаимодействовать с другим приложением (которое может быть HTTP-сервером или FTP-сервером), это уже не unit test, а сервер интеграции? Если это так, я делаю это неправильно, пытаясь использовать методы модульного тестирования для создания этого теста? Правильно ли говорить, что я не должен unit test этот класс? Это имеет смысл для меня, потому что это кажется большой работой для unit test.

Ответ 1

При тестировании всегда всегда нужно ответить на вопрос: что тестируется - то есть масштаб теста.

Итак, если вы тестируете реализацию FTP-сервера, вам придется создать FTP-клиент.

Если вы тестируете FTP-клиент, вам придется создать FTP-сервер.

Таким образом, вы должны уменьшить размер теста до тех пор, пока не достигнете унитарного уровня.

Это может быть, например, для вашей цели:

  • Получение списка текущих файлов, установленных для приложения;
  • Получение списка доступных файлов удаленно;
  • Получение обновления файла;
  • Проверка правильности файла (контрольная сумма?);
  • и т.д.

Каждый испытуемый элемент должен иметь некоторые макеты и заглушки. См. в этой статье о различии между ними. Короче говоря (AFAIK), заглушка - всего лишь объект эмуляции, который всегда работает. И макет (который должен быть уникальным в каждом тесте) - это элемент, который может изменить результат теста (пройти или выйти из строя).

Для конкретной цели FTP-соединения вы можете, например, (при тестировании клиентской стороны) есть некоторые заглушки, которые возвращают список файлов, и макет, который будет проверять несколько возможных проблем FTP-сервера (тайм-аут, потеря соединения, неправильный контент). Тогда ваша клиентская сторона будет реагировать так, как ожидалось. Ваш макет может быть настоящим экземпляром FTP-сервера, но который будет вести себя так, как ожидается, чтобы вызвать все возможные ошибки. Как правило, каждая ошибка должна вызывать исключение, которое должно отслеживаться тестовыми блоками, чтобы пройти/пропустить каждый тест.

Это немного сложно написать хороший тестовый код. Метод, основанный на проверке, сначала занимает немного времени, но всегда лучше в долгосрочной перспективе. Хорошая книга здесь обязательна или, по крайней мере, некоторые справочные статьи (например, Мартин Фаулер, как указано выше). В Delphi использование интерфейсов и принципов SOLID может помочь вам написать такой код и создать заглушки/макеты для написания ваших тестов.

Из моего эксперимента каждый программист иногда может быть потерян при написании тестов... хорошая тестовая запись может быть более трудоемкой, чем запись функций, в некоторых случаях... вы предупреждаетесь! Каждый тест должен рассматриваться как признак, и его стоимость должна оцениваться: стоит ли это? Не подходит ли еще один тест здесь? Является ли мой тест отключенным от функции, которую он тестирует? Это еще не проверено? Я проверяю свой код или стороннюю/библиотечную функцию?

Из темы, но мои два цента: HTTP/1.1 может быть лучшим кандидатом в настоящее время, чем FTP, даже для обновления файла. Вы можете возобновить HTTP-соединение, загружать контент HTTP кусками параллельно, и этот протокол более прокси-сервер, чем FTP. И гораздо проще разместить некоторый HTTP-контент, чем FTP (на некоторых FTP-серверах также известны проблемы с безопасностью). Большинство обновлений программного обеспечения в настоящее время выполняются через HTTP/1.1, а не FTP (например, продукты Microsoft или большинство репозиториев Linux).

EDIT:

Вы можете утверждать, что вы проводите интеграционные тесты, когда используете удаленный протокол. Это может иметь смысл, но ИМХО это не то же самое.

Насколько я понимаю, тесты интеграции выполняются, когда вы позволяете всем вашим компонентам работать вместе с реальным приложением, а затем проверяйте, что они работают должным образом. Мое предложение о тестировании FTP заключается в том, что вы издеваетесь над FTP-сервером, чтобы явно протестировать все возможные проблемы (время ожидания, соединение или ошибка передачи...). Это не что иное, как интеграционные тесты: покрытие кода намного больше. И вы проверяете только часть кода, а не всю интеграцию кода. Это связано не только с тем, что вы используете какое-то удаленное соединение, которое вы выполняете с помощью интеграционных тестов: это все еще унитарное тестирование.

И, конечно же, интеграционные и системные испытания должны проводиться после унитарных испытаний. Но унифицированные тесты FTP-клиента могут издеваться над FTP-сервером, запускать его локально, но тестировать все потенциальные проблемы, которые могут возникнуть в реальной большой глобальной сети.

Ответ 2

Если вы используете компонент Indy 10 TIdFTP, вы можете использовать класс Indy TIdIOHandlerStream для подделки FTP-соединения без фактического физического подключения к реальному FTP-серверу.

Создайте объект TStream, например TMemoryStream или TStringStream, который содержит ответы FTP, которые вы ожидаете получить TIdFTP для всех команд, которые он отправляет (используйте сниффер пакетов, чтобы захватить их заранее, чтобы дать вы идете о том, что вам нужно включить), и поместите копию вашего файла обновления в локальную папку, куда вы обычно загружаете. Создайте объект TIdIOHandlerStream и назначьте TStream как его ReceiveStream, затем назначьте IOHandler свойству TIdFTP.IOHandler перед вызовом Connect().

Например:

ResponseData := TStringStream.Create(
  '220 Welcome' + EOL +
  ... + // login responses here, etc...
  '150 Opening BINARY mode data connection for filename.ext' + EOL +
  '226 Transfer finished' + EOL +
  '221 Goodbye' + EOL);

IO := TIdIOHandlerStream.Create(FTP, ResponseData); // TIdIOHandlerStream takes ownership of ResponseData by default

FTP.IOHandler := IO;
FTP.Passive := False;  // Passive=True does not work under this setup

FTP.Connect;
try
  FTP.Get('filename.ext', 'c:\path\filename.ext');
  // copy your test update file to 'c:\path\filename.ext'...
finally
  FTP.Disconnect;
end;

Ответ 3

Модульные тесты должны быть быстрыми, молниеносно. Все, что замедляет их, не позволяет вам не запускать их.

Они также должны быть последовательными от одного запуска к другому. Тестирование фактической передачи файлов приведет к возможности случайных сбоев в ваших модульных тестах.

Если класс, который вы тестируете, не более чем переносит api из библиотеки ftp, которую вы используете, тогда вы достигли одной из границ вашего приложения, вам не нужно тестировать unit Это. (Ну, иногда вы это делаете. Его называют поисковым тестированием, но обычно их отбрасывают, как только вы получаете ответ)

Если, однако, в классе есть какая-то логика, вы должны попробовать протестировать ее отдельно от фактического api. Вы делаете это, создавая обертку для ftp api. Затем в ваших модульных тестах вы создаете тестовый двойник, который может стоять в качестве замены для обертки. Существует множество вариантов, которые имеют разные названия: заглушка, подделка, макет объекта. В нижней части вы хотите убедиться, что ваши юнит-тесты изолированы от любого внешнего воздействия. A unit test со спорадическим поведением меньше, чем бесполезно.

Тестирование фактического механизма передачи файлов должно выполняться при тестировании интеграции, которое обычно выполняется реже, потому что оно медленнее. Даже при тестировании интеграции вы захотите попробовать как можно больше контролировать тестовую среду. (т.е. тестирование с ftp-сервером в локальной сети, которая настроена на имитацию производственного сервера).

И помните, вы никогда не поймете все впереди. Ошибки проскальзывают независимо от того, насколько хороши тесты. Просто убедитесь, что когда они это сделают, вы добавите еще один тест, чтобы поймать их в следующий раз.

Я бы порекомендовал либо купить, либо проверить копию XUnit Test Patterns от Джерарда Мезароса. Это сокровищница полезной информации о том, что/когда/как unit test.

Ответ 4

Просто заимствуйте демоверсию FTP или HTTP Server, которая поставляется с любым желаемым компонентом сокета, который вы предпочитаете (Indy, ICS или что-то еще). Мгновенный сервер тестирования.

Я бы поместил его в папку с инструментами для моих модульных тестов. Я могу написать код, который проверяет, является ли TestFtpServer.exe уже живым, а если нет, запустите его.

Я бы оставил его вне моего пространства памяти приложения unit test, таким образом, отдельный процесс.

Обратите внимание, что к тому времени, как вы попадаете на операции FTP-сервера, модульное тестирование действительно должно называться "интеграционным тестированием".

Я бы не вручную создавал файлы из моего unit test. Я бы ожидал, что мой код должен быть проверен с помощью управления версиями и, как есть, собрать из пакетного файла, который запускает мою тестовую программу, которая знает о подпапке под названием "Инструменты", которая содержит EXE и, возможно, папку с именем ServerData и LocalData, которые могут использоваться для хранения данных, которые начинаются на сервере, и переносятся в локальное приложение unit test. Возможно, вы можете взломать ваш демонстрационный сервер, чтобы он завершил часть сеанса (когда вы хотите протестировать сбои), но я до сих пор не думаю, что вы получите хорошее покрытие.

Примечание. Если вы делаете автоматические обновления, я думаю, что никакое количество модульных испытаний не будет сокращать его. Вам нужно иметь дело с множеством потенциальных проблем, связанных с Интернетом. Что происходит, когда ваше имя хоста не разрешается? Что происходит, когда загрузка проходит частично и не удается? Автоматическое обновление не очень хорошо сочетается с возможностями модульного тестирования.

Ответ 5

Напишите пару целенаправленных интеграционных тестов для одного компонента, который знает, как общаться с FTP-сервером. Для этих тестов вам нужно будет запустить FTP-сервер перед каждым тестом, поместить туда любые файлы, необходимые для тестирования, и после завершения тестирования сервера.

При этом во всех других тестах вы не будете использовать компонент, который действительно подключается к FTP-серверу, но вы будете использовать фальшивую или макетную версию (которая поддерживается некоторой структурой данных в памяти вместо реальные файлы и сетевые сокеты). Таким образом, вы можете написать модульные тесты, которым не нужен FTP-сервер или сетевое подключение, для всего остального, кроме клиентского компонента FTP.

В дополнение к этим тестам может быть желательно также иметь некоторые сквозные тесты, которые запускают всю программу (в отличие от интегральных тестов на уровне компонентов) и подключают настоящий FTP-сервер. Сквозные тесты не могут охватывать все угловые случаи (в отличие от модульных тестов), но они могут помочь решить проблемы интеграции.