C # «внутренний» модификатор доступа при выполнении модульного тестирования

469

Я новичок в модульном тестировании и пытаюсь понять, стоит ли мне начинать использовать больше «внутреннего» модификатора доступа. Я знаю, что если мы используем 'internal' и устанавливаем переменную сборки 'InternalsVisibleTo', мы можем тестировать функции, которые не хотим объявлять общедоступными из проекта тестирования. Это заставляет меня думать, что я должен всегда использовать «внутренний», потому что, по крайней мере, у каждого проекта (должен?) Есть свой собственный проект тестирования. Ребята, вы можете сказать мне причину, почему я не должен этого делать? Когда я должен использовать «частный»?

Hertanto Lie
источник
1
Стоит упомянуть - вы часто можете избежать необходимости модульного тестирования ваших внутренних методов, используя System.Diagnostics.Debug.Assert()сами методы.
Майк Мариновски

Ответы:

1195

Внутренние классы должны быть проверены и есть атрибут сборки:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Добавьте это в файл информации о проекте, например Properties\AssemblyInfo.cs.

EricSchaefer
источник
70
Добавьте его в тестируемый проект (например, в Properties \ AssemblyInfo.cs). «MyTests» будет тестовой сборкой.
EricSchaefer
86
Это действительно должен быть принятый ответ. Я не знаю, как вы, ребята, но когда тесты "слишком далеки" от кода, который они тестируют, я склонен нервничать. Я полностью за то, что стараюсь избегать тестирования чего-либо, помеченного как private, но слишком много privateвещей могут очень хорошо указать на internalкласс, который изо всех сил пытается быть извлеченным. TDD или нет TDD, я предпочитаю иметь больше тестов, которые тестируют много кода, чем иметь несколько тестов, которые выполняют одинаковое количество кода. А уклонение от тестирования internalне совсем помогает достичь хорошего соотношения.
см
7
Между @DerickBailey и Dan Tao идет большая дискуссия о семантической разнице между внутренним и частным и о необходимости тестирования внутренних компонентов. Стоит прочитать.
Крис Макгиннес
31
Обтекание в и #if DEBUG, #endifблок включит эту опцию только в отладочных сборках.
Настоящий Эдвард Каллен
17
Это правильный ответ. Любой ответ, который говорит, что только общедоступные методы должны подвергаться модульному тестированию, упускает смысл модульных тестов и дает оправдание. Функциональное тестирование ориентировано на черный ящик. Модульные тесты ориентированы на белый ящик. Они должны тестировать «модули» функциональности, а не только публичные API.
Гуннар
127

Если вы хотите проверить личные методы, посмотреть на PrivateObjectи PrivateTypeв Microsoft.VisualStudio.TestTools.UnitTestingпространстве имен. Они предлагают простые в использовании обертки вокруг необходимого кода отражения.

Документы: PrivateType , PrivateObject

Для VS2017 и 2019 вы можете найти их, загрузив nuget MSTest.TestFramework

Брайан Расмуссен
источник
15
При отрицательном голосовании, пожалуйста, оставьте комментарий. Спасибо.
Брайан Расмуссен
35
Глупо голосовать за этот ответ. Это указывает на новое решение и действительно хорошее, не упомянутое ранее.
Игнасио Солер Гарсия
По-видимому, существует некоторая проблема с использованием TestFramework для приложений, нацеленных на .net2.0 или новее: github.com/Microsoft/testfx/issues/366
Джонни Ву,
41

Добавив ответ Эрика, вы также можете настроить это в csprojфайле:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>MyTests</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

Или, если у вас есть один тестовый проект на тестируемый проект, вы можете сделать что-то вроде этого в вашем Directory.Build.propsфайле:

<ItemGroup>
    <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
      <_Parameter1>$(MSBuildProjectName).Test</_Parameter1>
    </AssemblyAttribute>
</ItemGroup>

См .: https://stackoverflow.com/a/49978185/1678053
Пример: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12.

gldraphael
источник
2
Это должен быть главный ответ IMO. Все остальные ответы очень устарели, так как .net отходит от информации о сборке и переносит функциональность в определения csproj.
mBrice1024
12

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

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

Одна вещь, которую следует учитывать, может быть суффиксом «ForTest»:

internal void DoThisForTest(string name)
{
    DoThis(name);
}

private void DoThis(string name)
{
    // Real implementation
}

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

Джон Скит
источник
2
Если метод является внутренним, не исключает ли это его использование из тестовой сборки?
Ральф Шиллингтон
7
Я иногда использую этот ForTestподход, но всегда нахожу его уродливым (добавление кода, который не дает никакой реальной ценности с точки зрения производственной бизнес-логики). Обычно я нахожу, что мне пришлось использовать этот подход, потому что дизайн несколько неудачен (то есть приходится сбрасывать единичные экземпляры между тестами)
ChrisWue,
1
Соблазн понизить это - в чем разница между этим хаком и простым превращением класса в внутренний, а не в частный? Ну хотя бы с компиляцией условностей. Тогда это становится действительно грязным.
CAD блок
6
@CADbloke: Вы хотите сделать метод внутренним, а не частным? Разница в том, что очевидно, что вы действительно хотите, чтобы это было приватно. Любой код в вашей производственной кодовой базе, который вызывает метод с ForTest, явно ошибочен, в то время как если вы просто сделаете метод внутренним, это будет выглядеть нормально.
Джон Скит
2
@CADbloke: Вы можете исключить отдельные методы в сборке релиза так же легко, как и частичные классы, IMO. И если вы это сделаете, это говорит о том, что вы не запускаете свои тесты для своей сборки релиза, что для меня звучит как плохая идея.
Джон Скит
11

Вы также можете использовать private и вызывать private методы с отражением. Если вы используете Visual Studio Team Suite, у него есть приятная функциональность, которая генерирует прокси для вызова ваших личных методов. Вот статья проекта кода, которая демонстрирует, как вы можете выполнить работу самостоятельно для тестирования отдельных и защищенных методов:

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx

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

Стивен Бенке
источник
3

Я использую Dotnet 3.1.101и .csprojдополнения, которые работали для меня были:

<PropertyGroup>
  <!-- Explicitly generate Assembly Info -->
  <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
  <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
  <_Parameter1>MyProject.Tests</_Parameter1>
  </AssemblyAttribute>
</ItemGroup>

Надеюсь, это поможет кому-то там!

Плавающая Солнечная Рыба
источник
1
Добавление явно генерируемой информации о сборке было тем, что, наконец, заставило ее работать и у меня. Спасибо за публикацию!
thevioletsaber