C # 'de özel yöntemlerin test edilmesi


292

Visual Studio, otomatik olarak oluşturulan bir erişimci sınıfı aracılığıyla özel yöntemlerin birim sınanmasına olanak tanır. Başarıyla derleyen özel bir yöntem sınaması yazdım, ancak çalışma zamanında başarısız oluyor. Kodun ve testin oldukça minimal bir sürümü:

//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
}

Çalışma zamanı hatası:

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]'.

Intellisense göre - ve dolayısıyla derleyici - hedef TypeA_Accessor tipinde. Ancak çalışma zamanında TypeA türündedir ve bu nedenle liste ekleme işlemi başarısız olur.

Bu hatayı durdurabilmemin bir yolu var mı? Ya da, belki de daha büyük olasılıkla, diğer insanların önerileri nelerdir (belki "özel yöntemleri test etmiyorum" ve "birim testlerinin nesnelerin durumunu değiştirmesini beklemiyorum").


Özel sınıf TypeB için bir erişimciye ihtiyacınız var. AccessA TypeA_Accessor, TypeA'nın özel ve korumalı yöntemlerine erişim sağlar. Ancak TypeB bir yöntem değildir. Bu bir sınıf.
Dima

Accessor özel / korumalı yöntemlere, üyelere, özelliklere ve olaylara erişim sağlar. Sınıfınızdaki özel / korumalı sınıflara erişim sağlamaz. Özel / korumalı sınıfların (TypeB) sadece sınıfa sahip olma yöntemleri (TypeA) tarafından kullanılması amaçlanmıştır. Yani temelde TypeA dışından özel olan "myList" e özel sınıf (TypeB) eklemeye çalışıyorsunuz. Erişimci kullandığınızdan, myList'e erişmekte sorun yok. Ancak TypeB'yi erişimci aracılığıyla kullanamazsınız. Olası çözüm, TypeB'yi TypeA'nın dışına taşımak olacaktır. Ama tasarımınızı kırabilir.
Dima

Özel yöntemlerin test edilmesinin aşağıdaki stackoverflow.com/questions/250692/…
nate_weldon

Yanıtlar:


275

Evet, özel yöntemleri test etme .... Birim testi fikri, birimi herkese açık 'API' ile test etmektir.

Çok sayıda özel davranışı test etmeniz gerektiğini düşünüyorsanız, büyük olasılıkla test etmeye çalıştığınız sınıf içinde saklanan yeni bir 'sınıf' var, onu ayıklayın ve genel arayüzü ile test edin.

Bir tavsiye / Düşünme aracı ..... Hiçbir yöntemin asla özel olmaması gerektiği fikri vardır. Yani tüm yöntemler bir nesnenin genel arayüzünde yaşamalıdır .... eğer onu özel yapmanız gerektiğini düşünüyorsanız, büyük olasılıkla başka bir nesne üzerinde yaşar.

Bu tavsiye, pratikte pek işe yaramaz, ancak çoğunlukla iyi bir tavsiyedir ve genellikle insanları nesnelerini daha küçük nesnelere parçalamaya itecektir.


386
Katılmıyorum. OOD'de özel yöntemler ve özellikler, kendinizi tekrarlamamanın gerçek bir yoludur ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). Kara kutu programlama ve kapsüllemenin arkasındaki fikir, teknik detayları aboneden gizlemektir. Bu nedenle, kodunuzda önemsiz olmayan özel yöntemlere ve özelliklere sahip olmak gerçekten gereklidir. Ve eğer önemsiz değilse, test edilmesi gerekiyor.
AxD

8
içsel değil, bazı OO dillerinin özel yöntemleri yoktur, özel özellikler test edilebilen genel arayüzlere sahip nesneler içerebilir.
Keith Nicholas

7
Bu tavsiyenin amacı, nesneniz bir şey yaparsanız ve KURUYORSANIZ, özel yöntemlere sahip olmanız için genellikle çok az neden vardır. Genellikle özel yöntemler, nesnenin gerçekten sorumlu olmadığı bir şey yapar, ancak önemsiz değilse, oldukça faydalıdır, genellikle SRP'yi ihlal eden başka bir nesnedir
Keith Nicholas

28
Yanlış. Kod çoğaltmasını önlemek için özel yöntemler kullanmak isteyebilirsiniz. Veya doğrulama için. Veya Kamu dünyasının bilmemesi gereken birçok amaç için.
Jorj

37
Korkunç bir şekilde tasarlanmış ve "aşamalı olarak geliştirmek" istendiğinde bir OO kod tabanına atıldığınızda, özel yöntemler için bazı ilk testler yapamayacağımı bulmak bir hayal kırıklığı oldu. Evet, belki de ders kitaplarında bu yöntemler burada olmazdı, ancak gerçek dünyada ürün gereksinimleri olan kullanıcılarımız var. Sadece büyük bir "temiz kod bakmak" yeniden inşa projede bazı testler almadan yapamam. Uygulama yapan programcıları safça iyi görünen ama gerçek dağınık bokları hesaba katmayan caddelere zorlamanın başka bir örneği gibi geliyor.
dune.rocks

666

PrivateObject Sınıfını kullanabilirsiniz

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);

25
Bu doğru yanıt, şimdi Microsoft PrivateObject ekledi.
Zoey

4
İyi yanıt ama lütfen PrivateMethod'un "private" yerine "korunması" gerektiğini unutmayın.
HerbalMart

23
@HerbalMart: Belki de sizi yanlış anlıyorum, ancak PrivateObject'in özel üyelere değil yalnızca korunan üyelere erişebileceğini öneriyorsanız yanılıyorsunuz.
kmote

17
@JeffPearce Statik yöntemler için "PrivateType pt = new PrivateType (typeof (MyClass));" kullanın ve sonra pt nesnesinde InvokeStatic'i özel bir nesnede Invoke olarak adlandırdığınız gibi çağırabilirsiniz.
Steve Hibbert

12
Herkes MSTest.TestFramework v1.2.1 olarak merak ediyorsa - PrivateObject ve PrivateType sınıfları .NET Core 2.0'ı hedefleyen projeler için kullanılamaz - Bunun için bir github sorunu var: github.com/Microsoft/testfx/issues/366
shiitake

98

“Standart ya da en iyi uygulama diye bir şey yoktur, muhtemelen sadece popüler görüşlerdir”.

Aynı şey bu tartışma için de geçerlidir.

resim açıklamasını buraya girin

Her şey bir birim olduğunu düşündüğünüze bağlıdır, eğer ÜNİTE'nin bir sınıf olduğunu düşünüyorsanız, sadece genel yönteme çarparsınız. ÜNİTE'nin özel yöntemlere isabet eden kod satırları olduğunu düşünüyorsanız, kendinizi suçlu hissetmezsiniz.

Özel yöntemleri çağırmak istiyorsanız "PrivateObject" sınıfını kullanabilir ve invoke yöntemini çağırabilirsiniz. "PrivateObject" in nasıl kullanılacağını gösteren ve ayrıca özel yöntemlerin testinin mantıklı olup olmadığını tartışan bu derin youtube videosunu ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) izleyebilirsiniz .


55

Buradaki başka bir düşünce, testi "iç" sınıflara / yöntemlere genişletmek ve bu testin beyaz kutu hissini daha fazla vermektir. Bunları ayrı birim test modüllerine maruz bırakmak için montajdaki InternalsVisibleToAttribute öğesini kullanabilirsiniz.

Mühürlü sınıfla birlikte, test yönteminin sadece unittest montajından yöntemlerinizi görebileceği bir kapsülleme yaklaşımına yaklaşabilirsiniz. Mühürlü sınıfta korunan yöntemin de facto private olduğunu düşünün.

[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()
       {            
       }
   }
}

Ve birim testi:

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
}
}

Anladığımdan emin değilim. InternalsVisibleToAttribute "dahili" olarak işaretlenmiş yöntem ve nitelikleri erişilebilir hale getirir, ancak alanlarım ve yöntemlerim "özel" dir. Bazı şeyleri özel olmaktan içselliğe değiştirmemi mi öneriyorsunuz? Sanırım yanlış anladım.
junichiro

2
Evet, bunu öneriyorum. Biraz "kibirli" ama en azından "herkese açık" değiller.
Jeff

27
Bu sadece "özel yöntemleri test etme" demediği için harika bir cevap ama evet, oldukça "hacky". Keşke bir çözüm olsaydı. IMO "özel yöntemler test edilmemeli" demek kötü, çünkü benim gördüğüm şekilde: "özel yöntemler doğru olmamalı" ya eşdeğer.
MasterMastic

5
Ya ken, özel yöntemlerin birim testte test edilmemesi gerektiğini iddia edenler tarafından da kafam karıştı. Genel API çıktıdır, ancak bazen yanlış uygulama da doğru çıktıyı verir. Veya uygulama bazı kötü yan etkiler yaptı, örneğin gerekli olmayan kaynakların tutulması, gc tarafından toplanmasını engelleyen nesnelere gönderme ... vb. Birim testi yerine özel yöntemleri kapsayabilecek başka bir test sağlamadıkça,% 100 test edilmiş bir kodu koruyamayacaklarını düşünürüm.
mr.Pony

MasterMastic ile aynı fikirdeyim. Bu kabul edilen cevap olmalı.
XDS

27

Özel yöntemleri test etmenin bir yolu, düşünmektir. Bu, NUnit ve XUnit için de geçerlidir:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

call methods statik ve statik olmayan ?
Kiquenet

2
Yansıma bağımlı yöntemlerin dezavantajı, R # kullanarak yöntemleri yeniden adlandırdığınızda kopma eğilimindedir. Küçük projelerde büyük bir sorun olmayabilir, ancak büyük kod tabanlarında, birim testlerinin böyle bir şekilde kırılması ve sonra etrafta dolaşıp hızlı bir şekilde düzeltilmesi gerekir. Bu anlamda param Jeff'in cevabına giderim.
XDS

2
@XDS Çok kötü nameof () sınıfı dışında özel bir yöntemin adını almak için çalışmaz.
Gabriel Morin

12

Eee ... Tam olarak aynı problemle buraya geldik: Basit ama önemli bir özel yöntemi test et . Bu ipliği okuduktan sonra, "Bu basit metal parçasında bu basit deliği açmak istiyorum ve kalitenin spesifikasyonları karşıladığından emin olmak istiyorum" gibi görünüyor ve sonra "Tamam, bu kolay değil. her şeyden önce, bunu yapmak için hiçbir uygun aracı vardır, ama sen. bahçenizde bir yerçekimsel dalga gözlemevi inşa makalemi okuyun olabilir http://foobar.brigther-than-einstein.org/ Birincisi, elbette, bazı gelişmiş kuantum fiziği derslerine katılmak zorundasınız, o zaman tonlarca ultra-serin nitrogenium'a ihtiyacınız var ve elbette kitabım Amazon'da mevcut "...

Diğer bir deyişle...

Hayır, ilk önce.

Özel, dahili, korunan, kamuya açık olan her yöntem test edilebilir olmalıdır. Bu tür testleri burada sunulan ado olmadan uygulamak için bir yol olmalı.

Neden? Tam olarak nedeni mimari bazı katkıda bulunanlar tarafından bugüne kadar yapılan söz eder. Belki de yazılım ilkelerinin basit bir şekilde tekrarlanması bazı yanlış anlaşılmaları ortadan kaldırabilir.

Bu durumda, olağan şüpheliler şunlardır: OCP, SRP ve her zamanki gibi KIS.

Ama bir dakika. Her şeyi herkese açık hale getirme fikri daha az politik ve bir tür tutumdur. Fakat. Kod söz konusu olduğunda, Açık Kaynak Topluluğu'nda bile, bu bir dogma değildir. Bunun yerine, bir şeyi "gizlemek", belirli bir API'yi tanımayı kolaylaştırmak için iyi bir uygulamadır. Örneğin, piyasaya yeni giren dijital termometre yapı taşınızın temel hesaplamalarını gizlersiniz - gerçek ölçülen eğrinin arkasındaki matematiği meraklı kod okuyucularına gizlemek için değil, kodunuzun bazılarına bağımlı olmasını önlemek için, kendi fikirlerini uygulamak için eski özel, dahili, korunan kodunuzu kullanmaya dayanamayan aniden önemli kullanıcılar olabilir.

Ne hakkında konuşuyorum?

özel çift TranslateMeasurementIntoLinear (çift gerçek Ölçüm);

Kova Çağı'nı veya bugünlerde denilen şeyi açıklamak kolaydır, ancak sensör parçam 1.0'dan 2.0'a çıkarsa, Çevir ... uygulaması kolayca anlaşılabilir ve "yeniden- herkes için kullanılabilir ", analiz ya da her neyse kullanan oldukça sofistike bir hesaplama, ve ben başkalarının kodunu kırmak istiyorum. Neden? Çünkü yazılım kodlamanın çok önemli olduğunu anlamadılar, KIS'i bile.

Bu masalı kısaltmak için: Özel yöntemleri test etmek için basit bir yola ihtiyacımız var - ado olmadan.

İlk: Herkese mutlu yıllar!

İkincisi: Mimar derslerinizi prova edin.

Üçüncüsü: "Genel" değiştirici bir çözüm değil dindir.


5

Bahsedilmeyen bir diğer seçenek ise, test ettiğiniz nesnenin alt öğesi olarak birim test sınıfını oluşturmaktır. NUnit Örneği:

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

Bu, özel ve korumalı (kalıtsal olmayan özel) yöntemlerin kolayca test edilmesine olanak tanır ve test derlemelerini üretime dağıtmamanız için tüm testlerinizi gerçek koddan ayrı tutmanıza olanak tanır. Özel yöntemlerinizi korumalı yöntemlere dönüştürmek, devralınan birçok nesnede kabul edilebilir ve bunu yapmak oldukça basit bir değişikliktir.

ANCAK...

Bu, gizli yöntemleri test etme sorununu çözmek için ilginç bir yaklaşım olsa da, bunun her durumda sorunun doğru çözümü olduğunu savunacağımdan emin değilim. Bir nesneyi dahili olarak test etmek biraz tuhaf görünüyor ve bu yaklaşımın sizi uçuracağı bazı senaryolar olabileceğinden şüpheleniyorum. (Örneğin, dokunulmaz nesneler bazı testleri gerçekten zorlaştırabilir).

Bu yaklaşımdan bahsederken, bunun meşru bir çözümden ziyade beyin fırtınası yapılmış bir öneri olduğunu öneririm. Bir tuz tanesi ile alın.

EDIT: Bunu açıkça kötü bir fikir olarak tanımladığım için insanların bu cevabı oylamalarını gerçekten komik buluyorum. Bu, insanların benimle aynı fikirde olduğu anlamına mı geliyor? Kafam çok karıştı.....


Bu yaratıcı bir çözüm ama biraz hileli.
shinzou

3

Eski Kod ile Etkili Çalışma kitabından :

"Özel bir yöntemi test etmemiz gerekiyorsa, bunu herkese açık hale getirmeliyiz. Herkese açık hale getirmek bizi rahatsız ediyorsa, çoğu durumda, sınıfımızın çok fazla şey yaptığı ve bunu düzeltmemiz gerektiği anlamına gelir."

Yazara göre düzeltmenin yolu, yeni bir sınıf oluşturmak ve yöntemi olarak eklemektir public.

Yazar daha fazla açıklıyor:

"İyi tasarım test edilebilir ve test edilemeyen tasarım kötüdür."

Dolayısıyla, bu sınırlar dahilinde, tek gerçek seçeneğiniz yöntemi publicmevcut veya yeni bir sınıfta yapmaktır .


2

TL; DR: Özel yöntemi başka bir sınıfa çıkarın, o sınıfı test edin; SRP ilkesi hakkında daha fazla bilgi edinin (Tek Sorumluluk İlkesi)

Görünüşe göre privatebaşka bir sınıfa yönteme ihtiyacınız var ; bu olmalı public. privateYöntemi test etmeye çalışmak yerine, publicbu başka bir sınıfın yöntemini test etmelisiniz .

Şu senaryoya sahibiz:

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

Aşağıdakilerin mantığını test etmemiz gerekir _someLogic; ancak görünüşe göre Class Agerekenden daha fazla rol alıyor (SRP ilkesini ihlal ediyor); sadece iki sınıfa odaklanın

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

Bu şekilde someLogicA2 üzerinde test yapılabilir; A1'de sadece sahte A2 oluşturun, ardından A2'nin adlandırılmış işleve çağrıldığını test etmek için yapıcıya enjekte edin someLogic.


0

VS 2005/2008'de özel üyeyi test etmek için özel erişimciyi kullanabilirsiniz , ancak bu yol VS'nin sonraki sürümünde kayboldu


1
2008'de belki 2010'un başına kadar iyi bir yanıt. Şimdi lütfen PrivateObject ve Reflection alternatiflerine bakın (yukarıdaki birkaç cevaba bakınız). VS2010'da erişim hatası vardı, MS VS2012'de kullanımdan kaldırdı. VS2010 veya daha eski bir yaşta (> 18 yaşında yapı araçları) kalmak zorunda olmadığınız sürece, lütfen özel erişimcileri önleyerek kendinize zaman kazandırın. :-).
Zephan Schroeder
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.