Apakah statis secara universal “jahat” untuk pengujian unit dan jika demikian mengapa Resharper merekomendasikannya? [Tutup]

85

Saya telah menemukan bahwa hanya ada 3 cara untuk menguji unit dependensi (mock / stub) yang statis di C # .NET:

Mengingat dua di antaranya tidak gratis dan satu belum mencapai rilis 1.0, mengejek hal-hal statis tidak terlalu mudah.

Apakah itu membuat metode statis dan "jahat" seperti itu (dalam pengertian pengujian unit)? Dan jika demikian, mengapa penyelamat ingin saya membuat sesuatu yang bisa statis, statis? (Dengan asumsi penyelamat tidak juga "jahat".)

Klarifikasi: Saya berbicara tentang skenario ketika Anda ingin menguji unit metode dan metode yang memanggil metode statis di unit / kelas yang berbeda . Dengan sebagian besar definisi pengujian unit, jika Anda membiarkan metode yang sedang diuji memanggil metode statis di unit / kelas lain maka Anda bukan pengujian unit, Anda adalah pengujian integrasi . (Berguna, tetapi bukan tes unit.)

Gunung berapi
sumber
3
TheLQ: Kamu bisa. Saya percaya dia berbicara tentang tidak dapat menguji metode statis karena banyak waktu menyentuh variabel statis. Sehingga mengubah keadaan setelah dan di antara tes.
26
Secara pribadi saya pikir Anda mengambil definisi "unit" terlalu jauh. "Unit" harus "unit terkecil yang masuk akal untuk diuji secara terpisah". Itu mungkin sebuah metode, mungkin lebih dari itu. Jika metode statis tidak memiliki status dan diuji dengan baik maka memiliki panggilan tes unit kedua itu (IMO) bukan masalah.
mlk
10
"Secara pribadi saya pikir Anda mengambil definisi" unit "terlalu jauh." Tidak, hanya saja dia menggunakan standar penggunaan dan Anda membuat definisi sendiri.
7
"Mengapa penyelamat ingin aku membuat sesuatu yang bisa statis, statis?" Resharper tidak ingin Anda melakukan apa pun. Ini hanya membuat Anda sadar bahwa modifikasi itu mungkin dan mungkin diinginkan dari POV analisis kode. Resharper bukan pengganti penilaian Anda sendiri!
Adam Naylor
4
@ acidzombie24. Metode reguler dapat memodifikasi keadaan statis juga sehingga sama buruknya dengan metode statis. Fakta bahwa mereka juga dapat mengubah keadaan dengan siklus hidup yang lebih pendek membuat mereka lebih berbahaya lagi. (Saya tidak mendukung metode statis, tetapi poin tentang modifikasi negara adalah pukulan untuk metode biasa juga, bahkan lebih lagi)
mike30

Jawaban:

105

Melihat jawaban lain di sini, saya pikir mungkin ada beberapa kebingungan antara metode statis yang tahan keadaan statis atau menyebabkan efek samping (yang menurut saya seperti ide yang sangat buruk), dan metode statis yang hanya mengembalikan nilai.

Metode statis yang tidak tahan dan tidak menimbulkan efek samping harus mudah diuji unit. Bahkan, saya menganggap metode semacam itu sebagai bentuk pemrograman fungsional "orang miskin"; Anda memberikan metode objek atau nilai, dan itu mengembalikan objek atau nilai. Tidak ada lagi. Saya tidak melihat bagaimana metode seperti itu akan mempengaruhi pengujian unit sama sekali.

Robert Harvey
sumber
44
Metode statis dapat diuji unit, tetapi bagaimana dengan metode yang memanggil metode statis? Jika penelepon berada di kelas lain maka mereka memiliki ketergantungan yang perlu dipisahkan untuk melakukan tes unit.
Vaccano
22
@ Vaccano: Tetapi jika Anda menulis metode statis yang tidak tahan keadaan dan tidak memiliki efek samping, mereka lebih atau kurang fungsional setara dengan rintisan.
Robert Harvey
20
Yang sederhana mungkin. Tetapi yang lebih kompleks dapat mulai melemparkan pengecualian dan memiliki output yang tidak diharapkan (dan harus ditangkap dalam unit test untuk metode statis, bukan dalam unit test untuk penelepon metode statis), atau setidaknya itulah yang saya telah dituntun untuk percaya pada literatur yang telah saya baca.
Vaccano
21
@ Vaccano: Metode statis yang memiliki keluaran atau pengecualian acak memiliki efek samping.
Tikhon Jelvis
10
@TikhonJelvis Robert sedang berbicara tentang output; dan pengecualian "acak" tidak boleh menjadi efek samping, mereka pada dasarnya adalah bentuk output. Intinya adalah, setiap kali Anda menguji metode yang memanggil metode statis, Anda membungkus metode itu dan semua permutasi dari output potensial, dan tidak dapat menguji metode Anda secara terpisah.
Nicole
26

Anda tampaknya membingungkan data statis dan metode statis . Resharper, jika saya ingat dengan benar, merekomendasikan membuat privatemetode dalam kelas statis jika dapat dibuat demikian - saya percaya ini menghasilkan manfaat kinerja yang kecil. Ini tidak merekomendasikan membuat "sesuatu yang dapat" statis!

Tidak ada yang salah dengan metode statis dan mudah diuji (selama tidak mengubah data statis). Sebagai contoh, pikirkan perpustakaan Matematika, yang merupakan kandidat yang baik untuk kelas statis dengan metode statis. Jika Anda memiliki metode (dibuat-buat) seperti ini:

public static long Square(int x)
{
    return x * x;
}

maka ini sangat dapat diuji dan tidak memiliki efek samping. Anda hanya memeriksa bahwa ketika Anda lulus, katakanlah, 20 Anda mendapatkan kembali 400. Tidak masalah.

Dan Diplo
sumber
4
Apa yang terjadi ketika Anda memiliki kelas lain yang memanggil metode statis ini? Tampak sederhana dalam kasus ini, tetapi ini adalah ketergantungan yang tidak dapat diisolasi kecuali dengan salah satu dari tiga alat yang tercantum di atas. Jika Anda tidak mengisolasinya, maka Anda bukan "unit testing", melainkan "pengujian integrasi" (karena Anda menguji seberapa baik unit Anda yang berbeda "berintegrasi" bersama-sama.
Vaccano
3
Tidak ada yang terjadi. Kenapa harus begitu? Kerangka .NET penuh dengan metode statis. Apakah Anda mengatakan Anda tidak dapat menggunakan ini dalam metode apa pun yang Anda ingin uji unit?
Dan Diplo
3
Nah, jika kode Anda berada pada tingkat produksi / kualitas .NET Framework maka yakin, silakan. Tetapi inti dari pengujian unit adalah menguji unit, secara terpisah. Jika Anda juga memanggil metode di unit lain (baik itu statis atau lainnya) maka Anda sekarang sedang menguji unit Anda ditambah ketergantungannya. Saya tidak mengatakan itu bukan tes yang berguna, tetapi bukan "unit test" oleh sebagian besar definisi. (Karena Anda sekarang sedang menguji unit-under-test Anda dan unit yang memiliki metode statis).
Vaccano
10
Mungkin Anda (atau orang lain) telah menguji metode statis dan menunjukkan bahwa ia berfungsi (setidaknya seperti yang diharapkan) sebelum menulis terlalu banyak kode di sekitarnya. Jika ada yang rusak pada bagian yang Anda uji selanjutnya, di situlah Anda harus melihat dulu, bukan pada barang yang sudah Anda uji.
cHao
6
@ Vacano Jadi bagaimana Microsoft menguji .NET Framework, lalu, eh? Banyak kelas dalam kerangka acuan metode statis di kelas lain (seperti System.Math) belum lagi kelimpahan metode pabrik statis dll. Plus Anda tidak akan pernah dapat menggunakan metode ekstensi dll. Faktanya adalah, fungsi sederhana seperti ini adalah mendasar untuk bahasa modern. Anda dapat menguji ini secara terpisah (karena umumnya akan bersifat deterministik) dan kemudian menggunakannya di kelas Anda tanpa khawatir. Itu bukan masalah!
Dan Diplo
18

Jika pertanyaan sebenarnya di sini adalah "Bagaimana cara menguji kode ini?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Kemudian, cukup refactor kode dan menyuntikkan seperti biasa panggilan ke kelas statis seperti ini:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
Cerah
sumber
9
Untungnya kami juga memiliki pertanyaan tentang keterbacaan kode dan KISS on SO juga :)
gbjbaanb
Apakah seorang delegasi tidak akan lebih mudah dan melakukan hal yang sama?
jk.
@ jk bisa saja, tetapi sulit untuk menggunakan wadah IOC itu.
Cerah
15

Statika tidak selalu jahat, tetapi mereka dapat membatasi pilihan Anda ketika datang ke pengujian unit dengan palsu / mengolok-olok / bertopik.

Ada dua pendekatan umum untuk mengejek.

Yang pertama (tradisional - dilaksanakan oleh RhinoMocks, Moq, NMock2; mengolok-olok manual dan bertopik juga di kamp ini) bergantung pada jahitan uji dan injeksi ketergantungan. Misalkan Anda sedang menguji beberapa kode statis dan memiliki dependensi. Apa yang sering terjadi dalam kode yang dirancang dengan cara ini adalah statika menciptakan dependensi mereka sendiri, membalikkan inversi dependensi . Anda segera menemukan bahwa Anda tidak dapat menyuntikkan antarmuka tiruan ke dalam kode yang sedang diuji yang dirancang dengan cara ini.

Yang kedua (mengejek apa pun - diimplementasikan oleh TypeMock, JustMock dan Moles) bergantung pada .NET's Profileing API . Itu dapat mencegat salah satu instruksi CIL Anda dan mengganti sepotong kode Anda dengan yang palsu. Ini memungkinkan TypeMock dan produk lain di kamp ini untuk mengolok-olok apa pun: statika, kelas tertutup, metode pribadi - hal-hal yang tidak dirancang untuk dapat diuji.

Ada perdebatan yang sedang berlangsung antara dua aliran pemikiran. Seseorang mengatakan, ikuti prinsip - prinsip dan desain SOLID untuk testabilitas (yang sering termasuk statika mudah). Yang lain mengatakan, beli TypeMock dan jangan khawatir.

azheglov
sumber
14

Lihat ini: "Metode Statis adalah Kematian untuk Testabilitas" . Ringkasan singkat argumen:

Untuk menguji unit Anda perlu mengambil sepotong kecil kode Anda, rewire dependensi dan mengujinya secara terpisah. Ini sulit dengan metode statis, tidak hanya dalam kasus mereka mengakses keadaan global tetapi bahkan jika mereka hanya memanggil metode statis lainnya.

Rafał Dowgird
sumber
32
Saya tidak menyimpan status global atau menyebabkan efek samping dalam metode statis saya, jadi argumen ini tampaknya tidak relevan bagi saya. Artikel yang Anda tautkan membuat argumen lereng yang licin yang tidak memiliki dasar jika metode statis terbatas pada kode prosedural sederhana, bertindak dengan cara "generik" (seperti fungsi matematika).
Robert Harvey
4
@ Robert Harvey - bagaimana Anda menguji unit metode yang menggunakan metode statis dari kelas lain (yaitu memiliki ketergantungan pada metode statis). Jika Anda membiarkannya, maka Anda bukan "unit testing", melainkan "pengujian integrasi"
Vaccano
10
Jangan hanya membaca artikel blog, baca banyak komentar yang tidak setuju dengannya. Sebuah blog hanyalah sebuah opini, bukan fakta.
Dan Diplo
8
@ Vaccano: Metode statis tanpa efek samping atau keadaan eksternal secara fungsional setara dengan nilai. Mengetahui hal itu, tidak ada bedanya dengan unit yang menguji metode yang membuat integer. Ini adalah salah satu wawasan utama yang diberikan pemrograman fungsional kepada kita.
Steven Evers
3
Cegah sistem embedded bekerja atau aplikasi yang bug-nya memiliki potensi untuk menciptakan insiden internasional, IMO, ketika "testability" menggerakkan arsitektur, Anda telah memutarnya sebelum Anda mengadopsi metodologi test-every-last-corner-corner. Saya juga bosan dengan versi XP dari segalanya mendominasi setiap percakapan. XP bukan otoritas, ini adalah industri. Inilah definisi asli dan masuk akal dari pengujian unit: python.net/crew/tbryan/UnitTestTalk/slide2.html
Erik Reppen
5

Kebenaran sederhana yang jarang diakui adalah bahwa jika sebuah kelas berisi ketergantungan kompiler-terlihat pada kelas lain, itu tidak dapat diuji secara terpisah dari kelas itu. Anda dapat memalsukan sesuatu yang terlihat seperti tes, dan akan muncul pada laporan seolah-olah itu adalah tes.

Tapi itu tidak akan memiliki sifat penentu utama dari suatu tes; gagal ketika ada sesuatu yang salah, melewati ketika mereka benar.

Ini berlaku untuk panggilan statis, panggilan konstruktor, dan referensi apa pun untuk metode atau bidang yang tidak diwarisi dari kelas dasar atau antarmuka . Jika nama kelas muncul dalam kode, itu adalah ketergantungan kompiler-terlihat, dan Anda tidak dapat menguji tanpa itu. Setiap potongan yang lebih kecil bukan merupakan unit yang dapat diuji valid . Setiap upaya untuk memperlakukannya seolah-olah akan memiliki hasil yang tidak lebih bermakna daripada menulis sedikit utilitas untuk memancarkan XML yang digunakan oleh kerangka pengujian Anda untuk mengatakan 'lulus uji'.

Mengingat bahwa, ada tiga opsi:

  1. mendefinisikan pengujian unit untuk menguji unit yang terdiri dari kelas dan itu dependensi hard-coded. Ini berfungsi, asalkan Anda menghindari ketergantungan sirkular.

  2. jangan pernah membuat dependensi waktu kompilasi antara kelas yang Anda bertanggung jawab untuk pengujian. Ini berfungsi, asalkan Anda tidak keberatan dengan gaya kode yang dihasilkan.

  3. jangan uji unit, tes integrasi sebagai gantinya. Yang berfungsi, asalkan tidak bertentangan dengan hal lain yang Anda perlukan untuk menggunakan pengujian integrasi istilah.

soru
sumber
3
Tidak ada yang mengatakan bahwa tes unit adalah tes kelas tunggal. Ini adalah tes satu unit. Sifat-sifat yang menentukan dari unit test adalah bahwa tes-tes tersebut cepat, dapat diulang, dan independen. Merujuk Math.Pidalam suatu metode tidak menjadikannya sebagai tes integrasi dengan definisi yang masuk akal.
sara
yaitu opsi 1. Saya setuju itu pendekatan terbaik, tetapi bisa berguna untuk mengetahui orang lain menggunakan istilah secara berbeda (masuk akal atau tidak).
soru
4

Tidak ada dua cara tentang itu. Saran ReSharper dan beberapa fitur berguna dari C # tidak akan digunakan sesering jika Anda menulis tes unit atom terisolasi untuk semua kode Anda.

Misalnya, jika Anda memiliki metode statis dan Anda harus mematikannya, Anda tidak bisa kecuali Anda menggunakan kerangka kerja isolasi berbasis profil. Pemecahan masalah yang kompatibel dengan panggilan adalah mengubah bagian atas metode untuk menggunakan notasi lambda. Sebagai contoh:

SEBELUM:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

SETELAH:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Keduanya kompatibel dengan panggilan. Penelepon tidak harus berubah. Tubuh fungsi tetap sama.

Kemudian dalam kode Unit-Test Anda, Anda dapat mematikan panggilan ini seperti itu (dengan asumsi itu dalam kelas yang disebut Database):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Berhati-hatilah untuk menggantinya dengan nilai asli setelah Anda selesai. Anda dapat melakukannya melalui percobaan / akhirnya atau, dalam pembersihan unit-tes Anda, tes yang dipanggil setelah setiap tes, tulis kode seperti ini:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

yang akan memanggil kembali initializer statis kelas Anda.

Fungsi Lambda tidak terlalu kaya dalam dukungan seperti metode statis biasa, sehingga pendekatan ini memiliki efek samping yang tidak diinginkan:

  1. Jika metode statis adalah metode ekstensi, Anda harus mengubahnya ke metode non-ekstensi terlebih dahulu. Resharper dapat melakukan ini untuk Anda secara otomatis.
  2. Jika salah satu tipe data dari metode statis adalah rakitan tertanam-interop, seperti untuk Office, Anda harus membungkus metode, membungkus jenis atau mengubahnya untuk mengetikkan 'objek'.
  3. Anda tidak bisa lagi menggunakan alat refactoring perubahan tanda tangan Resharper.

Tetapi katakanlah Anda menghindari statika sama sekali, dan Anda mengonversinya menjadi metode instan. Itu masih tidak bisa dipermainkan kecuali metode ini virtual atau diimplementasikan sebagai bagian dari antarmuka.

Jadi pada kenyataannya, siapa pun yang menyarankan solusi untuk mematikan metode statis adalah menjadikannya metode instan, mereka juga akan menentang metode instan yang bukan virtual atau bagian dari antarmuka.

Jadi mengapa C # memiliki metode statis? Mengapa ini memungkinkan metode instance non-virtual?

Jika Anda menggunakan salah satu dari "Fitur" ini, maka Anda tidak dapat membuat metode yang terisolasi.

Jadi kapan Anda menggunakannya?

Gunakan mereka untuk kode apa pun yang Anda tidak harapkan ada orang yang ingin mematikannya. Beberapa contoh: metode Format () dari kelas String metode WriteLine () dari kelas Konsol metode Cosh () dari kelas Matematika

Dan satu hal lagi .. Kebanyakan orang tidak akan peduli tentang ini, tetapi jika Anda bisa tentang kinerja panggilan tidak langsung, itu alasan lain untuk menghindari metode instan. Ada kasus ketika itu adalah hit kinerja. Itu sebabnya metode non-virtual ada di tempat pertama.

zumalifeguard
sumber
3
  1. Saya percaya itu sebagian karena metode statis "lebih cepat" untuk memanggil daripada metode contoh. (Dalam kutipan karena ini bau optimasi mikro) lihat http://dotnetperls.com/static-method
  2. Ini memberi tahu Anda bahwa itu tidak perlu keadaan, karena itu dapat dipanggil dari mana saja, menghapus overhead instatiation jika itu satu-satunya hal yang dibutuhkan seseorang.
  3. Jika saya ingin mengejeknya, maka saya pikir ini adalah praktik umum yang dinyatakan pada antarmuka.
  4. Jika dideklarasikan pada antarmuka maka R # tidak akan menyarankan Anda membuatnya statis.
  5. Jika dinyatakan virtual, maka R # tidak akan menyarankan Anda membuatnya statis juga.
  6. Holding state (bidang) secara statis adalah sesuatu yang harus selalu dipertimbangkan dengan cermat . Keadaan statis dan benang bercampur seperti lithium dan air.

R # bukan satu-satunya alat yang akan membuat saran ini. Analisis Kode FxCop / MS juga akan melakukan hal yang sama.

Saya biasanya akan mengatakan bahwa jika metode ini statis, umumnya harus dapat diuji juga. Itu membawa beberapa pertimbangan desain dan mungkin lebih banyak diskusi daripada yang saya miliki di jari saya sekarang, jadi dengan sabar menunggu suara turun dan komentar ...;)

MIA
sumber
Bisakah Anda mendeklarasikan metode / objek statis pada antarmuka? (Kurasa tidak). Eter cara, saya maksudkan ketika metode statis Anda dipanggil oleh metode yang diuji. Jika pemanggil berada di unit yang berbeda maka metode statis perlu diisolasi. Ini sangat sulit dilakukan tanpa salah satu dari tiga alat yang tercantum di atas.
Vaccano
1
Tidak, Anda tidak dapat mendeklarasikan statik pada antarmuka. Itu tidak akan berarti.
MIA
3

Saya melihat bahwa setelah lama tidak ada yang menyatakan fakta yang sangat sederhana. Jika resharper mengatakan kepada saya bahwa saya dapat membuat metode statis, itu berarti hal yang sangat besar bagi saya, saya dapat mendengar suaranya mengatakan kepada saya: "hei, kamu, potongan-potongan logika ini bukan TANGGUNG JAWAB kelas saat ini untuk ditangani, jadi harus tetap di luar di beberapa kelas pembantu atau sesuatu ".

g1ga
sumber
2
Saya tidak setuju. Sebagian besar waktu ketika Resharper mengatakan saya dapat membuat sesuatu yang statis itu adalah sedikit kode yang umum untuk dua atau lebih metode di kelas dan dengan demikian saya mengekstraknya ke prosedur itu sendiri. Memindahkan itu ke penolong akan menjadi kompleksitas yang tidak berarti.
Loren Pechtel
2
Saya dapat melihat poin Anda hanya jika domainnya sangat sederhana dan tidak cocok untuk modifikasi di masa mendatang. Secara berbeda, apa yang Anda sebut "kompleksitas tidak berarti" itu baik dan desain yang dapat dibaca manusia bagi saya. Memiliki kelas pembantu dengan alasan sederhana dan jelas untuk ada dalam beberapa cara adalah "mantra" dari prinsip-prinsip SoC dan Single Responsibility. Selain itu, mengingat bahwa kelas baru ini menjadi ketergantungan bagi kelas utama, ia harus mengekspos beberapa anggota publik, dan sebagai konsekuensinya ia menjadi dapat diuji secara terpisah dan mudah diejek ketika bertindak sebagai ketergantungan.
g1ga
1
Tidak relevan dengan pertanyaan.
Igby Largeman
2

Jika metode statis dipanggil dari dalam metode lain , tidak mungkin untuk mencegah atau mengganti panggilan semacam itu. Ini berarti, kedua metode ini membuat Unit tunggal; Unit test jenis apa pun menguji keduanya.

Dan jika metode statis ini berbicara ke Internet, menghubungkan basis data, menampilkan popup GUI atau mengonversi uji Unit menjadi kekacauan total, itu hanya dilakukan tanpa ada penyelesaian yang mudah. Sebuah metode yang memanggil metode statis seperti itu tidak dapat diuji tanpa refactoring, bahkan jika ia memiliki banyak kode komputasi murni yang akan sangat diuntungkan dengan menjadi unit yang diuji.

h22
sumber
0

Saya percaya bahwa Resharper memberi Anda petunjuk dan menerapkan pedoman pengkodean yang telah disiapkan. Ketika saya telah menggunakan Resharper dan telah memberi tahu saya bahwa suatu metode harus statis itu terikat pada metode pribadi yang tidak bertindak pada variabel contoh apa pun.

Sekarang untuk testablity skenario ini seharusnya tidak menjadi masalah karena Anda tidak harus menguji metode pribadi.

Sedangkan untuk pengujian metode statis yang bersifat publik maka pengujian unit menjadi sulit ketika metode statis menyentuh keadaan statis. Secara pribadi saya akan menjaga ini seminimal mungkin dan menggunakan metode statis sebagai fungsi murni sebanyak mungkin di mana setiap dependensi dilewatkan ke dalam metode yang dapat dikontrol melalui perlengkapan uji. Namun ini adalah keputusan desain.

aqwert
sumber
Saya merujuk ketika Anda memiliki metode yang sedang diuji (bukan statis) yang memanggil metode statis di kelas lain. Ketergantungan itu hanya dapat diisolasi dengan satu dari tiga alat yang tercantum di atas. Jika Anda tidak mengisolasinya maka Anda menguji integrasi, bukan pengujian unit.
Vaccano
Mengakses variabel instan secara inheren berarti itu tidak boleh statis. Satu-satunya keadaan yang bisa dimiliki metode statis adalah variabel kelas dan satu-satunya alasan yang harus terjadi adalah jika Anda berurusan dengan singleton dan karenanya tidak punya pilihan.
Loren Pechtel