C # „wewnętrzny” modyfikator dostępu podczas wykonywania testów jednostkowych

469

Jestem nowy w testowaniu jednostkowym i staram się dowiedzieć, czy powinienem zacząć używać więcej „wewnętrznego” modyfikatora dostępu. Wiem, że jeśli użyjemy „wewnętrznego” i ustawimy zmienną asemblerową „InternalsVisibleTo”, możemy przetestować funkcje, których nie chcemy ogłaszać publicznie z projektu testowego. To sprawia, że ​​myślę, że zawsze powinienem używać „wewnętrznego”, ponieważ przynajmniej każdy projekt (powinien?) Ma swój własny projekt testowy. Czy możecie mi powiedzieć, dlaczego nie powinienem tego robić? Kiedy powinienem używać „prywatnego”?

Hertanto Lie
źródło
1
Warto wspomnieć - często można uniknąć konieczności jednostkowego testowania metod wewnętrznych, używając System.Diagnostics.Debug.Assert()samych metod.
Mike Marynowski

Odpowiedzi:

1195

Klasy wewnętrzne muszą zostać przetestowane, a istnieje atrybut assemby:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Dodaj to do pliku informacji o projekcie, np Properties\AssemblyInfo.cs.

EricSchaefer
źródło
70
Dodaj go do testowanego projektu (np. W Properties \ AssemblyInfo.cs). „MyTests” to zestaw testowy.
EricSchaefer
86
To naprawdę powinna być zaakceptowana odpowiedź. Nie wiem o was, ale kiedy testy są „zbyt daleko” od kodu, który testują, zwykle się denerwuję. Jestem za tym, by unikać testowania czegokolwiek oznaczonego jako private, ale zbyt wiele privaterzeczy może równie dobrze wskazywać na internalklasę, która ma trudności z wyodrębnieniem. TDD lub brak TDD, wolę mieć więcej testów, które testują dużo kodu, niż mieć kilka testów, które wykonują tę samą ilość kodu. A unikanie testowania internalrzeczy nie pomaga w osiągnięciu dobrego stosunku.
sm
7
Toczy się świetna dyskusja między @DerickBailey a Dan Tao na temat semantycznej różnicy między wewnętrznym a prywatnym oraz konieczności testowania wewnętrznych komponentów. Warto przeczytać.
Kris McGinnes
31
Zawijanie w i #if DEBUG, #endifblok włącza tę opcję tylko w kompilacjach debugowania.
Real Edward Cullen
17
To jest poprawna odpowiedź. Każda odpowiedź, która mówi, że tylko metody publiczne powinny być testowane jednostkowo, nie ma sensu testów jednostkowych i jest usprawiedliwieniem. Testy funkcjonalne są zorientowane na czarną skrzynkę. Testy jednostkowe są zorientowane na białe pola. Powinni testować „jednostki” funkcjonalności, a nie tylko publiczne interfejsy API.
Gunnar,
127

Jeśli chcesz przetestować metody prywatne, przyjrzeć się PrivateObjecti PrivateTypew Microsoft.VisualStudio.TestTools.UnitTestingprzestrzeni nazw. Oferują łatwe w użyciu opakowania wokół niezbędnego kodu odbicia.

Dokumenty: PrivateType , PrivateObject

W przypadku wersji VS2017 i 2019 można je znaleźć, pobierając nuget MSTest.TestFramework

Brian Rasmussen
źródło
15
Podczas głosowania w dół zostaw komentarz. Dzięki.
Brian Rasmussen
35
Głupie jest głosowanie za tą odpowiedzią. Wskazuje na nowe rozwiązanie i naprawdę dobre, o którym wcześniej nie wspomniano.
Ignacio Soler Garcia
Wygląda na to, że jest jakiś problem z używaniem TestFramework do celowania w aplikacje .net2.0 lub nowsze: github.com/Microsoft/testfx/issues/366
Johnny Wu
41

Dodając do odpowiedzi Erica, możesz również skonfigurować to w csprojpliku:

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

Lub jeśli masz jeden projekt testowy na projekt do przetestowania, możesz zrobić coś takiego w swoim Directory.Build.propspliku:

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

Zobacz: https://stackoverflow.com/a/49978185/1678053
Przykład: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

Gldraphael
źródło
2
To powinna być najlepsza odpowiedź imo. Wszystkie pozostałe odpowiedzi są bardzo nieaktualne, ponieważ .net odchodzi od informacji o złożeniu i przenosi funkcjonalność do definicji csproj.
mBrice1024
12

Domyślnie używaj prywatnego. Jeśli członek nie powinien być narażony poza ten typ, nie powinien być narażony poza ten typ, nawet w ramach tego samego projektu. Dzięki temu wszystko jest bezpieczniejsze i bardziej uporządkowane - gdy używasz obiektu, jest bardziej jasne, z jakich metod możesz korzystać.

To powiedziawszy, myślę, że czasem rozsądne jest uczynienie metod naturalnie prywatnych wewnętrznymi dla celów testowych. Wolę to od użycia refleksji, która jest nieprzyjazna dla refaktoryzacji.

Jedną rzeczą do rozważenia może być sufiks „ForTest”:

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

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

Następnie, gdy korzystasz z klasy w ramach tego samego projektu, oczywiste jest (teraz iw przyszłości), że tak naprawdę nie powinieneś używać tej metody - jest ona dostępna tylko w celach testowych. Jest to trochę zuchwałe i nie robię tego sam, ale przynajmniej warto to rozważyć.

Jon Skeet
źródło
2
Jeśli metoda jest wewnętrzna, czy nie wyklucza to jej zastosowania w zestawie testowym?
Ralph Shillington
7
Czasami używam tego ForTestpodejścia, ale zawsze uważam to za brzydkie (dodanie kodu, który nie zapewnia żadnej rzeczywistej wartości z punktu widzenia logiki biznesowej produkcji). Zwykle uważam, że musiałem zastosować to podejście, ponieważ projekt jest nieco niefortunny (tj. Musiałem resetować instancje singletonów między testami)
ChrisWue,
1
Kusiło cię, by głosować za tym - jaka jest różnica między tym hackiem a zwykłym uczynieniem klasy wewnętrzną, a nie prywatną? Cóż, przynajmniej z warunkowymi kompilacjami. Potem robi się naprawdę bałagan.
CAD bloke
6
@CADbloke: Czy masz na myśli uczynienie tej metody bardziej wewnętrzną niż prywatną? Różnica polega na tym, że jest oczywiste, że naprawdę chcesz , aby była prywatna. Każdy kod w bazie kodu produkcyjnego, który wywołuje metodę, ForTestjest oczywiście niepoprawny, podczas gdy jeśli uczynisz metodę wewnętrzną, wygląda na to, że można z niej korzystać.
Jon Skeet
2
@CADbloke: Możesz wykluczyć poszczególne metody w kompilacji wydania tak samo łatwo w tym samym pliku, jak przy użyciu klas częściowych, IMO. A jeśli to zrobisz, sugeruje to, że nie uruchamiasz testów w stosunku do kompilacji wydania, co wydaje mi się złym pomysłem.
Jon Skeet
11

Możesz także użyć prywatnego i możesz wywoływać prywatne metody z refleksją. Jeśli korzystasz z Visual Studio Team Suite, ma on fajną funkcjonalność, która wygeneruje serwer proxy do wywołania twoich prywatnych metod. Oto artykuł dotyczący projektu kodu, który pokazuje, jak możesz sam wykonać pracę, aby przetestować metody prywatne i chronione:

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

Pod względem tego, jakiego modyfikatora dostępu należy użyć, moja ogólna zasada zaczyna się od prywatnego i eskaluje w razie potrzeby. W ten sposób ujawnisz tak mało wewnętrznych szczegółów swojej klasy, jakie są naprawdę potrzebne, i pomaga ukryć szczegóły implementacji, tak jak powinny.

Steven Behnke
źródło
3

Używam Dotnet 3.1.101i .csprojdodatki, które działały dla mnie były:

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

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

Mam nadzieję, że to pomoże komuś tam!

Pływający Sunfish
źródło
1
Dodanie jawnie generujących informacji o montażu było tym, co w końcu sprawiło, że zadziałało również dla mnie. Dziękujemy za opublikowanie tego!
thevioletsaber