Unit menguji metode pribadi dalam C #

291

Visual Studio memungkinkan pengujian unit metode pribadi melalui kelas accessor yang dihasilkan secara otomatis. Saya telah menulis tes metode pribadi yang berhasil dikompilasi, tetapi gagal saat runtime. Versi kode yang cukup minimal dan tesnya adalah:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Kesalahan runtime adalah:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Menurut intellisense - dan karenanya saya kira kompiler - target adalah tipe TypeA_Accessor. Tetapi pada saat runtime itu adalah tipe TypeA, dan karenanya menambahkan daftar gagal.

Apakah ada cara saya bisa menghentikan kesalahan ini? Atau, mungkin lebih mungkin, apa saran orang lain (saya memperkirakan mungkin "jangan menguji metode pribadi" dan "tidak memiliki unit test memanipulasi keadaan objek").

junichiro
sumber
Anda memerlukan pengakses untuk kelas pribadi TypeB. Accessor TypeA_Accessor menyediakan akses ke metode TypeA pribadi dan dilindungi. Namun TypeB bukan metode. Itu kelas.
Dima
Accessor menyediakan akses ke metode, anggota, properti, dan acara pribadi / yang dilindungi. Itu tidak menyediakan akses ke kelas privat / terlindungi di dalam kelas Anda. Dan kelas privat / terlindungi (TypeB) dimaksudkan untuk digunakan hanya dengan metode memiliki kelas (TypeA). Jadi pada dasarnya Anda mencoba menambahkan kelas privat (TypeB) dari luar TypeA ke "myList" yang bersifat pribadi. Karena Anda menggunakan accessor, tidak ada masalah untuk mengakses myList. Namun Anda tidak dapat menggunakan TypeB melalui accessor. Solusi posiible adalah memindahkan TypeB di luar TypeA. Tapi itu bisa merusak desain Anda.
Dima
Merasa bahwa pengujian metode pribadi harus dilakukan oleh stackoverflow.com/questions/250692/…
nate_weldon

Jawaban:

274

Ya, jangan Uji metode pribadi .... Gagasan uji unit adalah untuk menguji unit dengan 'API' publiknya.

Jika Anda merasa perlu menguji banyak perilaku pribadi, kemungkinan besar Anda memiliki 'kelas' yang bersembunyi di dalam kelas yang Anda coba uji, ekstrak dan uji dengan antarmuka publiknya.

Salah satu saran / alat Berpikir ..... Ada ide bahwa tidak ada metode yang harus pribadi. Berarti semua metode harus hidup pada antarmuka publik dari suatu objek .... jika Anda merasa perlu menjadikannya pribadi, kemungkinan besar tinggal di objek lain.

Nasihat ini tidak begitu berhasil dalam praktiknya, tetapi sebagian besar sarannya bagus, dan seringkali akan mendorong orang untuk menguraikan benda-benda mereka menjadi benda-benda yang lebih kecil.

Keith Nicholas
sumber
385
Saya tidak setuju. Dalam OOD, metode dan properti pribadi adalah cara intrinsik untuk tidak mengulangi diri Anda sendiri ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Gagasan di balik pemrograman kotak hitam dan enkapsulasi adalah untuk menyembunyikan detail teknis dari pelanggan. Jadi memang perlu memiliki metode pribadi dan properti non-sepele dalam kode Anda. Dan jika itu non-sepele, itu perlu diuji.
AxD
8
ini bukan intrinsik, beberapa bahasa OO tidak memiliki metode pribadi, properti pribadi dapat berisi objek dengan antarmuka publik yang dapat diuji.
Keith Nicholas
7
Inti dari saran ini, adalah jika Anda objek Anda melakukan satu hal, dan KERING, maka sering ada sedikit alasan untuk memiliki metode pribadi. Seringkali metode pribadi melakukan sesuatu yang objeknya tidak benar-benar bertanggung jawab tetapi cukup berguna, jika tidak sepele, maka umumnya adalah objek lain karena kemungkinan melanggar SRP
Keith Nicholas
28
Salah. Anda mungkin ingin menggunakan metode pribadi untuk menghindari duplikasi kode. Atau untuk validasi. Atau untuk banyak tujuan lain yang dunia seharusnya tidak ketahui.
Jorj
37
Ketika Anda telah dibuang pada basis kode OO yang dirancang begitu mengerikan dan diminta untuk "secara bertahap meningkatkannya", itu mengecewakan saya menemukan bahwa saya tidak dapat melakukan beberapa tes pertama untuk metode pribadi. Yeh, mungkin dalam buku teks metode ini tidak akan ada di sini, tetapi di dunia nyata kita memiliki pengguna yang memiliki persyaratan produk. Saya tidak bisa hanya melakukan refactoring "Lihatlah kode ma" yang besar tanpa mendapatkan beberapa tes dalam proyek untuk membangun. Terasa seperti contoh lain dari memaksa memprogram programmer ke jalan yang secara naif tampak bagus, tetapi gagal untuk memperhitungkan omong kosong yang benar-benar berantakan.
dune.rocks
666

Anda dapat menggunakan Kelas PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Membatalkan
sumber
25
Ini adalah jawaban yang benar, sekarang Microsoft telah menambahkan PrivateObject.
Zoey
4
Jawaban yang bagus tetapi harap dicatat bahwa PrivateMethod perlu "dilindungi" sebagai pengganti "pribadi".
HerbalMart
23
@HerbalMart: Mungkin saya salah paham dengan Anda, tetapi jika Anda menyarankan bahwa PrivateObject hanya dapat mengakses anggota yang dilindungi dan bukan yang pribadi, Anda salah.
kmote
17
@JeffPearce Untuk metode statis Anda dapat menggunakan "PrivateType pt = new PrivateType (typeof (MyClass));", dan kemudian panggil InvokeStatic pada objek pt seperti Anda akan memanggil Invoke pada objek pribadi.
Steve Hibbert
12
Jika ada yang bertanya-tanya pada MSTest.TestFramework v1.2.1 - kelas PrivateObject dan PrivateType tidak tersedia untuk proyek yang menargetkan .NET Core 2.0 - Ada masalah github untuk ini: github.com/Microsoft/testfx/issues/366
shiitake
98

"Tidak ada yang disebut sebagai standar atau praktik terbaik, mungkin itu hanya pendapat populer".

Hal yang sama juga berlaku untuk diskusi ini.

masukkan deskripsi gambar di sini

Itu semua tergantung pada apa yang Anda anggap unit, jika Anda berpikir UNIT adalah kelas maka Anda hanya akan mencapai metode publik. Jika Anda berpikir UNIT adalah jalur kode yang mengenai metode pribadi tidak akan membuat Anda merasa bersalah.

Jika Anda ingin memanggil metode pribadi, Anda bisa menggunakan kelas "PrivateObject" dan memanggil metode panggil. Anda dapat menonton video youtube mendalam ini ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) yang menunjukkan cara menggunakan "PrivateObject" dan juga membahas apakah pengujian metode pribadi logis atau tidak.

Shivprasad Koirala
sumber
55

Pemikiran lain di sini adalah untuk memperluas pengujian ke kelas / metode "internal", memberikan lebih banyak rasa kotak putih dari pengujian ini. Anda dapat menggunakan InternalsVisibleToAttribute pada perakitan untuk mengekspos ini ke modul pengujian unit terpisah.

Dalam kombinasi dengan kelas tertutup, Anda dapat mendekati enkapsulasi sedemikian rupa sehingga metode pengujian hanya dapat dilihat dari perakitan metode Anda yang tidak terbaik. Pertimbangkan bahwa metode yang dilindungi dalam kelas tertutup adalah de facto pribadi.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

Dan unit test:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
sumber
Saya tidak yakin saya mengerti. InternalsVisibleToAttribute membuat metode dan atribut yang ditandai sebagai "internal" dapat diakses, tetapi bidang dan metode saya "pribadi". Apakah Anda menyarankan saya mengubah sesuatu dari menjadi pribadi ke internal? Saya pikir saya salah paham.
junichiro
2
Ya, itulah yang saya sarankan. Agak "hacky", tapi setidaknya mereka tidak "publik".
Jeff
27
Ini adalah jawaban yang bagus hanya karena itu tidak mengatakan "jangan menguji metode pribadi" tapi ya, itu cukup "peretasan". Saya berharap ada solusinya. IMO itu buruk untuk mengatakan "metode pribadi tidak boleh diuji" karena cara saya melihatnya: itu setara dengan "metode pribadi tidak boleh benar".
MasterMastic
5
ya ken, saya juga bingung dengan mereka yang mengklaim bahwa metode pribadi tidak boleh diuji dalam unit test. Public API adalah output, tetapi terkadang implementasi yang salah juga memberikan output yang tepat. Atau implementasi membuat beberapa efek samping yang buruk, misalnya memegang sumber daya yang tidak perlu, merujuk objek yang mencegahnya dikumpulkan oleh gc ... dll. Kecuali mereka memberikan tes lain yang dapat mencakup metode pribadi daripada tes unit, jika tidak saya akan mempertimbangkan bahwa mereka tidak dapat mempertahankan kode yang diuji 100%.
mr.Pony
Saya setuju dengan MasterMastic. Ini harus menjadi jawaban yang diterima.
XDS
27

Salah satu cara untuk menguji metode pribadi adalah melalui refleksi. Ini berlaku untuk NUnit dan XUnit, juga:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
sumber
call methods statis dan tidak statis ?
Kiquenet
2
Kelemahan dari metode yang bergantung pada refleksi adalah bahwa mereka cenderung rusak ketika Anda mengganti nama metode menggunakan R #. Ini mungkin bukan masalah besar pada proyek-proyek kecil tetapi pada basis kode besar menjadi agak mengganggu untuk memiliki unit test melanggar sedemikian rupa dan kemudian harus berkeliling dan memperbaikinya dengan cepat. Dalam hal ini saya, uang saya digunakan untuk jawaban Jeff.
XDS
2
@XDS Terlalu buruk nameof () tidak berfungsi untuk mendapatkan nama metode pribadi dari luar kelasnya.
Gabriel Morin
12

Ermh ... Datang ke sini dengan masalah yang persis sama: Uji metode pribadi yang sederhana namun penting . Setelah membaca utas ini, tampaknya seperti "Saya ingin mengebor lubang sederhana ini dalam potongan logam sederhana ini, dan saya ingin memastikan kualitasnya memenuhi spesifikasi", dan kemudian muncul "Oke, ini tidak mudah. Pertama-tama, tidak ada alat yang tepat untuk melakukannya, tetapi Anda bisa membangun observatorium gelombang gravitasi di taman Anda. Baca artikel saya di http://foobar.brigther-than-einstein.org/ Pertama, tentu saja, Anda harus mengikuti beberapa kursus fisika kuantum tingkat lanjut, maka Anda perlu banyak nitrogenium yang sangat dingin, dan kemudian, tentu saja, buku saya tersedia di Amazon "...

Dengan kata lain...

Tidak, hal pertama yang pertama.

Setiap metode, baik itu swasta, internal, terlindungi, publik harus dapat diuji. Harus ada cara untuk mengimplementasikan tes tersebut tanpa basa-basi seperti yang disajikan di sini.

Mengapa? Tepatnya karena arsitektur menyebutkan sejauh ini dilakukan oleh beberapa kontributor. Mungkin pengulangan sederhana prinsip-prinsip perangkat lunak dapat menjernihkan beberapa kesalahpahaman.

Dalam hal ini, tersangka yang biasa adalah: OCP, SRP, dan, seperti biasa, KIS.

Tapi tunggu dulu. Gagasan membuat segala sesuatu tersedia untuk umum lebih bersifat kurang politis dan semacam sikap. Tapi. Ketika datang ke kode, bahkan di Komunitas Open Source saat itu, ini bukan dogma. Alih-alih, "menyembunyikan" sesuatu adalah praktik yang baik untuk membuatnya lebih mudah mengenal API tertentu. Anda akan menyembunyikan, misalnya, perhitungan inti dari blok bangunan termometer digital baru Anda untuk pasar - tidak untuk menyembunyikan matematika di belakang kurva yang diukur nyata untuk pembaca kode yang ingin tahu, tetapi untuk mencegah kode Anda menjadi tergantung pada beberapa, mungkin tiba-tiba pengguna penting yang tidak bisa menolak menggunakan kode pribadi, internal, dan terproteksi Anda sebelumnya untuk mengimplementasikan ide mereka sendiri.

Apa yang saya bicarakan?

private double TranslateMeasurementIntoLinear (double actualMeasurement);

Sangat mudah untuk memproklamirkan Zaman Aquarius atau apa yang disebut saat ini, tetapi jika sensor saya mendapatkan dari 1.0 ke 2.0, implementasi Translate ... dapat berubah dari persamaan linear sederhana yang mudah dimengerti dan "dikembalikan". dapat digunakan "untuk semua orang, untuk perhitungan yang cukup canggih yang menggunakan analisis atau apa pun, dan jadi saya akan memecahkan kode orang lain. Mengapa? Karena mereka tidak memahami prinsip pengkodean perangkat lunak, bahkan KIS.

Untuk membuat dongeng ini singkat: Kita perlu cara sederhana untuk menguji metode pribadi - tanpa basa-basi.

Pertama: Selamat tahun baru semuanya!

Kedua: Latih pelajaran arsitek Anda.

Ketiga: Pengubah "publik" adalah agama, bukan solusi.

CP70
sumber
5

Opsi lain yang belum disebutkan adalah hanya menciptakan kelas uji unit sebagai anak dari objek yang Anda uji. Contoh NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Ini akan memungkinkan pengujian mudah metode pribadi dan terlindungi (tetapi tidak diwariskan), dan itu akan memungkinkan Anda untuk menjaga semua tes Anda terpisah dari kode nyata sehingga Anda tidak menggunakan majelis uji untuk produksi. Mengubah metode pribadi Anda ke metode yang dilindungi akan diterima di banyak objek yang diwarisi, dan itu adalah perubahan yang cukup sederhana untuk dilakukan.

NAMUN...

Meskipun ini merupakan pendekatan yang menarik untuk memecahkan masalah tentang cara menguji metode tersembunyi, saya tidak yakin bahwa saya akan menganjurkan bahwa ini adalah solusi yang tepat untuk masalah dalam semua kasus. Tampaknya agak aneh untuk secara internal menguji suatu objek, dan saya menduga mungkin ada beberapa skenario bahwa pendekatan ini akan meledak pada Anda. (Objek yang tidak dapat diubah misalnya, mungkin membuat beberapa tes sangat sulit).

Sementara saya menyebutkan pendekatan ini, saya akan menyarankan bahwa ini lebih merupakan saran curah pendapat daripada solusi yang sah. Ambillah dengan sebutir garam.

EDIT: Saya merasa benar-benar lucu bahwa orang memilih jawaban ini, karena saya secara eksplisit menggambarkan ini sebagai ide yang buruk. Apakah itu berarti orang setuju dengan saya? Aku sangat bingung.....

Roger Hill
sumber
Ini solusi kreatif tetapi agak retas.
shinzou
3

Dari buku Bekerja Efektif dengan Legacy Code :

"Jika kita perlu menguji metode pribadi, kita harus mempublikasikannya. Jika mempublikasikannya mengganggu kita, dalam banyak kasus, itu berarti kelas kita melakukan terlalu banyak dan kita harus memperbaikinya."

Cara untuk memperbaikinya, menurut penulis, adalah dengan membuat kelas baru dan menambahkan metode sebagai public.

Penulis menjelaskan lebih lanjut:

"Desain yang bagus bisa diuji, dan desain yang tidak bisa diuji itu buruk."

Jadi, dalam batas-batas ini, satu-satunya pilihan nyata Anda adalah membuat metode public, baik di kelas saat ini atau yang baru.

Kai Hartmann
sumber
2

TL; DR: Ekstrak metode pribadi ke kelas lain, uji pada kelas itu; baca lebih lanjut tentang prinsip SRP (Prinsip Tanggung Jawab Tunggal)

Tampaknya Anda perlu mengekstrak ke privatemetode ke kelas lain; dalam hal ini seharusnya public. Alih-alih mencoba menguji privatemetode ini, Anda harus mengujipublic metode kelas ini.

Kami memiliki skenario berikut:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Kita perlu menguji logika _someLogic; tetapi tampaknya Class Amengambil peran lebih dari yang dibutuhkan (melanggar prinsip SRP); hanya refactor menjadi dua kelas

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Dengan cara ini someLogicdapat diuji pada A2; di A1 hanya membuat beberapa A2 palsu lalu menyuntikkan ke konstruktor untuk menguji bahwa A2 dipanggil ke fungsi bernama someLogic.

o0omycomputero0o
sumber
0

Dalam VS 2005/2008 Anda dapat menggunakan accessor pribadi untuk menguji anggota pribadi, tetapi cara ini menghilang di versi VS yang lebih baru

Allen
sumber
1
Jawaban yang bagus di tahun 2008 hingga mungkin awal 2010. Sekarang silakan lihat alternatif PrivateObject dan Reflection (lihat beberapa jawaban di atas). VS2010 memiliki bug accessor, MS tidak lagi menggunakannya dalam VS2012. Kecuali Anda terpaksa tinggal di VS2010 atau lebih tua (> 18 tahun build tooling), silakan menghemat waktu dengan menghindari aksesor pribadi. :-).
Zephan Schroeder