Как протестировать реализацию интернет-протокола? - PullRequest
6 голосов
/ 04 сентября 2010

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

Мой SieveClient объект использует два других объекта для сетевого взаимодействия:CocoaAsyncSocket и мой собственный SaslConn объект, который является моей оболочкой для библиотеки Cyrus SASL для обработки методов аутентификации.Для тестирования мне нужно заменить их на фиктивные объекты.Я собираюсь использовать для этого фреймворк OCMock.Я не совсем уверен, как это сделать, поскольку объект SieveClient должен сам создавать эти объекты.Прямо сейчас я перезаписываю (приватные) сеттеры для этого объекта, чтобы всегда устанавливать мои фиктивные объекты, используя метод OCMocks partialMockForObject:.Но мне кажется, что это неправильно.Любые идеи, как это можно решить лучше?

Другая часть, с которой у меня проблемы, это сама розетка.Чтобы иметь возможность проверить детали протокола, мне нужен способ вернуть предопределенные тестовые данные из сокета.Я полагаю, я мог бы просто использовать механизмы OCMock, чтобы подделать возвращаемые значения из сокета.Но так как CocoaAsyncSocket предоставляет много различных методов для чтения данных из сокета, я должен точно знать, какие объекты протокола используются в каком порядке.Я не хочу, чтобы мой модульный тест зависел от деталей реализации моего объекта протокола.Так что мне здесь делать?Реализовать фиктивный объект для класса сокета вручную?Это кажется нетривиальным, поэтому мне, вероятно, понадобятся и модульные тесты для этого.Это хорошая идея?

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

Если вы хотите увидеть код, вы можете найти его в Bitbucket: SieveClient.m и SieveClient.h

Редактировать: Внедрение зависимостей

Итак, я прочитал о Внедрении зависимостей и думаю, что собираюсь использоватьэто для получения объектов AsyncSocket и SaslConn в мой SieveClient объект.Я изменю свой конструктор, чтобы принимать эти объекты и использовать их.Поскольку пользователю этого класса обычно нет дела до сокета и объекта SASL, я добавлю фабричный метод (в форме вспомогательного конструктора), который просто создает эти объекты и передает их конструктору.

Но это решает только первую (и более легкую) часть моей проблемы с тестированием.

Ответы [ 3 ]

7 голосов
/ 07 сентября 2010

Но потом я отверг эту идею, потому что она не очень поможет. Я мог бы проверить объект SieveClient легче, правда. Но тогда у меня было бы то же самое проблемы с тестированием нового объекта. Мне кажется, что это просто до проблемы на потом. Особенно так как у меня ничего нет, я мог бы повторно использовать новый класс для.

Это не будет той же проблемой.

Я предполагаю, что вам нужен SieveClient для управления созданием других объектов внутри, потому что это часть API, которую вы не хотите предоставлять. Если по этой причине, разделяя их, у вас больше нет той же потребности, поскольку у вас может быть SieveClient, контролирующий привязки, а другая часть , которая выполняет протокол , получает экземпляры, которые будут работать с .

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

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


Перечитав вопрос из-за редактирования, я должен подчеркнуть то, что я сказал о целевых интеграционных тестах выше. Вы спрашиваете:

Но поскольку CocoaAsyncSocket предоставляет много разных методов для чтения данных из розетки я должен точно знать которые используются протоколом объект в каком порядке. Я не хочу, чтобы мой модульный тест, который будет зависеть от детали реализации моего протокола объект. Так что мне здесь делать? Реализация фиктивного объекта для сокета класс от руки? Это кажется нетривиальным, поэтому мне, вероятно, понадобятся юнит-тесты для это тоже. Это хорошая идея?

Если вы имеете дело с очень сложным объектом, и весь этот объект связан с интеграцией за пределы, вам лучше всего избегать его в рамках модульных тестов. В этом сценарии вы хотите сфокусированный интеграционный тест / поразить реальную внешнюю систему. Это не означает, что все модульные тесты остального кода, попадания во внешнюю систему, просто очень простая единица кода / класса, которая использует этот объект .

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

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

2 голосов
/ 10 сентября 2010

Не слушайте слишком много догм.Пойдите для самой простой вещи, которая могла бы работать, также для тестов.(Отказ от ответственности: я знаю TDD, но я не знаю Цель C).

Чтобы позволить SieveClient создавать свои SaslConn в рабочем коде, но использовать фиктивные в тестах, вы можете использовать Внедрение зависимости .Добавьте метод установки к SieveClient, чтобы передать фабрику (как объект или функцию, в зависимости от того, что разрешено Objective C), которую SieveClient будет использовать для создания SaslConn,вместо того, чтобы делать их самостоятельно.Тестовый код предоставляет тестовую фабрику, которая готовит макеты.Код производственного варианта для создания SaslConn либо перемещается на другую фабрику для независимого тестирования модулем, либо, если его слишком просто сломать, остается в качестве поведения по умолчанию внутри SieveClient, когда фабричный установщик не вызывается.

Самый простой способ протестировать код сетевого клиента - на данный момент реализовать или повторно использовать фиктивный сервер .Не смейтесь над деталями сокетов Гори в SaslConn;вместо этого, напишите сервер SASL в своих тестах.Тот факт, что ваш SaslConn может общаться с ним, имеет большое значение для обеспечения тестирования для этого фиктивного сервера;другими словами, SaslConn и фиктивный сервер являются юнит-тестами друг друга.(Да, не «единица» в пуристском смысле, но никого это не волнует.)

Наконец, у меня смешанные чувства по поводу того, что трудно тестируемый код плохо спроектирован.Это зависит.Вы должны разработать свой код так, чтобы его было легко использовать (в коде вызывающего) и легко модифицировать.Модульные тесты являются лишь средством для достижения этих целей: они являются первым кодом вызывающей стороны, который вы напишите, и они дают вам уверенность, что вы не облажаетесь при внесении изменений.Не позволяйте конкретной структуре или методологии искажать и искажать ваш дизайн до такой степени, чтобы перевесить преимущества TDD.В частности, основанные на ожидании моделирующие среды, такие как OCMock, упрощают запись хрупких тестов , которые выглядят как «Я ожидаю, что метод foo будет вызван 3 раза, и только тогда метод barназываться именно такими и такими аргументами ".Вместо того, чтобы использовать неправильные инструменты для работы, напишите свой собственный!

2 голосов
/ 04 сентября 2010

Можете ли вы разделить то, что вы делаете, на две части, одна из которых является абстрактным протоколом, а другая - привязкой к сокетам? Затем вы сможете легче тестировать абстрактный протокол и сосредоточиться на проверке привязки на том, правильно ли она вызывает методы / операции любого связанного абстрактного протокола.

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

...