Aynı sınıf içinde diğer yöntemleri çağıran en iyi test yöntemi yöntemi


35

Geçenlerde bazı arkadaşlarla tartışarak geri dönüş sonuçlarını saptamak için en iyi olan aşağıdaki 2 yöntemden hangisinin aynı sınıf içindeki yöntemlerden aynı sınıf içindeki yöntemlere çağrı yapmaktan bahsediyordum.

Bu çok basitleştirilmiş bir örnek. Gerçekte, işlevler çok daha karmaşıktır.

Örnek:

public class MyClass
{
     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected int FunctionB()
     {
         return new Random().Next();
     }
}

Bunu test etmek için 2 yöntemimiz var.

Yöntem 1: Yöntemlerin işlevselliğini değiştirmek için İşlevler ve İşlemler'i kullanın. Örnek:

public class MyClass
{
     public Func<int> FunctionB { get; set; }

     public MyClass()
     {
         FunctionB = FunctionBImpl;
     }

     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected int FunctionBImpl()
     {
         return new Random().Next();
     }
}

[TestClass]
public class MyClassTests
{
    private MyClass _subject;

    [TestInitialize]
    public void Initialize()
    {
        _subject = new MyClass();
    }

    [TestMethod]
    public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
    {
        _subject.FunctionB = () => 1;

        var result = _subject.FunctionA();

        Assert.IsFalse(result);
    }
}

Yöntem 2: Üyeleri sanal hale getirin, sınıfı türetin ve türetilmiş sınıfta işlevselliği değiştirmek için İşlevler ve Eylemler kullanın Örnek:

public class MyClass
{     
     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }

     protected virtual int FunctionB()
     {
         return new Random().Next();
     }
}

public class TestableMyClass
{
     public Func<int> FunctionBFunc { get; set; }

     public MyClass()
     {
         FunctionBFunc = base.FunctionB;
     }

     protected override int FunctionB()
     {
         return FunctionBFunc();
     }
}

[TestClass]
public class MyClassTests
{
    private TestableMyClass _subject;

    [TestInitialize]
    public void Initialize()
    {
        _subject = new TestableMyClass();
    }

    [TestMethod]
    public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
    {
        _subject.FunctionBFunc = () => 1;

        var result = _subject.FunctionA();

        Assert.IsFalse(result);
    }
}

Hangisinin daha iyi olduğunu ve niçin olduğunu bilmek istiyorum.

Güncelleme: NOT: FunctionB ayrıca herkese açık olabilir


Örneği basit, ama tam olarak doğru değil. FunctionAbir bool döndürür, ancak yalnızca yerel bir değişken ayarlar xve hiçbir şey döndürmez.
Eric P.

1
Bu özel örnekte, FunctionB public staticancak farklı bir sınıfta olabilir.

Kod İnceleme için, basitleştirilmiş bir sürümünü değil, gerçek kodu göndermeniz beklenir. SSS bölümüne bakın. Halen, kod incelemesi aramayacağınız belirli bir soru soruyorsunuz.
Winston Ewert,

1
FunctionBtasarım tarafından bozuldu. new Random().Next()neredeyse her zaman yanlış. Örneğini enjekte etmeniz gerekir Random. ( Randomayrıca birkaç ek soruna neden olabilen kötü tasarlanmış bir sınıftır)
CodesInChaos

Daha genel bir notta, delegeler yoluyla DI kesinlikle imho
jk

Yanıtlar:


32

Orijinal poster güncellemesinden sonra düzenlenir.

Feragatname: C # programcısı değil (çoğunlukla Java veya Ruby). Cevabım şöyle olurdu: Ben hiç sınamadım ve yapmam gerektiğini sanmıyorum.

Uzun versiyon şudur: özel / korumalı yöntemler API'nin bir parçası değildir, bunlar temelde uygulama seçenekleridir, dışarıdan herhangi bir etki yapmadan tamamen gözden geçirme, güncelleme veya atma kararı alabilirsiniz.

Sanırım sınıfın dış dünyadan görülebilen bir parçası olan FunctionA () hakkında bir testiniz var. Uygulanması gereken bir sözleşmesi olan (ve test edilebilecek) tek kişi bu olmalıdır. Özel / korumalı yönteminizin yerine getirmek ve / veya test etmek için hiçbir sözleşmesi yoktur.

İlgili bir tartışmaya bakın: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones

Yorumdan sonra , eğer FunctionB halka açıksa, her ikisini de test kullanarak test edeceğim. FunctionA testinin tamamen "ünite" olmadığını düşünebilirsiniz (FunctionB adını verdiği gibi), ancak bunun için çok endişelenmemeliyim: FunctionB testi işe yararsa ancak FunctionA testi değilse, sorunun açıkça olmadığı anlamına gelir. Bir ayrımcı olarak benim için yeterince iyi olan FunctionB alt alanı.

İki testi gerçekten tamamen ayırabilmek istiyorsanız, FunctionA'yı test ederken FunctionB ile alay etmek için bir tür alaycı teknik kullanırdım (tipik olarak, bilinen bir sabit doğru değer döndürürüm). Belirli bir alaycı kütüphaneyi tavsiye edecek C # ekosistemi bilgim yok ama bu soruya bakabilirsiniz .


2
@Martin cevabı ile tamamen aynı fikirde. Sınıf için birim testleri yazarken yöntemleri test etmemelisiniz . Test ettiğiniz şey, bir sınıf davranışıdır, sözleşmenin (hangi sınıfın yapması gerektiğini beyan ettiği) tatmin edicidir. Bu nedenle, birim sınavlarınız, istisnai durumlar da dahil olmak üzere, bu sınıfa (kamuya açık metotlar / özellikler kullanarak) maruz kalan tüm gereklilikleri kapsamalıdır

Merhaba, cevap için teşekkürler, ama sorumu cevaplamadı. FunctionB özel / korumalı olup olmadığı önemli değil. Ayrıca halka açık olabilir ve yine de FunctionA'dan çağrılabilir.

Temel sınıfı yeniden tasarlamadan, bu sorunu çözmenin en yaygın yolu, MyClasssaplamak istediğiniz işlevsellik ile yöntemi alt sınıfa koymak ve geçersiz kılmaktır. Ayrıca, sorunuzu FunctionBkamuya açık bir şekilde eklemek için güncellemeniz iyi bir fikir olabilir.
Eric P.

1
protectedYöntemler, sınıfınızın farklı düzeneklerde uygulanmasının mümkün olmadığından emin olmadıkça, sınıfın genel yüzeyinin bir parçasıdır.
CodesInChaos

2
FunctionA'nın FunctionB'yi çağırması, birim test perspektifinden alakasız bir ayrıntıdır. Eğer FunctionA için testler doğru yazılırsa, daha sonra testleri kesmeden yeniden uygulanabilecek bir uygulama detayıdır (FunctionA'nın genel davranışı değişmeden bırakıldığı sürece). Asıl mesele şudur ki, FunctionB'nin rastgele bir sayı alması enjekte edilmiş bir nesneyle yapılmalıdır, böylece iyi bilinen bir sayının döndürülmesini sağlamak için test sırasında sahte bir takma kullanabilirsiniz. Bu, iyi bilinen giriş / çıkışları test etmenizi sağlar.
Dan Lyons,

11

Bir fonksiyonun test edilmesi ya da değiştirilmesi önemliyse, test edilen sınıfın özel bir uygulama detayı olmamak, farklı bir sınıfın genel uygulama detayı olmak için yeterince önemli olduğu teorisine katılıyorum.

Öyleyse sahip olduğum bir senaryodaysam

class A 
{
     public B C()
     {
         D();
     }

     private E D();
     {
         // i actually want to control what this produces when I test C()
         // or this is important enough to test on its own
         // and, typically, both of the above
     }
}

Sonra ben refactor'a gidiyorum.

class A 
{
     ICollaborator collaborator;

     public A(ICollaborator collaborator)
     {
         this.collaborator = collaborator;
     }

     public B C()
     {
         collaborator.D();
     }
}

Şimdi D () nin bağımsız olarak test edilebilir ve tamamen değiştirilebilir olduğu bir senaryo var.

Bir organizasyon aracı olarak, ortak çalışanım aynı ad alanı seviyesinde olmayabilir . Örneğin, AFooCorp.BLL'deyse, ortak çalışanım FooCorp.BLL.Collaborators'da olduğu gibi derin bir başka katman olabilir (veya hangi adı uygunsa). İşbirlikçim, daha internalsonra InternalsVisibleTomontaj niteliğindeki ünite test projelerime de maruz kalacağım erişim değiştiricisi aracılığıyla yalnızca montaj içinde görülebilir . Paket servisi doğrulayan kod üretirken, arayanlar söz konusu olduğunda API'nizi hala temiz tutabilirsiniz.


Evet, eğer ICollaborator birkaç yönteme ihtiyaç duyuyorsa. Tek işin tek bir yöntemi sarmak olan bir nesneniz varsa, bunun yerine bir temsilci ile değiştirilmesini tercih ederim.
jk.

Adlandırılmış bir delegenin bir anlam ifade edip etmediğine veya bir arayüzün anlam ifade edip etmediğine karar vermelisin, ben de senin için karar vermem. Şahsen, tek (genel) yöntem sınıflarına karşı değilim. Ne kadar küçük olursa, o kadar kolay anlaşılır hale gelir.
Anthony Pegram,

0

Martin’in neye işaret ettiğini ekleyerek,

Metodunuz özel / korumalı ise - test etmeyin. Sınıfın içindedir ve sınıf dışında erişilmemelidir.

Bahsettiğiniz her iki yaklaşımda da bu endişeler var -

Yöntem 1 - Bu aslında sınama sınama davranışını sınıf değiştirir.

Yöntem 2 - Bu aslında üretim kodunu test etmiyor, bunun yerine başka bir uygulamayı test ediyor.

Belirtilen problemde, A'nın tek mantığının, FunctionB çıktısının eşit olup olmadığını görmek olduğunu görüyorum. Açıklayıcı olmasına rağmen, FunctionB test edilmesi zor olan Rasgele bir değer verir.

FunctionB'in döneceğini bilecek şekilde MyClass'ı ayarlayabileceğimiz gerçekçi bir senaryo bekliyorum. O zaman beklenen sonucumuz bilinir, FunctionA olarak adlandırabilir ve fiili sonucu iddia edebiliriz.


3
protectedneredeyse aynı public. Sadece privateve internaluygulama detayları.
CodesInChaos

@codeinchaos - Burada merak ediyorum. Bir test için, montaj özelliklerini değiştirmezseniz korunan yöntemler 'özeldir'. Yalnızca türetilmiş türler korumalı üyelere erişebilir. Sanal istisna dışında, korunan bir testten halka neden benzer şekilde korunmaları gerektiğini anlamıyorum. lütfen detaylandırır mısın
Srikanth Venugopalan

Bu türetilmiş sınıflar farklı meclislerde olabileceğinden, üçüncü taraf koduna ve dolayısıyla sınıfınızın genel yüzeyinin bir bölümüne maruz kalırlar. Bunları test etmek internal protectediçin test projenizde bunları yapabilir , özel bir yansıma yardımcısı kullanabilir veya türetilmiş bir sınıf oluşturabilirsiniz.
CodesInChaos

@CodesInChaos, türetilmiş sınıfın farklı düzeneklerde olabileceğine karar verdi, ancak kapsam hala temel ve türetilmiş türlerle sınırlı. Erişim düzenleyicisini sadece test edilebilir hale getirmek için değiştirmek, biraz endişelendiğim bir şey. Yaptım, ama benim için bir antipattern gibi görünüyor.
Srikanth Venugopalan

0

Şahsen Metod1'i kullanıyorum, yani bütün metotları Eylemler veya Fonksiyonlar haline getirerek yaptığım için kod test edilebilirliği oldukça arttı. Herhangi bir çözümde olduğu gibi, bu yaklaşımın avantajları ve dezavantajları vardır:

Artıları

  1. Yalnızca Birim Testi için Kod Paternlerinin kullanılmasının ek karmaşıklık sağlayabileceği basit kod yapıları sağlar.
  2. Sınıfların mühürlenmesini ve Moq gibi popüler alaycı çerçevelerin gerektirdiği sanal yöntemlerin ortadan kaldırılmasını sağlar. Sınıfların kapatılması ve sanal yöntemlerin ortadan kaldırılması, satır içi ve diğer derleyici optimizasyonlarına aday olmalarını sağlar. ( https://msdn.microsoft.com/en-us/library/ff647802.aspx )
  3. Bir Birim testinde Func / Action uygulamasını değiştirmek yerine test edilebilirliği basitleştirir, Func / Action'a yeni bir değer vermek kadar basit
  4. Ayrıca, statik yöntemlerin alamadığı için statik bir Func'un başka bir yöntemden çağrılıp çağrılmadığını test etmeye izin verir.
  5. Çağrı sitesindeki bir yöntemi çağırmak için kullanılan sözdizimi aynı kaldığından, mevcut yöntemleri Funcs / Actions'a yeniden yansıtmak kolaydır. (Bir yöntemi Func / Action içine yeniden uygulayamadığınız zamanlar için eksilerini görün)

Eksileri

  1. Sınıfınız Funcs / Action'lar olarak türetilebilirse Funcs / Actions kullanılamazsa Yöntemler gibi miras yolları yoktur
  2. Varsayılan parametreler kullanılamaz. Varsayılan parametrelere sahip bir Func oluşturmak, kullanım durumuna bağlı olarak kodun kafasını karıştıracak yeni bir temsilci oluşturmayı gerektirir.
  3. Adlandırılmış parametre sözdizimini bir şey gibi yöntemler çağırmak için kullanılamaz (firstName: "S", lastName: "K")
  4. En büyük Con, Funcs ve Actions'daki 'this' referansına erişiminiz olmadığı ve bu nedenle sınıfa bağımlılıkların açıkça parametre olarak iletilmesi gerektiğidir. Tüm bağımlılıkları bildiğiniz gibi iyidir ancak Func'unuzun dayanacağı birçok özelliğe sahipseniz kötüdür. Kilometreniz kullanım durumunuza göre değişecektir.

Özetlemek gerekirse, sınıflarınızın asla geçersiz kılınmayacağını biliyorsanız, Ünite Testi için Funcs ve İşlemleri kullanmak harikadır.

Ayrıca, genellikle Func'lar için özellikler oluşturmaz, ancak bunları doğrudan bu gibi satır içi

public class MyClass
{
     public Func<int> FunctionB = () => new Random().Next();

     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }
}

Bu yardımcı olur umarım!


-1

Mock kullanarak mümkün. Nuget: https://www.nuget.org/packages/moq/

İnan bana oldukça basit ve mantıklı.

public class SomeClass
{
    public SomeClass(int a) { }

    public void A()
    {
        B();
    }

    public virtual void B()
    {

    }
}

[TestFixture]
public class Test
{
    [Test]
    public void Test_A_Calls_B()
    {
        var mockedObject = new Mock<SomeClass>(5); // You can also specify constructor arguments.
        //You can also setup what a function can return.
        var obj = mockedObject.Object;
        obj.A();

        Mock.Get(obj).Verify(x=>x.B(),Times.AtLeastOnce);//This test passes
    }
}

Sahte geçersiz kılmak için sanal yöntem gerekir.

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.