Модульное тестирование C ++: что тестировать?

20

TL; DR

Написание хороших, полезных тестов сложно и дорого обходится в C ++. Можете ли вы опытные разработчики поделиться своим обоснованием того, что и когда тестировать?

Длинная история

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

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

Теперь я унаследовал новый проект, и мне интересно, как его протестировать. Это родное приложение C ++ / OpenGL, поэтому интеграционные тесты на самом деле не подходят. Но модульное тестирование в C ++ немного сложнее, чем в Java (вы должны явно делать вещи virtual), и программа не сильно объектно-ориентирована, поэтому я не могу издеваться над некоторыми вещами.

Я не хочу разрывать и разбирать все это, просто чтобы написать несколько тестов ради написания тестов. Поэтому я спрашиваю вас: для чего мне писать тесты? например:

  • Функции / Классы, которые я ожидаю часто менять?
  • Функции / классы, которые сложнее проверить вручную?
  • Функции / классы, которые уже легко проверить?

Я начал исследовать некоторые уважительные базы кода C ++, чтобы увидеть, как они идут на тестирование. Прямо сейчас я изучаю исходный код Chromium, но мне трудно извлечь обоснование их тестирования из кода. Если у кого-нибудь есть хороший пример или пост о том, как к этому подходят популярные пользователи C ++ (ребята из комитета, авторы книг, Google, Facebook, Microsoft, ...), это было бы очень полезно.

Обновить

Я искал свой путь вокруг этого сайта и в Интернете с момента написания этого. Найдены хорошие вещи:

К сожалению, все они скорее ориентированы на Java / C #. Написание большого количества тестов на Java / C # не является большой проблемой, поэтому выгода обычно перевешивает затраты.

Но, как я уже писал выше, в C ++ это сложнее. Особенно, если ваша кодовая база не так уж и хороша, вам придется серьезно испортить ситуацию, чтобы получить хороший охват модульных тестов. Например: у приложения, которое я унаследовал, есть Graphicsпространство имен, которое является тонким слоем над OpenGL. Чтобы протестировать любую из сущностей - которые все используют ее функции напрямую - мне нужно было бы превратить это в интерфейс и класс и внедрить его во все сущности. Это только один пример.

Поэтому, отвечая на этот вопрос, имейте в виду, что я должен сделать довольно большие инвестиции для написания тестов.

futlib
источник
3
+1 за сложность в юнит-тестировании C ++. Если ваш модульный тест требует, чтобы вы изменили код, не делайте этого.
DPD
2
@DPD: я не уверен, что, если что-то действительно стоит проверить? В текущей базе кода я с трудом могу что-либо тестировать в коде симуляции, потому что все это вызывает графические функции напрямую, и я не могу имитировать / заглушить их. Все, что я могу проверить прямо сейчас, - это служебные функции. Но я согласен, изменение кода, чтобы сделать его "тестируемым", кажется ... неправильным. Сторонники TDD часто говорят, что это улучшит весь ваш код всеми возможными способами, но я смиренно не согласен. Не всем нужен интерфейс и несколько реализаций.
Futlib
Позвольте мне привести вам недавний пример: я провел целый день, пытаясь протестировать одну отдельную функцию (написанную на C ++ / CLI), и инструмент тестирования MS Test всегда завершался сбоем для этого теста. Казалось, что есть некоторые проблемы с простыми ссылками CPP. Вместо этого я просто проверил выходной сигнал вызывающей функции, и он работал нормально. Я потратил целый день на UT одну функцию. Это была потеря драгоценного времени. Также я не мог получить какой-либо инструмент для заглушки, подходящий для моих нужд. Я сделал ручную заглушку, где это возможно.
DPD
Вот что я хотел бы избежать: D, я думаю, мы, разработчики C ++, должны особенно прагматично относиться к тестированию. Вы закончили тестирование, так что я думаю, что все в порядке.
Futlib
@DPD: я думал об этом больше, и я думаю, что вы правы, вопрос в том, какой компромисс я хочу сделать. Стоит ли проводить рефакторинг всей графической системы для тестирования нескольких объектов? Там не было ни одной ошибки, о которой я знаю, так что, вероятно: Нет. Если она начнет казаться глючной, я напишу тесты. Жаль, что я не могу принять ваш ответ, потому что это комментарий :)
futlib

Ответы:

5

Ну, юнит тестирование это только одна часть. Интеграционные тесты помогут вам решить проблему вашей команды. Интеграционные тесты могут быть написаны для всех видов приложений, а также для нативных приложений и приложений OpenGL. Вы должны проверить «Растущее объектно-ориентированное программное обеспечение под руководством тестов» Стива Фриманна и Ната Прайса (например, http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Он шаг за шагом ведет вас к разработке приложения с графическим интерфейсом и сетевым взаимодействием.

Тестирование программного обеспечения, которое не было протестировано, - другая история. Проверьте Майкла Фезерса «Эффективная работа с устаревшим кодом» (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
источник
Я знаю обе книги. Дело в том, что: 1. Мы не хотим использовать TDD, потому что он плохо работает с нами. Мы хотим тесты, но не религиозно. 2. Я уверен, что интеграционные тесты для приложений OpenGL как-то возможны, но это требует слишком много усилий. Я хочу продолжать улучшать приложение, а не начинать исследовательский проект.
futlib
Помните, что модульное тестирование «после факта» всегда сложнее, чем «сначала тестировать», потому что код не предназначен для тестирования (повторного использования, сопровождения и т. Д.). Если вы все равно хотите это сделать, попробуйте придерживаться трюков Майкла Фезерса (например, швов), даже если вы избегаете TDD. Например, если вы хотите расширить функцию, попробуйте что-то вроде «метода прорастания» и постарайтесь сохранить новый метод тестируемым. Это можно сделать, но сложнее ИМХО.
EricSchaefer
Я согласен с тем, что код, не относящийся к TDD, не предназначен для тестирования, но я бы не сказал, что он не подлежит сопровождению или повторному использованию - как я уже говорил выше, некоторые вещи просто не нуждаются в интерфейсах и множественных реализациях. С Mockito не проблема, но в C ++ мне нужно сделать все функции, которые я хочу, заглушить / смоделировать виртуальными. В любом случае, непроверяемый код - это моя самая большая проблема на данный момент: мне нужно изменить некоторые очень фундаментальные вещи, чтобы сделать некоторые части тестируемыми, и поэтому я хочу получить хорошее обоснование того, что тестировать, чтобы убедиться, что оно того стоит.
Futlib
Вы, конечно, правы, я буду осторожен, чтобы сделать любой новый код, который я пишу, тестируемым. Но это будет непросто, учитывая то, как сейчас все работает в этой кодовой базе.
Futlib
Когда вы добавляете функцию / функцию, просто подумайте, как вы можете ее протестировать. Не могли бы вы ввести какие-нибудь уродливые зависимости? Откуда вы знаете, что функция делает то, что должна делать? Не могли бы вы наблюдать за поведением? Есть ли какой-нибудь результат, который вы могли бы проверить на правильность? Есть ли инварианты, которые вы можете проверить?
EricSchaefer
2

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

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

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

Конечно, новые разработки выиграют от TDD. По мере добавления новых функций рефакторинг для TDD может помочь протестировать новую разработку, а также позволит разработать новый модульный тест для унаследованных функций.

Билл Дверь
источник
4
Мы делали TDD около полутора лет, и все они были очень увлечены этим. Тем не менее, сравнивая проекты TDD с более ранними проектами, выполненными без TDD (но не без тестов), я бы не сказал, что они на самом деле более стабильны или имеют лучше разработанный код. Может быть, это наша команда: мы много спариваем и проверяем, качество нашего кода всегда было довольно хорошим.
futlib
1
Чем больше я думаю об этом, тем больше я думаю, что TDD просто не очень хорошо подходил для технологии этого конкретного проекта: Flex / Swiz. Происходит много событий, привязок и внедрений, которые усложняют взаимодействие между объектами и практически невозможно выполнить модульное тестирование. Разделение этих объектов не делает это лучше, потому что они работают правильно с самого начала.
futlib
2

Я не делал TDD в C ++, поэтому я не могу это комментировать, но вы должны проверить ожидаемое поведение вашего кода. Хотя реализация может измениться, поведение должно (обычно?) Оставаться прежним. В мире, ориентированном на Java \ C #, это будет означать, что вы будете тестировать только общедоступные методы, писать тесты на ожидаемое поведение и делать это до реализации (что обычно лучше сказать, чем сделать :)).

Dante
источник