Использование класса друга или добавление средств доступа для модульного тестирования на С++?

Не лучше ли добавить функции, которые возвращают внутреннее состояние объекта для модульного тестирования, вместо того, чтобы сделать класс тестирования другом? - особенно, когда от функций нет никакой пользы, кроме случая юнит-тестирования.


person bias    schedule 19.07.2009    source источник


Ответы (6)


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

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

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

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

person Drew Hoskins    schedule 19.07.2009

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

Часть состояния, которое вы тестируете, вероятно, зависит от реализации вашего класса; вы тестируете детали, о которых другой код обычно не знает или не заботится о них и на которые не следует полагаться. Общедоступные функции доступа делают эти детали реализации частью интерфейса класса. Если внутреннее состояние, которое вы тестируете, не является частью предполагаемого интерфейса, оно не должно быть видно через общедоступные функции. С пуристической точки зрения вы застряли между двумя неправильными ответами, поскольку дружественные классы также технически являются частью общедоступного интерфейса. На мой взгляд, возникает вопрос, какой вариант с меньшей вероятностью приведет к плохому выбору кода в будущем? Использование набора общедоступных функций доступа, зависящих от реализации, непреднамеренно поощряет концептуальную модель класса, зависящую от реализации, что приводит к зависимому от реализации использованию класса. Единственный класс друзей, правильно названный и задокументированный, с меньшей вероятностью станет объектом злоупотреблений.

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

person Darryl    schedule 19.07.2009

Использование дружественных классов для модульного тестирования вполне законно и позволяет поддерживать инкапсуляцию. Вы не должны изменять открытый интерфейс вашего класса просто для того, чтобы сделать класс более тестируемым. Подумайте об этом таким образом. Что делать, если вы приобрели стороннюю FTP-библиотеку и пытаетесь ее использовать, а ее общедоступный интерфейс загроможден кучей методов, о которых вам даже не нужно знать, просто из-за модульных тестов! Даже изменение защищенного интерфейса для компенсации модульных тестов — ПЛОХО. Если я наследую от какого-то класса, я не хочу беспокоиться о том, какие методы мне полезны, а какие существуют только из-за модульных тестов!!! Использование дружественных классов для модульных тестов помогает поддерживать простой и удобный интерфейс класса; это помогает сохранить инкапсуляцию и абстракцию!!!

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

класс друга MyClassTest;

и теперь вы можете тестировать свой класс любым удобным для вас способом!

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

person cchampion    schedule 13.11.2010

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

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

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

person James Thompson    schedule 19.07.2009
comment
Чтобы убедиться, что я вас понимаю, метод доступа к переменной double time; Я бы создал: double time() const; - person bias; 19.07.2009
comment
Да, по двум причинам: 1, он сообщает другим программистам, что вы хотите, чтобы они могли делать с данными, и 2: компилятор часто может оптимизировать константные функции, которые он не мог бы сделать без константного идентификатора. - person jkeys; 19.07.2009
comment
О, да, обратите внимание, что наличие переменной time и функции time() не будет компилироваться; у большинства людей есть фактическая переменная со знаком подчеркивания в конце, например, time_, и метод доступа, такой как time() { return time_; } - person jkeys; 20.07.2009

Как насчет того, чтобы сделать внутреннее состояние «защищенным»? А затем выполните unittest, используя производный класс.

person Jinuk Kim    schedule 19.07.2009
comment
protected имеет худшую защиту от инкапсуляции, чем friend. Любой наследник вашего класса будет иметь законный доступ. И поскольку вы ставите их защищенными, вы говорите, что это действительно поддерживается. По крайней мере, класс друзей с определенным именем friend class MyObjectOnlyForTestingPurpose ограничит доступ к ОДНОМУ классу с ОДНИМ конкретным именем. . . В общем, это как отдать ключи от своей квартиры ОДНОМУ другу, а не раздавать ключи всем членам семьи (включая сводного брата мужа дочери) - person paercebal; 14.06.2013

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

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

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

person Timo Geusch    schedule 19.07.2009
comment
Разве производные классы не будут считаться клиентами/пользователями класса точно так же, как клиенты/пользователи открытого интерфейса? Если единственным производным классом не является заглушка для модульного тестирования, у вас возникает та же проблема, что и в том случае, если вы сделали методы доступа общедоступными — авторы производных классов не могут сказать, какие защищенные функции можно вызывать, а какие следует использовать только для тестирования. целей. Если вы измените функции, связанные с тестированием, вы можете сломать производные классы, которые не знали, что нельзя использовать метод доступа. - person Darryl; 29.04.2013