Bagaimana Anda menguji metode pribadi?

479

Saya sedang membangun perpustakaan kelas yang akan memiliki beberapa metode publik & pribadi. Saya ingin dapat menguji unit metode pribadi (kebanyakan sambil mengembangkan, tetapi juga bisa berguna untuk refactoring di masa depan).

Apa cara yang benar untuk melakukan ini?

Eric Labashosky
sumber
3
Saya mungkin kehilangan sesuatu, atau mungkin hanya pertanyaan ini, yah ... pre-historicdalam hal tahun Internet, tetapi pengujian unit metode pribadi sekarang mudah dan lurus ke depan, dengan Visual Studio menghasilkan kelas accessor yang diperlukan ketika diperlukan dan pra-mengisi logika tes dengan potongan yang hampir sesuai dengan keinginan seseorang untuk tes fungsional sederhana. Lihat misalnya. msdn.microsoft.com/en-us/library/ms184807%28VS.90%29.aspx
mjv
3
Ini sepertinya duplikat dekat dari stackoverflow.com/questions/34571/… .
Raedwald
Penanya mungkin tidak menggunakan studio visual
Dave
3
Jangan uji unit internal: blog.ploeh.dk/2015/09/22/unit-testing-internals
Mark Seemann

Jawaban:

122

Jika Anda menggunakan .net, Anda harus menggunakan InternalsVisibleToAttribute .

TcK
sumber
86
Yuck. Ini akan dikompilasi ke dalam majelis Anda yang dirilis.
Jay
14
@Jay - tidak bisa salah satu penggunaan #if DEBUGsekitar InternalsVisibleToatribut untuk membuatnya tidak berlaku untuk kode rilis?
mpontillo
20
@ Mike, Anda bisa, tetapi kemudian Anda hanya dapat menjalankan tes unit pada kode debug, bukan melepaskan kode. Karena kode rilis dioptimalkan, Anda mungkin melihat perilaku yang berbeda dan timing yang berbeda. Dalam kode multithreaded ini berarti unit test Anda tidak akan mendeteksi kondisi balapan dengan tepat. Jauh lebih baik menggunakan refleksi melalui saran @ AmazedSaint di bawah ini atau menggunakan PrivateObject / PrivateType bawaan. Ini memungkinkan Anda untuk melihat privat di rilis build dengan asumsi test harness Anda berjalan dengan kepercayaan penuh (yang MSTest jalankan secara lokal)
Jay
121
Apa yang saya lewatkan? Mengapa ini menjadi jawaban yang diterima padahal sebenarnya tidak menjawab pertanyaan khusus tentang pengujian metode pribadi? InternalsVisibleTo hanya memperlihatkan metode yang ditandai sebagai internal dan bukan yang ditandai pribadi seperti yang diminta oleh OP (dan alasan saya mendarat di sini). Saya kira saya harus terus menggunakan PrivateObject seperti dijawab oleh Seven?
Darren Lewis
6
@Jay Saya tahu ini agak terlambat, tetapi satu opsi adalah menggunakan sesuatu seperti di #if RELEASE_TESTsekitar InternalsVisibleToseperti yang disarankan Mike, dan buat salinan konfigurasi build rilis Anda yang menentukan RELEASE_TEST. Anda bisa menguji kode rilis Anda dengan optimisasi, tetapi ketika Anda benar-benar membangun untuk rilis, tes Anda akan dihilangkan.
Shaz
349

Jika Anda ingin menguji unit metode pribadi, sesuatu mungkin salah. Tes unit (umumnya berbicara) dimaksudkan untuk menguji antarmuka kelas, yang berarti metode publik (dan dilindungi). Anda tentu saja dapat "meretas" solusi untuk ini (bahkan jika hanya dengan membuat metode publik), tetapi Anda mungkin juga ingin mempertimbangkan:

  1. Jika metode yang ingin Anda uji benar-benar layak untuk diuji, mungkin ada baiknya untuk memindahkannya ke kelasnya sendiri.
  2. Tambahkan lebih banyak tes ke metode publik yang memanggil metode privat, menguji fungsionalitas metode privat. (Seperti yang ditunjukkan oleh komentator, Anda hanya boleh melakukan ini jika fungsionalitas metode privat ini benar-benar merupakan bagian dari antarmuka publik. Jika mereka benar-benar melakukan fungsi yang disembunyikan dari pengguna (yaitu unit test), ini mungkin buruk).
Jeroen Heijmans
sumber
38
Opsi 2 membuat unit test harus memiliki pengetahuan tentang implementasi fungsi yang mendasarinya. Saya tidak suka melakukan itu. Saya biasanya berpikir unit test harus menguji fungsi tersebut tanpa mengasumsikan implementasi.
Herms
15
Kerugian dari implementasi pengujian adalah bahwa tes akan rapuh untuk pecah jika Anda memperkenalkan perubahan pada implementasi. Dan ini tidak diinginkan karena refactoring sama pentingnya dengan menulis tes dalam TDD.
JtR
30
Nah, tes seharusnya rusak jika Anda mengubah implementasi. TDD berarti mengubah tes terlebih dahulu.
sleske
39
@sleske - Saya tidak begitu setuju. Jika fungsi tidak berubah, maka tidak ada alasan pengujian harus putus, karena tes harus benar-benar menguji perilaku / keadaan, bukan implementasi. Inilah arti jtr tentang hal itu membuat tes Anda rapuh. Di dunia yang ideal, Anda harus dapat melakukan refactor kode Anda dan tes Anda masih lulus, memverifikasi bahwa refactoring Anda belum mengubah fungsionalitas sistem Anda.
Alconja
26
Permintaan maaf untuk kenaifan (belum banyak pengalaman dengan pengujian), tetapi bukankah ide pengujian unit untuk menguji setiap modul kode sendiri? Saya tidak begitu mengerti mengapa metode pribadi harus dikecualikan dari ide ini.
OMill
118

Mungkin tidak berguna untuk menguji metode pribadi. Namun, saya kadang-kadang juga suka memanggil metode pribadi dari metode pengujian. Sebagian besar waktu untuk mencegah duplikasi kode untuk pembuatan data uji ...

Microsoft menyediakan dua mekanisme untuk ini:

Pengakses

  • Mendapat kode sumber definisi kelas
  • Klik kanan pada nama kelas
  • Pilih "Buat Accessor Pribadi"
  • Pilih proyek di mana accessor harus dibuat => Anda akan berakhir dengan kelas baru dengan nama foo_accessor. Kelas ini akan dihasilkan secara dinamis selama kompilasi dan memberikan nilai bagi semua anggota yang tersedia untuk umum.

Namun, mekanisme ini kadang-kadang agak sulit dilakukan ketika datang ke perubahan antarmuka kelas asli. Jadi, sebagian besar kali saya menghindari menggunakan ini.

Kelas PrivateObject Cara lain adalah menggunakan Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );
Tujuh
sumber
2
Bagaimana Anda memanggil metode statis pribadi?
StuperUser
18
Aksesor pribadi tidak digunakan lagi dalam Visual Studio 2012 .
Ryan Gates
3
Metode pengakses dari pengujian Metode Pribadi sedang tidak digunakan sejak VS 2011 dan seterusnya. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra
1
Membaca dokumen yang ditemukan di situs web Microsoft di sini saya tidak melihat penyebutan kelas PrivateObject. Saya menggunakan MSVS 2013 dan berfungsi seperti yang diharapkan.
stackunderflow
3
@RyanGates Solusi pertama di situs yang Anda referensikan menyatakan bahwa solusi untuk mengakses anggota pribadi adalah dengan "Gunakan kelas PrivateObject untuk membantu mengakses API internal dan pribadi dalam kode Anda. Ini ditemukan di Microsoft.VisualStudio.QualityTools.UnitTestFramework. perakitan dll. "
stackunderflow
78

Saya tidak setuju dengan filosofi "Anda hanya tertarik untuk menguji antarmuka eksternal". Ini seperti mengatakan bahwa bengkel mobil seharusnya hanya melakukan tes untuk melihat apakah roda berputar. Ya, pada akhirnya saya tertarik pada perilaku eksternal tetapi saya suka tes internal saya sendiri, pribadi untuk menjadi sedikit lebih spesifik dan to the point. Ya, jika saya refactor, saya mungkin harus mengubah beberapa tes, tetapi kecuali itu adalah refactor besar, saya hanya perlu mengubah beberapa dan fakta bahwa tes internal lainnya (tidak berubah) masih berfungsi adalah indikator yang bagus bahwa refactoring telah berhasil.

Anda dapat mencoba untuk menutupi semua kasus internal hanya menggunakan antarmuka publik dan secara teoritis dimungkinkan untuk menguji setiap metode internal (atau setidaknya setiap yang penting) sepenuhnya dengan menggunakan antarmuka publik tetapi Anda mungkin harus berdiri di atas kepala untuk mencapai ini dan koneksi antara kasus uji yang dijalankan melalui antarmuka publik dan bagian internal dari solusi yang dirancang untuk diuji mungkin sulit atau tidak mungkin untuk dilihat. Setelah ditunjukkan, pengujian individual yang menjamin bahwa mesin internal berfungsi dengan baik layak dilakukan terhadap perubahan tes kecil yang terjadi dengan refactoring - setidaknya itulah pengalaman saya. Jika Anda harus membuat perubahan besar pada pengujian Anda untuk setiap refactoring, maka mungkin ini tidak masuk akal, tetapi dalam hal ini, mungkin Anda harus memikirkan kembali desain Anda sepenuhnya.

Darrell Plank
sumber
20
Saya khawatir saya masih tidak setuju dengan Anda. Memperlakukan setiap komponen sebagai kotak hitam memungkinkan modul untuk ditukar masuk / keluar tanpa masalah. Jika Anda memiliki FooServiceyang harus dilakukan X, yang harus Anda pedulikan adalah bahwa hal itu memang dilakukan Xketika diminta. Bagaimana itu seharusnya tidak masalah. Jika ada masalah di kelas yang tidak dapat dilihat melalui antarmuka (tidak mungkin), itu masih valid FooService. Jika itu masalah yang sangat terlihat melalui antarmuka, tes pada anggota masyarakat harus mendeteksi itu. Intinya adalah bahwa selama roda berputar dengan benar, dapat digunakan sebagai roda.
Dasar
5
Salah satu pendekatan umum adalah bahwa jika logika internal Anda cukup rumit sehingga Anda merasa memerlukan pengujian unit, mungkin perlu diekstraksi ke dalam beberapa jenis kelas pembantu dengan antarmuka publik yang dapat diuji unit. Maka kelas 'orang tua' Anda dapat memanfaatkan bantuan ini, dan semua orang dapat diuji dengan tepat.
Mir
9
@ Dasar: Logika yang salah dalam jawaban ini. Kasing klasik ketika Anda membutuhkan metode pribadi adalah ketika Anda memerlukan beberapa kode untuk digunakan kembali dengan metode publik. Anda menaruh kode ini ke beberapa PrivMethod. Metode ini tidak boleh diekspos ke publik, tetapi perlu pengujian untuk memastikan bahwa metode publik, yang bergantung pada PrivMethod, dapat benar-benar bergantung padanya.
Dima
6
@ Dima Tentunya kalau ada masalah dengan PrivMethod, tes di PubMethodmana panggilan PrivMethodharus memaparkannya? Apa yang terjadi ketika Anda mengubah SimpleSmtpServiceke GmailService? Tiba-tiba tes pribadi Anda menunjukkan kode yang tidak lagi ada atau mungkin bekerja secara berbeda dan akan gagal, meskipun aplikasi dapat bekerja dengan sempurna seperti yang dirancang. Jika ada pemrosesan kompleks yang akan berlaku untuk kedua pengirim email, mungkin harus dalam EmailProcessoryang dapat digunakan oleh keduanya dan diuji secara terpisah?
Dasar
2
@ Miltonb Kita mungkin melihat ini dari berbagai gaya pengembangan. WRT internal, saya tidak cenderung menguji unit mereka. Jika ada masalah (seperti yang diidentifikasi oleh tes antarmuka), mudah untuk dilacak dengan melampirkan debugger atau kelasnya terlalu kompleks dan harus dipisah-pisah (dengan antarmuka publik dari unit kelas yang baru diuji) IMHO
Dasar
51

Dalam kasus yang jarang saya ingin menguji fungsi pribadi, saya biasanya memodifikasinya untuk dilindungi, dan saya telah menulis subkelas dengan fungsi pembungkus publik.

Kelas:

...

protected void APrivateFunction()
{
    ...
}

...

Subclass untuk pengujian:

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...
Jason Jackson
sumber
1
Anda bahkan dapat menempatkan kelas anak itu di file tes unit Anda alih-alih mengacaukan kelas nyata. +1 untuk kelicikan.
Tim Abell
Saya selalu meletakkan semua kode terkait tes dalam proyek unit test jika memungkinkan. Ini hanya kode psuedo.
Jason Jackson
2
Fungsi ini bukan pribadi, terlindungi, hasil bersih ... Anda membuat kode Anda kurang aman / terkena fungsionalitas pribadi tipe anak
War
22

Saya pikir pertanyaan yang lebih mendasar yang harus ditanyakan adalah mengapa Anda mencoba untuk menguji metode pribadi di tempat pertama. Itu adalah bau kode yang Anda coba untuk menguji metode pribadi melalui antarmuka publik kelas itu sedangkan metode itu pribadi karena suatu alasan karena ini merupakan detail implementasi. Orang seharusnya hanya peduli dengan perilaku antarmuka publik bukan pada bagaimana itu diterapkan di bawah selimut.

Jika saya ingin menguji perilaku metode pribadi, dengan menggunakan refactoring umum, saya dapat mengekstrak kodenya ke kelas lain (mungkin dengan visibilitas tingkat paket jadi pastikan itu bukan bagian dari API publik). Saya kemudian dapat menguji perilakunya dalam isolasi.

Produk dari refactoring berarti bahwa metode pribadi sekarang menjadi kelas terpisah yang telah menjadi kolaborator dengan kelas asli. Perilakunya akan menjadi dipahami dengan baik melalui unit test sendiri.

Saya kemudian dapat mengejek perilakunya ketika saya mencoba menguji kelas asli sehingga saya kemudian dapat berkonsentrasi pada pengujian perilaku antarmuka publik kelas itu daripada harus menguji ledakan kombinatorial dari antarmuka publik dan perilaku semua metode privatnya. .

Saya melihat ini analog dengan mengendarai mobil. Ketika saya mengendarai mobil, saya tidak mengemudi dengan kap mesin sehingga saya dapat melihat bahwa mesinnya bekerja. Saya mengandalkan antarmuka yang disediakan mobil, yaitu penghitung putaran dan speedometer untuk mengetahui mesin bekerja. Saya mengandalkan kenyataan bahwa mobil itu benar-benar bergerak ketika saya menekan pedal gas. Jika saya ingin menguji mesin saya dapat melakukan pemeriksaan itu secara terpisah. : D

Tentu saja menguji metode pribadi secara langsung mungkin merupakan pilihan terakhir jika Anda memiliki aplikasi lawas tetapi saya lebih suka kode warisan itu direactored untuk memungkinkan pengujian yang lebih baik. Michael Feathers telah menulis buku yang bagus tentang hal ini. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052

Kahuna besar
sumber
16
Logika yang benar-benar salah dalam jawaban ini. Kasing klasik ketika Anda membutuhkan metode pribadi adalah ketika Anda memerlukan beberapa kode untuk digunakan kembali dengan metode publik. Anda menaruh kode ini ke beberapa PrivMethod. Metode ini tidak boleh diekspos ke publik, tetapi perlu pengujian untuk memastikan bahwa metode publik, yang bergantung pada PrivMethod, dapat benar-benar bergantung padanya.
Dima
Masuk akal selama pengembangan awal, tetapi apakah Anda ingin tes untuk metode pribadi dalam setelan regresi standar Anda? Jika demikian, jika implementasi berubah, ini dapat merusak test suite. OTOH, jika tes regresi Anda hanya berfokus pada metode publik yang terlihat secara eksternal, maka jika metode pribadi itu rusak kemudian, paket regresi masih harus mendeteksi kesalahan. Maka jika perlu, Anda dapat membersihkan tes pribadi lama jika diperlukan.
Alex Blakemore
9
Tidak setuju, Anda harus menguji antarmuka publik saja, kalau tidak mengapa perlu metode pribadi. Jadikan semuanya publik dalam kasus itu dan uji semuanya. Jika Anda menguji metode pribadi, Anda melanggar enscapulation. Jika Anda ingin menguji metode pribadi dan itu digunakan dalam beberapa metode publik maka itu harus dipindahkan ke kelas sendiri dan diuji secara terpisah. Semua metode publik kemudian harus mendelegasikan ke kelas baru itu. Dengan cara itu Anda masih memiliki tes untuk antarmuka kelas asli dan Anda dapat memverifikasi perilaku tidak berubah dan Anda memiliki tes terpisah untuk metode pribadi yang didelegasikan.
Big Kahuna
@Big Kahuna - Jika Anda pikir tidak ada kasus di mana Anda perlu menguji metode pribadi Anda tidak pernah bekerja dengan proyek yang cukup besar / kompleks. Banyak kali fungsi publik seperti validasi khusus klien berakhir dengan 20 baris hanya memanggil metode pribadi yang sangat sederhana untuk membuat kode lebih mudah dibaca tetapi Anda masih harus menguji setiap metode pribadi masing-masing. Menguji 20 kali fungsi publik akan membuatnya sangat sulit untuk debut ketika tes unit gagal.
Pedro. Anak
1
Saya bekerja untuk perusahaan FTSE 100. Saya pikir saya telah melihat beberapa proyek kompleks di waktu saya, terima kasih. Jika Anda perlu menguji ke tingkat itu, maka masing-masing metode pribadi sebagai kolaborator terpisah, harus diuji secara terpisah karena menyiratkan mereka memiliki perilaku individu yang memerlukan pengujian. Tes untuk objek mediator utama kemudian hanya menjadi tes interaksi. Itu hanya menguji strategi yang tepat dipanggil. Skenario Anda terdengar seperti kelas yang dimaksud tidak mengikuti SRP. Itu tidak memiliki satu alasan untuk berubah tetapi 20 => pelanggaran SRP. Baca buku GOOS atau Paman Bob. YMWV
Big Kahuna
17

Jenis pribadi, internal, dan anggota pribadi begitu karena beberapa alasan, dan seringkali Anda tidak ingin mengacaukannya secara langsung. Dan jika Anda melakukannya, kemungkinan besar Anda akan melanggar kemudian, karena tidak ada jaminan bahwa orang-orang yang menciptakan majelis tersebut akan menjaga implementasi pribadi / internal seperti itu.

Tetapi, kadang-kadang, ketika melakukan beberapa peretasan / eksplorasi majelis yang dikompilasi atau pihak ketiga, saya sendiri akhirnya ingin menginisialisasi kelas privat atau kelas dengan konstruktor pribadi atau internal. Atau, kadang-kadang, ketika berhadapan dengan perpustakaan lawas yang sudah dikompilasi sebelumnya yang tidak dapat saya ubah - saya akhirnya menulis beberapa tes terhadap metode pribadi.

Maka lahirlah AccessPrivateWrapper - http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html - ini adalah kelas pembungkus cepat yang akan mempermudah pekerjaan menggunakan fitur dinamis C # 4.0 dan refleksi.

Anda dapat membuat tipe internal / pribadi seperti

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();
amazedsaint
sumber
12

Anda dapat menguji metode pribadi dengan dua cara

  1. Anda dapat membuat instance dari PrivateObjectkelas sintaksnya adalah sebagai berikut

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. Anda bisa menggunakan refleksi.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });
Tidak dikenal
sumber
Jawaban yang bagus, tetapi untuk # 1 sintaks Anda salah. Anda harus mendeklarasikan instance PrivateClasspertama dan menggunakannya. stackoverflow.com/questions/9122708/…
SharpC
10

Saya juga menggunakan metode InternalsVisibleToAttribute. Perlu disebutkan juga bahwa, jika Anda merasa tidak nyaman membuat metode yang sebelumnya pribadi Anda internal untuk mencapai ini, maka mungkin mereka seharusnya tidak menjadi subjek dari tes unit langsung.

Setelah semua, Anda menguji perilaku kelas Anda, daripada implementasi spesifik - Anda dapat mengubah yang terakhir tanpa mengubah yang pertama dan tes Anda masih harus lulus.

philsquared
sumber
Saya suka poin tentang pengujian perilaku daripada implementasi. Jika Anda mengikat tes unit Anda ke implementasi (metode pribadi), maka tes akan menjadi rapuh dan perlu berubah ketika implementasi berubah.
Bob Horn
9

Ada 2 jenis metode pribadi. Metode Privat Statis dan Metode Privat Non Statis (Metode Instance). 2 artikel berikut menjelaskan cara unit menguji metode pribadi dengan contoh.

  1. Unit Pengujian Metode Privat Statis
  2. Unit Pengujian Metode Privat Non Statis
Venkat
sumber
Berikan beberapa contoh, tidak hanya memberikan tautan
vinculis
Terlihat jelek. Tidak masuk akal. Solusi buruk dari MS. Saya kaget!
alerya
Cara termudah untuk menguji metode statis pribadi
Berikan
8

MS Test memiliki fitur bagus bawaan yang membuat anggota pribadi dan metode yang tersedia dalam proyek dengan membuat file yang disebut VSCodeGenAccessors

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

Dengan kelas yang berasal dari BaseAccessor

seperti

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }
Marcus King
sumber
8
File gen'd hanya ada di VS2005. DI 2008 mereka dibuat di belakang layar. Dan mereka adalah kekejian. Dan tugas Shadow yang terkait terkelupas pada server build.
Ruben Bartelink
Aksesor juga tidak digunakan lagi dalam VS2012-2013.
Zephan Schroeder
5

Pada CodeProject, ada sebuah artikel yang secara singkat membahas pro dan kontra dari pengujian metode pribadi. Kemudian memberikan beberapa kode refleksi untuk mengakses metode pribadi (mirip dengan kode yang diberikan Marcus di atas.) Satu-satunya masalah yang saya temukan dengan sampel adalah bahwa kode tidak memperhitungkan metode kelebihan beban akun.

Anda dapat menemukan artikelnya di sini:

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

Pedro
sumber
4

Nyatakan internal, lalu gunakan InternalsVisibleToAttributeuntuk memungkinkan unit uji unit Anda melihatnya.

James Curran
sumber
11
Saya tidak suka menggunakan InternalsVisibleTo karena saya membuat metode pribadi karena suatu alasan.
swilliams
4

Saya cenderung tidak menggunakan arahan kompiler karena mereka mengacaukan semuanya dengan cepat. Salah satu cara untuk memitigasinya jika Anda benar-benar membutuhkannya adalah dengan meletakkannya di kelas parsial dan membuat build Anda mengabaikan file .cs tersebut saat membuat versi produksi.

swilliams
sumber
Anda akan memasukkan penguji uji dalam versi produksi (untuk menguji optimasi kompiler dll) tetapi mengecualikan mereka dalam versi rilis. Tapi saya memisahkan rambut & saya tetap memilih ini karena saya pikir itu ide yang baik untuk meletakkan barang-barang itu di satu tempat. Terima kasih untuk idenya.
CAD berbicara
4

Anda seharusnya tidak menguji metode pribadi kode Anda di tempat pertama. Anda harus menguji 'antarmuka publik' atau API, hal-hal umum dari kelas Anda. API adalah semua metode publik yang Anda paparkan ke penelepon luar.

Alasannya adalah bahwa setelah Anda mulai menguji metode pribadi dan internal kelas Anda, Anda menggabungkan implementasi kelas Anda (hal-hal pribadi) ke tes Anda. Ini berarti bahwa ketika Anda memutuskan untuk mengubah detail implementasi Anda, Anda juga harus mengubah tes Anda.

Karena itu Anda harus menghindari penggunaan InternalsVisibleToAtrribute.

Berikut ini adalah pembicaraan hebat oleh Ian Cooper yang membahas hal ini: Ian Cooper: TDD, di mana semuanya salah

cda01
sumber
3

Terkadang, ada baiknya untuk menguji deklarasi pribadi. Pada dasarnya, kompiler hanya memiliki satu metode publik: Kompilasi (string outputFileName, params string [] sourceSFileNames). Saya yakin Anda mengerti bahwa akan sulit untuk menguji metode seperti itu tanpa menguji setiap deklarasi "tersembunyi"!

Itu sebabnya kami membuat Visual T #: untuk membuat tes lebih mudah. Ini adalah bahasa pemrograman .NET gratis (kompatibel dengan C # v2.0).

Kami telah menambahkan operator '.-'. Itu hanya berperilaku seperti '.' operator, kecuali Anda juga dapat mengakses deklarasi tersembunyi dari pengujian Anda tanpa mengubah apa pun dalam proyek yang diuji.

Lihatlah situs web kami: unduh secara gratis .

Ludovic Dubois
sumber
3

Saya terkejut belum ada yang mengatakan ini, tetapi solusi yang saya gunakan adalah membuat metode statis di dalam kelas untuk menguji sendiri. Ini memberi Anda akses ke semua publik dan pribadi untuk diuji.

Selanjutnya, dalam bahasa scripting (dengan kemampuan OO, seperti Python, Ruby dan PHP), Anda dapat membuat tes file itu sendiri saat dijalankan. Cara cepat yang bagus untuk memastikan perubahan Anda tidak merusak apa pun. Ini jelas membuat solusi scalable untuk menguji semua kelas Anda: jalankan saja semuanya. (Anda juga dapat melakukan ini dalam bahasa lain dengan void main yang selalu menjalankan tesnya juga).

orang udik
sumber
1
Meskipun ini praktis, namun tidak terlalu elegan. Ini dapat membuat sedikit kekacauan basis kode dan juga tidak memungkinkan Anda untuk memisahkan tes Anda dari kode asli Anda. Kemampuan untuk menguji secara eksternal membuka kemampuan untuk skrip mengotomatisasi pengujian alih-alih menulis metode statis dengan tangan.
Darren Reid
Ini tidak menghalangi Anda untuk menguji secara eksternal ... cukup panggil metode statis sesuka Anda. Basis kode juga tidak berantakan ... Anda menyebutkan metode yang sesuai. Saya menggunakan "runTests", tetapi hal-hal serupa bekerja.
rube
Hak Anda, itu tidak menghalangi pengujian eksternal, namun itu menghasilkan lebih banyak kode, yaitu membuat basis kode berantakan. Setiap kelas mungkin memiliki banyak metode pribadi untuk menguji yang menginisialisasi variabel dalam satu atau lebih konstruktornya. Untuk menguji, Anda harus menulis setidaknya metode statis sebanyak metode untuk menguji, dan metode pengujian mungkin harus besar untuk menginisialisasi nilai yang benar. Ini akan membuat pemeliharaan kode lebih sulit. Seperti yang orang lain katakan, menguji perilaku kelas adalah pendekatan yang lebih baik, sisanya harus cukup kecil untuk di-debug.
Darren Reid
Saya menggunakan jumlah baris yang sama untuk menguji seperti orang lain (sebenarnya kurang seperti yang akan Anda baca nanti). Anda tidak perlu menguji SEMUA metode pribadi Anda. Hanya yang membutuhkan pengujian :) Anda juga tidak perlu menguji masing-masing dengan metode terpisah. Saya melakukannya dengan satu panggilan. Ini benar-benar membuat pemeliharaan kode KURANG sulit, karena semua kelas saya memiliki metode pengujian unit payung yang sama, yang menjalankan semua tes unit pribadi dan dilindungi baris demi baris. Seluruh test harness kemudian memanggil metode yang sama pada semua kelas saya dan pemeliharaan semua berada di dalam kelas saya - tes dan semua.
rube
3

Saya ingin membuat contoh kode yang jelas di sini yang dapat Anda gunakan pada kelas mana pun di mana Anda ingin menguji metode pribadi.

Di kelas ujian Anda, sertakan saja metode ini dan kemudian gunakan sesuai indikasi.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this -> _ callMethod ('_ someFunctionName', array (param1, param2, param3));

Hanya mengeluarkan parameter dalam urutan yang muncul di fungsi pribadi asli

Damon Hogan
sumber
3

Bagi siapa saja yang ingin menjalankan metode pribadi tanpa semua fess dan kekacauan. Ini berfungsi dengan kerangka pengujian unit apa pun yang menggunakan Refleksi lama yang bagus.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

Kemudian dalam tes Anda yang sebenarnya, Anda dapat melakukan sesuatu seperti ini:

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");
Erick Stone
sumber
2

MbUnit punya pembungkus yang bagus untuk ini yang disebut Reflector.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

Anda juga dapat mengatur dan mendapatkan nilai dari properti

dogReflector.GetProperty("Age");

Mengenai "tes pribadi" saya setuju itu .. di dunia yang sempurna. tidak ada gunanya melakukan tes unit pribadi. Tetapi di dunia nyata Anda mungkin ingin menulis tes pribadi alih-alih kode refactoring.

Carl Bergquist
sumber
4
Sekadar info, Reflectortelah digantikan oleh yang lebih kuat Mirrordi Gallio / MbUnit v3.2. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
Yann Trevin
2

Berikut adalah artikel yang bagus tentang unit testing metode pribadi. Tapi saya tidak yakin apa yang lebih baik, untuk membuat Anda aplikasi yang dirancang khusus untuk pengujian (seperti membuat tes untuk pengujian saja) atau menggunakan refleksi untuk pengujian. Cukup yakin sebagian besar dari kita akan memilih cara kedua.

Johnny_D
sumber
2

Menurut pendapat saya, Anda hanya harus menguji unit API publik kelas Anda.

Membuat metode publik, untuk menguji unit, istirahat enkapsulasi mengekspos detail implementasi.

API publik yang baik memecahkan tujuan langsung dari kode klien dan menyelesaikan tujuan itu sepenuhnya.

jpchauny
sumber
Ini harus menjadi jawaban IMO yang tepat. Jika Anda memiliki banyak metode pribadi maka ini mungkin karena Anda memiliki kelas tersembunyi yang harus Anda hancurkan ke antarmuka publiknya sendiri.
sunefred
2

Saya menggunakan kelas PrivateObject . Tetapi seperti yang disebutkan sebelumnya lebih baik untuk menghindari pengujian metode pribadi.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
vsapiha
sumber
2
CC -Dprivate=public

"CC" adalah kompiler baris perintah pada sistem yang saya gunakan. -Dfoo=bartidak sama dengan #define foo bar. Jadi, opsi kompilasi ini secara efektif mengubah semua hal pribadi menjadi publik.

Mark Harrison
sumber
2
Apa ini? Apakah ini berlaku untuk Visual Studio?
YeahStu
"CC" adalah kompiler baris perintah pada sistem yang saya gunakan. "-Dfoo = bar" melakukan hal yang setara dengan "#define foo bar". Jadi, opsi kompilasi ini secara efektif mengubah semua hal pribadi menjadi publik. ha ha!
Mark Harrison
Di Visual Studio, setel define di lingkungan build Anda.
Mark Harrison
1

Inilah sebuah contoh, pertama metode tanda tangan:

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

Inilah tesnya:

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}
Chuck Savage
sumber
1

Cara untuk melakukan ini adalah dengan memiliki metode Anda protecteddan menulis perlengkapan ujian yang mewarisi kelas Anda untuk diuji. Dengan cara ini, Anda tidak membalik metode Anda public, tetapi Anda mengaktifkan pengujian.

Kiriloff
sumber
Saya tidak setuju dengan ini, karena Anda juga akan memungkinkan konsumen Anda mewarisi dari kelas dasar dan menggunakan fungsi yang dilindungi. Ini adalah sesuatu yang ingin Anda cegah sejak awal dengan menjadikan fungsi-fungsi itu pribadi atau internal.
Nick N.
1

1) Jika Anda memiliki kode lawas maka satu-satunya cara untuk menguji metode pribadi adalah dengan refleksi.

2) Jika kode baru maka Anda memiliki opsi berikut:

  • Gunakan refleksi (untuk memperumit)
  • Tulis unit test di kelas yang sama (buat kode produksi jelek dengan ada kode tes juga di dalamnya)
  • Refactor dan buat metode ini publik di beberapa jenis kelas utilitas
  • Gunakan anotasi @VisibleForTesting dan hapus pribadi

Saya lebih suka metode penjelasan, paling sederhana dan paling tidak rumit. Satu-satunya masalah adalah bahwa kami telah meningkatkan visibilitas yang saya pikir bukan masalah besar. Kita harus selalu mengkodekan ke antarmuka, jadi jika kita memiliki antarmuka MyService dan implementasi MyServiceImpl maka kita dapat memiliki kelas uji yang sesuai yaitu MyServiceTest (metode antarmuka uji) dan MyServiceImplTest (uji metode pribadi). Semua klien tetap harus menggunakan antarmuka sehingga dengan cara meskipun visibilitas metode pribadi telah ditingkatkan, itu seharusnya tidak terlalu menjadi masalah.

anuj
sumber
1

Anda juga dapat mendeklarasikannya sebagai publik atau internal (dengan InternalsVisibleToAttribute) saat membangun dalam Mode debug:

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

Itu menggembungkan kode, tetapi akan privatedalam rilis rilis

Alex H
sumber
0

Anda bisa membuat metode pengujian untuk metode pribadi dari Visual studio 2008. Ketika Anda membuat tes unit untuk metode pribadi, folder Referensi Referensi ditambahkan ke proyek pengujian Anda dan accessor ditambahkan ke folder itu. Accessor juga disebut dalam logika metode unit test. Accessor ini memungkinkan pengujian unit Anda untuk memanggil metode pribadi dalam kode yang Anda uji. Untuk detailnya lihat

http://msdn.microsoft.com/en-us/library/bb385974.aspx

Sarath
sumber
0

Perhatikan juga bahwa InternalsVisibleToAtrribute memiliki persyaratan agar nama perakitan Anda kuat , yang menciptakan serangkaian masalah sendiri jika Anda bekerja dalam solusi yang belum memiliki persyaratan itu sebelumnya. Saya menggunakan accessor untuk menguji metode pribadi. Lihat pertanyaan ini sebagai contohnya.

Chuck Dee
sumber
2
Tidak, InternalsVisibleToAttributeitu tidak mengharuskan majelis Anda diberi nama kuat. Saat ini saya menggunakannya pada proyek di mana itu tidak terjadi.
Cody Grey
1
Untuk memperjelas ini: "Baik majelis saat ini dan teman perakitan harus ditandatangani, atau keduanya harus ditandatangani dengan nama yang kuat." - Dari MSDN
Hari