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


469

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


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

Ответы:


1195

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

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

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


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.


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.