Я бы сказал, что использование friend
в этом случае неудачно.На мой взгляд, один хороший вариант использования для friend
состоит в том, чтобы разрешить доступ к закрытым элементам между классами, которые концептуально имеют тесную связь.Когда я пишу, они концептуально имеют тесную связь, я имею в виду, что тесная связь не является следствием использования friend
, но тесная связь между этими классами обусловлена их зависимостью, которая является следствиемих определенные роли.В таких случаях friend
- это механизм для правильного обращения с этой жесткой муфтой.Например, контейнеры и их соответствующие классы итераторов концептуально тесно связаны.
В вашем случае мне кажется, что классы не так тесно связаны на концептуальном уровне.Вы используете friend
для другой цели, а именно для обеспечения соблюдения архитектурного правила: только Model
должны использовать методы Load
, Update
и Use
.К сожалению, этот шаблон имеет ограничения: если у вас есть другой класс Foo
и второе архитектурное правило, которому Foo
разрешается вызывать только методы Use
, вы не можете выразить оба архитектурных правила: если вы делаете Foo
также друг других классов, тогда Foo
будет предоставлен не только доступ к Use
, но также к Load
и Update
- вы не можете предоставить права доступа детально.
Если мое понимание верно, то я бы сказал, что Load
, Update
и Use
концептуально не private
, то есть они не представляют детали реализации класса, который должен быть скрыт для внешней стороны: онипринадлежат к «официальному» API класса, только с дополнительным правилом, что только Model
должен их использовать.Часто private
методы являются закрытыми, потому что разработчик хочет сохранить свободу переименовывать или удалять их, потому что другой код просто не может получить к ним доступ.Я полагаю, что это не является целью.
Учитывая все это, я бы сказал, что было бы лучше по-другому справиться с этой ситуацией.Сделайте методы Load
, Update
и Use
общедоступными, а также добавьте комментарии для объяснения архитектурных ограничений.И хотя моя аргументация не касалась тестируемости, это также решает одну из ваших проблем тестирования, а именно, позволяет вашим тестам также получать доступ к Load
, Update
и Use
.
Если вы также хотитеуметь высмеивать ваши классы Texture
, Material
и Mesh
, а затем принять во внимание предложение от Quarra
для введения соответствующих интерфейсов.
Несмотря на то, что для вашего конкретногоНапример, я предлагаю сделать методы Load
, Update
и Use
общедоступными, я не противник деталей реализации юнит-тестирования.Альтернативные реализации одного и того же интерфейса имеют разные потенциальные ошибки.И поиск ошибок - одна из основных целей тестирования (см. Майерс, Бадгетт, Сандлер: Искусство тестирования программного обеспечения или Байзер: методы тестирования программного обеспечения и многие другие).
В качестве примера рассмотрим memcpy
функция: давайте предположим, что вы должны реализовать и протестировать это.Вы начинаете с простого решения, копируя побайтовое, и тщательно его проверяете.Затем вы понимаете, что для вашей 32-битной машины вы можете работать быстрее, если исходный адрес и целевой адрес выровнены по 32-битной схеме: в этом случае вы можете скопировать четыре байта одновременно.Когда вы реализуете это изменение, то новый memcpy
внутренне выглядит совсем иначе: есть начальная проверка, подходит ли выравнивание указателя.Если это не подходит, то выполняется оригинальная побайтовая копия, в противном случае выполняется более быстрая процедура копирования (которая также должна обрабатывать случай, когда число байтов не кратно четырем, поэтому может бытьнекоторые дополнительные байты для копирования в конце).
Интерфейс memcpy
остается прежним.Тем не менее, я думаю, что вам определенно нужно расширить набор тестов для новой реализации: у вас должны быть тестовые случаи для двух четырехбайтовых выровненных указателей, для случаев, когда только один указатель выровнен для четырехбайтовых символов и т. Д. Вам нужны случаи, когдаоба указателя выровнены по четырем байтам, а число копируемых байтов кратно четырем, и случаи, когда они не кратны четырем, и т. д. То есть ваш набор тестов будет значительно расширен - только потому, что реализациядетали изменились.Новые тесты необходимы для поиска ошибок в новой реализации - хотя все тесты все еще могут использовать общедоступный API, а именно функцию memcpy
.
Таким образом, неверно предполагать единицу измеренияТестирование не связано с деталями реализации, и также неверно полагать, что тесты не являются специфичными для реализации только потому, что они тестируют через открытый API.
Однако верно, что тест не должен Излишне зависит от деталей реализации.Всегда пытайтесь сначала создать полезные тесты, которые не зависят от реализации, а затем добавьте тесты, которые зависят от реализации.В последнем случае тестирование приватных методов (например, из тестового класса friend
) также может быть допустимым вариантом - при условии, что вы знаете о недостатках (обслуживание тестового кода будет необходимо, если частные методы будут переименованы, удалены и т. Д.).) и взвесить их против преимуществ.