модульные тесты в C ++ - PullRequest
       16

модульные тесты в C ++

5 голосов
/ 30 июня 2009

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

Как работает разработка через тестирование в реальных приложениях C ++? Я прочитал «Эффективная работа с унаследованным кодом» и очень полезен, но не в состоянии освоить TDD.

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

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

Ответы [ 7 ]

5 голосов
/ 30 июня 2009

Обновление : См. и этот вопрос.

Я могу ответить только на некоторые части здесь:

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

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

Вы всегда можете «подружить» свои тестовые классы или объект TestAccessor, с помощью которого ваши тесты могут исследовать вещи внутри него. Это позволяет избежать динамической диспетчеризации всего лишь для тестирования. (это звучит как совсем немного работы.)

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

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

Большие рефакторинги по определению сильно меняются, включая тесты. Будьте счастливы, что они у вас есть, так как они будут проверять и после рефакторинга.

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

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

Надеюсь, это поможет.

3 голосов
/ 30 июня 2009

Я обычно использовал макросы, #if и другие приемы препроцессора для «макетирования» зависимостей с целью модульного тестирования в C и C ++, именно потому, что с такими макросами мне не нужно платить ни одного запуска затраты времени, когда код компилируется для производства, а не для тестирования. Не элегантно, но достаточно эффективно.

Что касается рефакторинга, то для них может потребоваться смена тестов, когда они настолько огромны и навязчивы, как вы описываете. Однако я не вижу в этом такого резкого рефакторинга.

1 голос
/ 30 июня 2009

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

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

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

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

Может быть, вы на самом деле не делаете TDD, как:

  1. Создать тест, который не проходит
  2. Создать код для прохождения теста
  3. Создать еще один тест, который не прошел
  4. Исправить код для прохождения обоих тестов
  5. Прополощите и повторяйте, пока не решите, что у вас достаточно тестов, которые показывают, что должен делать ваш код

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

На самом деле я работаю в реальном программном обеспечении, написанном на C ++, с ОГРОМНЫМ унаследованным кодом. Мы используем TDD, и это действительно помогает развивать дизайн программного обеспечения.

1 голос
/ 30 июня 2009

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

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

1 голос
/ 30 июня 2009

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

0 голосов
/ 30 июня 2009

Это связано со значительными накладными расходами в время выполнения из-за множества виртуальных вызовов вместо простых вызовов методов.

Помните, что это только накладные расходы виртуального вызова, если вы обращаетесь к методу через указатель (или ссылку) на ваш интерфейс или объект. Если вы обращаетесь к методу через конкретный объект в стеке, у него не будет виртуальных издержек, и он даже может быть встроен.

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

0 голосов
/ 30 июня 2009

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

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

Можно ли нарушить все зависимости, используя интерфейсы, только чтобы сделать класс тестируемым?

Определенно все в порядке, чтобы классы были более тестируемыми . В конце концов, это часть цели TDD.

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

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

Вполне возможно, что программа будет эффективной и легко тестируемой. Однако бывают моменты, когда вам нужно выбрать, что важнее. Это один из тех моментов. Я не знаю, насколько важна эффективность для вас и вашей программы, но вы знаете. Итак, «вы бы предпочли иметь медленную, хорошо протестированную программу или быструю программу без полного покрытия тестами?»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...