Pengubah akses "internal" C # saat melakukan pengujian unit

469

Saya baru dalam pengujian unit dan saya mencoba mencari tahu apakah saya harus mulai menggunakan lebih banyak pengubah akses 'internal'. Saya tahu bahwa jika kita menggunakan 'internal' dan mengatur variabel perakitan 'InternalsVisibleTo', kita dapat menguji fungsi yang tidak ingin kita umumkan dari proyek pengujian. Ini membuat saya berpikir bahwa saya harus selalu menggunakan 'internal' karena setidaknya setiap proyek (harus?) Memiliki proyek pengujian sendiri. Bisakah kalian memberi tahu saya alasan mengapa saya tidak melakukan ini? Kapan saya harus menggunakan 'pribadi'?

Berbohong Hertanto
sumber
1
Layak disebutkan - Anda sering dapat menghindari kebutuhan unit menguji metode internal Anda dengan menggunakan System.Diagnostics.Debug.Assert()dalam metode itu sendiri.
Mike Marynowski

Jawaban:

1195

Kelas internal perlu diuji dan ada atribut assemby:

using System.Runtime.CompilerServices;

[assembly:InternalsVisibleTo("MyTests")]

Tambahkan ini ke file info proyek, mis Properties\AssemblyInfo.cs.

EricSchaefer
sumber
70
Tambahkan ke proyek yang sedang diuji (misalnya dalam Properties \ AssemblyInfo.cs). "MyTests" akan menjadi majelis uji.
EricSchaefer
86
Ini benar-benar jawaban yang diterima. Saya tidak tahu tentang kalian, tetapi ketika tes "terlalu jauh" dari kode yang mereka uji saya cenderung gugup. Saya semua untuk menghindari untuk menguji sesuatu yang ditandai private, tetapi terlalu banyak privatehal mungkin menunjuk ke internalkelas yang berjuang untuk diekstraksi. TDD atau tanpa TDD, saya lebih suka memiliki lebih banyak tes yang menguji banyak kode, daripada memiliki beberapa tes yang menggunakan jumlah kode yang sama. Dan menghindari menguji internalbarang-barang tidak benar-benar membantu untuk mencapai rasio yang baik.
sm
7
Ada diskusi besar yang terjadi antara @DerickBailey dan Dan Tao mengenai perbedaan semantik antara internal dan pribadi dan kebutuhan untuk menguji komponen internal . Layak dibaca.
Kris McGinnes
31
Dengan membungkus dan #if DEBUG, #endifblok akan mengaktifkan opsi ini hanya di build debug.
The Real Edward Cullen
17
Ini jawaban yang benar. Setiap jawaban yang mengatakan bahwa hanya metode publik yang harus diuji unit tidak ada gunanya tes unit dan membuat alasan. Pengujian fungsional berorientasi pada kotak hitam. Tes unit berorientasi kotak putih. Mereka harus menguji "unit" fungsionalitas, bukan hanya API publik.
Gunnar
127

Jika Anda ingin menguji metode pribadi, lihat PrivateObjectdan PrivateTypedi Microsoft.VisualStudio.TestTools.UnitTestingnamespace. Mereka menawarkan pembungkus yang mudah digunakan di sekitar kode refleksi yang diperlukan.

Documents: PrivateType , PrivateObject

Untuk VS2017 & 2019, Anda dapat menemukannya dengan mengunduh nuget MSTest.TestFramework

Brian Rasmussen
sumber
15
Saat memilih, silakan tinggalkan komentar. Terima kasih.
Brian Rasmussen
35
Sangat bodoh memilih jawaban ini. Ini menunjuk ke solusi baru dan benar-benar bagus yang tidak disebutkan sebelumnya.
Ignacio Soler Garcia
Rupanya, ada beberapa masalah dengan menggunakan TestFramework untuk penargetan aplikasi .net2.0 atau yang lebih baru: github.com/Microsoft/testfx/issues/366
Johnny Wu
41

Menambahkan ke jawaban Eric, Anda juga dapat mengkonfigurasi ini di csprojfile:

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

Atau jika Anda memiliki satu proyek uji per proyek yang akan diuji, Anda dapat melakukan sesuatu seperti ini di Directory.Build.propsfile Anda :

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

Lihat: https://stackoverflow.com/a/49978185/1678053
Contoh: https://github.com/gldraphael/evlog/blob/master/Directory.Build.props#L5-L12

gldraphael
sumber
2
Ini harus menjadi jawaban atas imo. Semua jawaban lain sangat ketinggalan jaman karena .net menjauh dari info assembly dan memindahkan fungsionalitas ke definisi csproj.
mBrice1024
12

Tetap menggunakan pribadi secara default. Jika seorang anggota tidak boleh diekspos di luar tipe itu, ia tidak boleh diekspos di luar tipe itu, bahkan di dalam proyek yang sama. Ini menjaga hal-hal lebih aman dan lebih rapi - ketika Anda menggunakan objek, itu lebih jelas metode mana yang Anda maksudkan untuk dapat digunakan.

Karena itu, saya pikir itu masuk akal untuk membuat metode pribadi-swasta internal untuk tujuan pengujian kadang-kadang. Saya lebih suka menggunakan refleksi, yang refactoring-tidak ramah.

Satu hal yang perlu dipertimbangkan mungkin sufiks "ForTest":

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

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

Kemudian ketika Anda menggunakan kelas dalam proyek yang sama, sudah jelas (sekarang dan di masa depan) bahwa Anda seharusnya tidak benar-benar menggunakan metode ini - itu hanya ada untuk tujuan pengujian. Ini agak berantakan, dan bukan sesuatu yang saya lakukan sendiri, tapi setidaknya patut dipertimbangkan.

Jon Skeet
sumber
2
Jika metode ini internal, apakah ini tidak menghalangi penggunaannya dari unit pengujian?
Ralph Shillington
7
Saya kadang-kadang menggunakan ForTestpendekatan tetapi saya selalu menemukan itu mati jelek (menambahkan kode yang tidak memberikan nilai aktual dalam hal logika bisnis produksi). Biasanya saya menemukan saya harus menggunakan pendekatan karena desainnya agak disayangkan (yaitu harus mengatur ulang contoh tunggal di antara tes)
ChrisWue
1
Tergoda untuk melakukan downvote ini - apa perbedaan antara peretasan ini dan hanya membuat kelas internal alih-alih pribadi? Yah, setidaknya dengan persyaratan kompilasi. Maka itu menjadi sangat berantakan.
CAD berbicara
6
@CADbloke: Maksud Anda membuat metode internal daripada pribadi? Perbedaannya adalah jelas bahwa Anda benar - benar ingin menjadi pribadi. Setiap kode dalam basis kode produksi Anda yang memanggil metode dengan ForTestyang jelas salah, sedangkan jika Anda hanya membuat metode internal sepertinya baik-baik saja itu untuk digunakan.
Jon Skeet
2
@CADbloke: Anda bisa mengecualikan metode individual dalam rilis rilis semudah dalam file yang sama dengan menggunakan kelas parsial, IMO. Dan jika Anda melakukan itu, itu menunjukkan bahwa Anda tidak menjalankan tes Anda terhadap rilis rilis Anda, yang terdengar seperti ide yang buruk bagi saya.
Jon Skeet
11

Anda dapat menggunakan pribadi juga dan Anda dapat memanggil metode pribadi dengan refleksi. Jika Anda menggunakan Visual Studio Team Suite, ia memiliki beberapa fungsi bagus yang akan menghasilkan proxy untuk memanggil metode pribadi Anda untuk Anda. Berikut ini adalah artikel proyek kode yang menunjukkan bagaimana Anda dapat melakukan pekerjaan sendiri untuk menguji unit metode pribadi dan yang dilindungi:

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

Dalam hal pengubah akses yang harus Anda gunakan, aturan umum saya adalah mulai dengan pribadi dan meningkat sesuai kebutuhan. Dengan cara itu Anda akan mengekspos detail internal kelas Anda seperti yang benar-benar dibutuhkan dan itu membantu menjaga detail implementasi tersembunyi, sebagaimana seharusnya.

Steven Behnke
sumber
3

Saya menggunakan Dotnet 3.1.101dan .csprojpenambahan yang berfungsi untuk saya adalah:

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

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

Semoga ini bisa membantu seseorang di luar sana!

Mengambang Sunfish
sumber
1
Tambahan yang secara eksplisit menghasilkan info perakitan adalah apa yang akhirnya membuatnya bekerja untuk saya juga. Terima kasih telah memposting ini!
thevioletsaber