Я надеялся создать локальный подкласс OAuthService
, назначить его в качестве моего sut и назвать что-то вроде sut.initAuthCodeFlow()
, а затем утверждать, что мои ожидания оправдались.
Я бы настоятельно не рекомендовал вам использовать этот подход.Если ваша SUT является экземпляром подкласса, тогда ваш тест на самом деле не тестирует OAuthService
, но OAuthService
макет.
Более того, если мы думаем о тестах как о инструменте для:
- предотвращение ошибок при изменении кода
- помогает рефакторинг и обслуживание кода
тогда я бы сказал, что тестирование того, что вызов определенной функции вызывает другую функцию, не является хорошим тестом,Это жестко, я знаю, поэтому позвольте мне распаковать, почему это так.
Единственное, что он проверяет, это то, что initAuthCodeFlow()
вызывает renderOAuthWebView(forService:, queryitems:)
под капотом.Он не имеет никакого утверждения о фактическом поведении тестируемой системы, о выходных данных, которые он производит напрямую или нет.Если бы мне пришлось отредактировать реализацию renderOAuthWebView(forService:, queryitems:)
и добавить некоторый код, который мог бы вылетать во время выполнения, этот тест не провалился бы.
Такой тест не поможет сохранить легкость изменения кодовой базы, потому что еслиВы хотите изменить реализацию OAuthService
, возможно, добавив параметр в renderOAuthWebView(forService:, queryitems:)
или переименовав queryitems
в queryItems
, чтобы он соответствовал заглавным буквам, вам придется обновить как рабочий код, так и тест.Другими словами, тест помешает вам выполнить рефакторинг - изменение внешнего вида кода без изменения его поведения - без каких-либо дополнительных преимуществ.
Итак, как следует тестировать OAuthService
таким образом, чтобы предотвратитьошибки и помогает двигаться быстро?Вся хитрость заключается в тестировании поведения вместо реализации.
Что должен OAuthService
делать ?initAuthCodeFlow()
не возвращает никакого значения, поэтому мы можем проверять прямые выходы, но мы все равно можем проверять косвенные выходы, побочные эффекты.
Я угадаю здесь, но я из вашего теста проверяю, чтоrenderOAuthWebView(forService:, queryitems:)
Я бы и тот факт, что он получает тип APIClient
в качестве входных данных, я бы сказал, что он представит какое-то веб-представление для определенного URL, а затем сделаю еще один запрос к данному APIClient
, возможно, смаркер OAuth, полученный из веб-представления?
Чтобы проверить взаимодействие с APIClient
, нужно сделать утверждение для ожидаемой конечной точки, которая будет вызвана.Вы можете сделать это с помощью инструмента, такого как OHHTTPubs или с вашим пользовательским двойным тестом для URLSession
, который записывает полученные запросы и позволяет вам проверять их.
Что касается представленияВ веб-представлении вы можете использовать для него шаблон делегата и установить двойной тест, соответствующий протоколу делегата, который записывает, вызван он или нет.Или вы можете протестировать на более высоком уровне и проверить UIWindow
, в котором выполняется тест, чтобы увидеть, является ли контроллер корневого представления тем, у которого есть веб-представление.
В конце дня всевопрос компромиссов.Подход, который вы выбрали, не является неправильным, он просто больше оптимизирует утверждение кода, а не его поведение.Я надеюсь, что с этим ответом я продемонстрировал другой вид оптимизации, смещенный в сторону поведения.По моему опыту, этот стиль тестирования оказывается более полезным в среднесрочной перспективе.