Soyut sınıfları test etme: saplamalarla genişletme?


446

Soyut sınıfları ve soyut sınıfları genişleten sınıfları nasıl test edeceğini merak ediyordum.

Soyut sınıfı genişleterek, soyut yöntemleri saplayarak ve sonra tüm somut yöntemleri test etmeli miyim? Sonra sadece geçersiz kıldığım yöntemleri test et ve soyut sınıfımı genişleten nesneler için birim testlerindeki soyut yöntemleri test et.

Soyut sınıfın yöntemlerini test etmek ve test sınıfımda bu sınıfı soyut sınıfı genişleten nesneler için genişletmek için kullanılabilecek soyut bir test durumum olmalı mı?

Soyut sınıfımın bazı somut yöntemleri olduğunu unutmayın.

Yanıtlar:


268

Bir Mock nesnesi yazın ve bunları yalnızca test için kullanın. Genellikle çok çok çok azdır (soyut sınıftan miras alınır) ve daha fazlası değildir.Daha sonra, Birim Testinizde test etmek istediğiniz soyut yöntemi çağırabilirsiniz.

Tüm diğer sınıflar gibi mantık içeren soyut sınıfı test etmelisiniz.


9
Kahretsin, bunu ilk kez sahte kullanma fikrine katıldığımı söylemeliyim.
Jonathan Allen

5
İki sınıfa ihtiyacınız var, bir sahte ve test. Mock sınıfı, test edilen Abstract sınıfının yalnızca soyut yöntemlerini genişletir. Bu yöntemler test edilmeyecekleri için op-off, return null vb. Olabilir. Test sınıfı yalnızca soyut olmayan genel API'yi (yani, Özet sınıfı tarafından uygulanan Arayüz) test eder. Abstract sınıfını genişleten herhangi bir sınıf için, soyut yöntemler ele alınmadığından ek test sınıflarına ihtiyacınız olacaktır.
cyber-monk

10
Açıkçası bunu yapmak mümkündür .. ama türetilmiş herhangi bir sınıfı gerçekten test etmek için bu temel işlevselliği tekrar tekrar test edeceksiniz .. bu soyut bir test fikstürüne sahip olmanızı sağlar, böylece testteki bu tekrarlamayı etkisiz hale getirebilirsiniz. Tüm bunlar kokuyor! İlk etapta soyut sınıfları neden kullandığınıza bir göz atmanızı ve başka bir şeyin daha iyi çalışıp çalışmadığını görmenizi şiddetle tavsiye ediyorum.
Nigel Thorne

5
aşırı onaylanmış bir sonraki cevap çok daha iyi.
Martin Spamer

22
@MartiSpamer: Bu cevabın aşırı puanlandığını söyleyemem, çünkü aşağıda daha iyi gördüğünüz cevaptan çok daha erken (2 yıl) yazılmıştır. Patrick'i teşvik edelim, çünkü bu cevabı gönderdiğinde harikaydı. Birbirimizi teşvik edelim. Şerefe
Marvin Thobejane

449

Soyut temel sınıfların kullanılmasının iki yolu vardır.

  1. Soyut nesnenizi uzmanlaştırıyorsunuz, ancak tüm istemciler türetilen sınıfı temel arabirimi üzerinden kullanacak.

  2. Tasarımınızdaki nesnelerdeki yinelemeyi ortadan kaldırmak için soyut bir temel sınıf kullanıyorsunuz ve istemciler somut uygulamaları kendi arabirimleri aracılığıyla kullanıyor.!


1 için Çözüm - Strateji Kalıbı

Seçenek 1

İlk durumunuz varsa, aslında soyut sınıfta türetilmiş sınıflarınızın uyguladığı sanal yöntemlerle tanımlanan bir arabiriminiz vardır.

Bunu gerçek bir arayüz yapmayı, soyut sınıfınızı somut olarak değiştirmeyi ve yapıcısında bu arayüzün bir örneğini almayı düşünmelisiniz. Ardından türetilmiş sınıflarınız bu yeni arayüzün uygulamaları haline gelir.

IMotor

Bu, yeni arabirimin sahte bir örneğini ve şimdi herkese açık arabirim üzerinden her yeni uygulamayı kullanarak daha önce soyut olan sınıfınızı test edebileceğiniz anlamına gelir. Her şey basit ve test edilebilir.


Çözüm 2

İkinci durumunuz varsa, soyut sınıfınız yardımcı bir sınıf olarak çalışmaktadır.

AbstractHelper

İçerdiği işlevselliğe bir göz atın. Bu çoğaltmayı en aza indirmek için herhangi birinin manipüle edilen nesnelere itilip itilemeyeceğini görün. Hâlâ bir şey kaldıysa, bunu somut uygulamanızın yapıcılarına aldığı yardımcı bir sınıf haline getirin ve temel sınıflarını kaldırın.

Motor Yardımcısı

Bu yine basit ve kolayca test edilebilir somut sınıflara yol açar.


Kural olarak

Karmaşık nesnelerin basit bir ağı üzerinden basit nesnelerin karmaşık ağını tercih edin.

Genişletilebilir test edilebilir kodun anahtarı küçük yapı taşları ve bağımsız kablolamadır.


Güncellendi: Her ikisinin karışımları nasıl ele alınır?

Bu rollerin her ikisini birden gerçekleştiren bir temel sınıf olması mümkündür ... yani: ortak bir arayüze sahiptir ve korumalı yardımcı yöntemleri vardır. Bu durumda, yardımcı yöntemleri bir sınıfa (senaryo2) dahil edebilir ve miras ağacını bir strateji modeline dönüştürebilirsiniz.

Temel sınıfınızın doğrudan uyguladığı ve diğerlerinin sanal olduğu bazı yöntemleriniz olduğunu fark ederseniz, kalıtım ağacını bir strateji modeline dönüştürebilirsiniz, ancak sorumlulukların doğru bir şekilde hizalanmadığını ve yeniden düzenleme gerekir.


Güncelleme 2: Adım Taşı Olarak Soyut Sınıflar (2014/06/12)

Geçen gün soyut kullandığım bir durum vardı, bu yüzden nedenini araştırmak istiyorum.

Konfigürasyon dosyalarımız için standart bir formatımız var. Bu özel araç, hepsi bu biçimde 3 yapılandırma dosyasına sahiptir. Her ayar dosyası için kuvvetle yazılan bir sınıf istedim, bu yüzden bağımlılık enjeksiyonu yoluyla bir sınıf, bakımını yaptığı ayarları isteyebilir.

Bu, ayarları dosya biçimlerini ve bu yöntemleri aynı yöntemleri maruz türetilmiş sınıfları ayrıştırma bilen, ama ayarları dosyasının konumunu kapsüllenmiş soyut bir temel sınıf sahip tarafından uygulandı.

Ben 3 sınıfları sarılmış ve daha sonra veri erişim yöntemleri ortaya çıkarmak için temel sınıfa delege bir "SettingsFileParser" yazmış olabilir. Ben bunu yapmaması için seçti henüz daha 3 türetilmiş sınıfları yol açacak şekilde heyeti her şeyden daha onlara kod.

Ancak ... bu kod geliştikçe ve bu ayar sınıflarının her birinin tüketicileri daha açık hale gelir. Her ayar, kullanıcılar bazı ayarları soracak ve bir şekilde dönüştüreceklerdir (ayarlar metin olduğu için, bunları sayılara dönüştüren nesnelerde sarabilirler). Bu gerçekleştikçe, bu mantığı veri işleme yöntemlerine çıkarmaya ve bunları güçlü bir şekilde yazılan ayar sınıflarına geri itmeye başlayacağım. Bu, her ayar kümesi için daha yüksek bir arayüze yol açacaktır, bu da sonunda 'ayarlar' ile uğraştığının farkında değildir.

Bu noktada, güçlü bir şekilde yazılan ayar sınıfları artık temeldeki 'ayarlar' uygulamasını ortaya çıkaran "alıcı" yöntemlerine ihtiyaç duymayacaktır.

Bu noktada artık ortak arabirimlerinin ayar erişimci yöntemlerini içermesini istemem; bu yüzden ondan türetmek yerine bir ayarları ayrıştırıcı sınıfını kapsüllemek için bu sınıfı değiştireceğim.

Bu nedenle Abstract sınıfı: şu anda temsilci kodundan kaçınmanın bir yolu ve daha sonra tasarımı değiştirmem gerektiğini hatırlatmak için koddaki bir işaretçi. Ona asla ulaşamayabilirim, bu yüzden iyi bir süre yaşayabilir ... sadece kod söyleyebilir.

Bunu herhangi bir kural ile doğru buluyorum ... "statik yöntem yok" veya "özel yöntem yok" gibi. Kodda bir koku olduğunu gösteriyorlar ... ve bu iyi. Kaçırdığınız soyutlamayı aramanızı sağlar ... ve bu arada müşterinize değer sağlamaya devam etmenizi sağlar.

Vadilerinde korunabilir kodun yaşadığı bir manzara tanımlayan bunun gibi kurallar hayal ediyorum. Yeni davranışlar ekledikçe, kodunuza yağmur yağması gibi. Başlangıçta onu nereye koyarsanız koyun, o zaman iyi tasarım güçlerinin davranışı vadilerde bitene kadar itmeye izin vermeyi yeniden düzenlersiniz.


18
Bu harika bir cevap. En beğenilenlerden çok daha iyi. Ama sonra sadece gerçekten test edilebilir kod yazmak isteyenler bunu takdir edecektir .. :)
MalcomTucker

22
Bunun cevabının ne kadar iyi olduğunu anlayamıyorum. Soyut sınıfları düşünme şeklim tamamen değişti. Teşekkürler Nigel.
MalcomTucker

4
Oh hayır .. başka bir prensip yeniden düşünmek zorundayım! Teşekkürler (şimdilik hem alaycı ve hem de alaycı olmayan bir kez bunu asimile ettim ve daha iyi bir programcı gibi hissediyorum)
Martin Lyne

11
Güzel cevap. Kesinlikle düşünülmesi gereken bir şey ... ama söylediğiniz şey soyut sınıfları kullanmamak için kaynamıyor mu?
brianestey

32
Yalnızca Kural için +1, "Basit nesnelerin karmaşık ağını basit bir karmaşık nesneler ağı üzerinden tercih edin."
David Glass

12

Soyut sınıflar ve arayüzler için yaptığım şey şudur: Nesneyi somut olarak kullanan bir test yazıyorum. Ancak X tipindeki değişken (X soyut sınıftır) testte ayarlanmamıştır. Bu test sınıfı, test takımına eklenmez, ancak değişkenin X'in somut bir uygulamasına ayarlanan bir kurulum yöntemine sahip olan alt sınıfları eklenir. Bu şekilde test kodunu çoğaltmam. Kullanılmayan testin alt sınıfları gerekirse daha fazla test yöntemi ekleyebilir.


bu alt sınıfta döküm sorunlarına neden olmaz mı? X'in a metodu varsa ve Y X'i miras alır ancak b metoduna da sahiptir. Test sınıfınızı alt sınıflandırdığınızda, b üzerinde testler yapmak için soyut değişkeninizi Y'ye atmanız gerekmez mi?
Johnno Nolan

8

Özel olarak soyut sınıfta bir birim testi yapmak için, bunu test amacı, test base.method () sonuçları ve devralma sırasında amaçlanan davranış için türetmelisiniz.

Bir yöntemi çağırarak test edersiniz, bu yüzden soyut bir sınıfı uygulayarak test edin ...


8

Soyut sınıfınız iş değeri olan somut işlevsellik içeriyorsa, genellikle doğrudan verileri özetleyen bir test çiftini oluşturarak veya bunu benim için yapmak için alaycı bir çerçeve kullanarak test edeceğim. Hangisini seçeceğim, soyut yöntemlerin teste özgü uygulamalarını yazmam gerekip gerekmediğine çok bağlıdır.

Bunu yapmam gereken en yaygın senaryo, 3. tarafça kullanılacak bir tür genişletilebilir çerçeve oluşturduğum gibi Şablon Yöntemi desenini kullandığım zamandır. Bu durumda, soyut sınıf test etmek istediğim algoritmayı tanımlayan şeydir, bu nedenle soyut tabanı test etmek belirli bir uygulamadan daha mantıklıdır.

Ancak, bu testlerin yalnızca gerçek iş mantığının somut uygulamalarına odaklanması gerektiğini düşünüyorum ; soyut sınıfın test uygulama detaylarını birim olarak yapmamalısınız , çünkü kırılgan testlerle sonuçlanırsınız.


6

Bunun bir yolu, soyut sınıfınıza karşılık gelen soyut bir test senaryosu yazmak, sonra soyut test durumunuzu alt sınıflayan somut test senaryoları yazmaktır. bunu orijinal soyut sınıfınızın her somut alt sınıfı için yapın (örn. test senaryosu hiyerarşiniz sınıf hiyerarşinizi yansıtır). Junit tarifleri kitabındaki bir arayüzü test etme: http://safari.informit.com/9781932394238/ch02lev1sec6 .

ayrıca xUnit modellerinde Testcase Superclass'a da bakın: http://xunitpatterns.com/Testcase%20Superclass.html


4

"Soyut" testlere karşı çıkarım. Testin somut bir fikir olduğunu ve soyutlamanın olmadığını düşünüyorum. Ortak öğeleriniz varsa, bunları herkesin kullanması için yardımcı yöntemlere veya sınıflara koyun.

Soyut bir test sınıfını test etmeye gelince, kendinize neyi test ettiğinizi sorun. Birkaç yaklaşım vardır ve senaryonuzda neyin işe yaradığını bulmalısınız. Alt sınıfınızda yeni bir yöntemi test etmeye mi çalışıyorsunuz? Ardından testlerinizin yalnızca bu yöntemle etkileşime girmesini sağlayın. Temel sınıfınızdaki yöntemleri test ediyor musunuz? Sonra muhtemelen sadece o sınıf için ayrı bir fikstür var ve her yöntemi gerektiği kadar testle ayrı ayrı test edin.


Ben soyut test durumda yolda gidiyordu bu yüzden zaten test etmişti kodu tekrar test etmek istemiyordu. Soyut sınıfımdaki tüm somut yöntemleri tek bir yerde test etmeye çalışıyorum.
Paul Whelan

7
En azından bazı (çok?) Vakalarda yardımcı sınıflara ortak unsurlar çıkarmaya katılmıyorum. Soyut bir sınıf somut bir işlevsellik içeriyorsa, bu işlevselliği doğrudan test etmek tamamen kabul edilebilir.
Seth Petry-Johnson

4

Soyut bir sınıfı test etmek için bir koşum takımı kurarken genellikle takip ettiğim desen:

public abstract class MyBase{
  /*...*/
  public abstract void VoidMethod(object param1);
  public abstract object MethodWithReturn(object param1);
  /*,,,*/
}

Ve test altında kullandığım sürüm:

public class MyBaseHarness : MyBase{
  /*...*/
  public Action<object> VoidMethodFunction;
  public override void VoidMethod(object param1){
    VoidMethodFunction(param1);
  }
  public Func<object, object> MethodWithReturnFunction;
  public override object MethodWithReturn(object param1){
    return MethodWihtReturnFunction(param1);
  }
  /*,,,*/
}

Beklemediğim zaman soyut yöntemler çağrılırsa, testler başarısız olur. Testleri düzenlerken, soyut yöntemleri, iddiaları yerine getiren, istisnalar atan, farklı değerler döndüren lambdaslarla kolayca saplayabilirim.


3

Somut yöntemler, stratejinin işe yaramayacağı soyut yöntemlerden herhangi birini çağırırsa ve her bir alt sınıf davranışını ayrı ayrı test etmek istersiniz. Aksi takdirde, soyut sınıf somut yöntemlerin çocuk sınıflarından ayrılması şartıyla, onu genişletmek ve tanımladığınız gibi soyut yöntemleri saplamak iyi olmalıdır.


2

Sanırım soyut bir sınıfın temel işlevselliğini test etmek isteyebilirsiniz ... Ama muhtemelen herhangi bir yöntemi geçersiz kılmadan sınıfı genişleterek ve soyut yöntemler için minimum çaba sarf ederek en iyi durumda olacaksınız.


2

Soyut bir sınıf kullanmak için ana motivasyonlardan biri, uygulamanızda polimorfizmi etkinleştirmektir - yani: çalışma zamanında farklı bir sürümü değiştirebilirsiniz. Aslında, soyut sınıf dışında bir şablon kullanmakla aynı şeydir, ancak soyut sınıf, genellikle Şablon deseni olarak adlandırılan bazı ortak sıhhi tesisat sağlar .

Birim testi açısından, dikkate alınması gereken iki şey vardır:

  1. Soyut sınıfınızın onunla ilişkili sınıflarla etkileşimi . Soyut sınıfınızın başkalarıyla iyi oynadığını gösterdiği için, sahte bir test çerçevesi kullanmak bu senaryo için idealdir.

  2. Türetilmiş sınıfların işlevselliği . Türetilmiş sınıflarınız için yazdığınız özel mantığınız varsa, bu sınıfları ayrı ayrı test etmelisiniz.

edit: RhinoMocks dinamik sınıfından türeterek zamanında sahte nesneler oluşturabilirsiniz harika bir sahte test çerçevesidir. Bu yaklaşım, saatlerce elde kodlanmış türetilmiş dersleri kurtarabilir.


2

Öncelikle soyut sınıf bazı somut yöntemler içeriyorsa, bunu yapmalısınız bence bu örnek

 public abstract class A 

 {

    public boolean method 1
    {
        // concrete method which we have to test.

    }


 }


 class B extends class A

 {

      @override
      public boolean method 1
      {
        // override same method as above.

      }


 } 


  class Test_A 

  {

    private static B b;  // reference object of the class B

    @Before
    public void init()

      {

      b = new B ();    

      }

     @Test
     public void Test_method 1

       {

       b.method 1; // use some assertion statements.

       }

   }

1

Soyut bir sınıf uygulamanız için uygunsa, türetilmiş bir beton sınıfı test edin (yukarıda önerildiği gibi). Varsayımlarınız doğru.

Gelecekteki karışıklığı önlemek için, bu somut test sınıfının bir sahte değil sahte olduğunu unutmayın .

Katı terimlerle, bir alay aşağıdaki özelliklerle tanımlanır:

  • Test edilen konu sınıfının her bir bağımlılığının yerine bir sahte kullanılır.
  • Sahte, bir arayüzün sahte bir uygulamasıdır (genel bir kural olarak, bağımlılıkların arabirimler olarak bildirilmesi gerektiğini hatırlayabilirsiniz; test edilebilirlik bunun temel nedenlerinden biridir)
  • Alaycı arayüz üyelerinin davranışları - ister yöntemler ister özellikler - test zamanında (yine alaycı bir çerçeve kullanılarak) sağlanır. Bu şekilde, test edilen uygulamanın bağımlılıklarının uygulanmasıyla (hepsinin kendi ayrı testleri olması gerekir) birleştirilmesini önlersiniz.

1

@ Patrick-desjardins cevabını takiben, özeti uyguladım ve uygulama sınıfı @Testaşağıdaki gibi:

Soyut sınıf - ABC.java

import java.util.ArrayList;
import java.util.List;

public abstract class ABC {

    abstract String sayHello();

    public List<String> getList() {
        final List<String> defaultList = new ArrayList<>();
        defaultList.add("abstract class");
        return defaultList;
    }
}

As Özet sınıflar başlatılamaz, ancak sınıflandırma yapılabilir beton sınıfı, DEF.java , aşağıdaki gibi olduğu:

public class DEF extends ABC {

    @Override
    public String sayHello() {
        return "Hello!";
    }
}

@ Hem soyut hem de soyut olmayan yöntemi test etmek için test sınıfı:

import org.junit.Before;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import java.util.Collection;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;

import org.junit.Test;

public class DEFTest {

    private DEF def;

    @Before
    public void setup() {
        def = new DEF();
    }

    @Test
    public void add(){
        String result = def.sayHello();
        assertThat(result, is(equalTo("Hello!")));
    }

    @Test
    public void getList(){
        List<String> result = def.getList();
        assertThat((Collection<String>) result, is(not(empty())));
        assertThat(result, contains("abstract class"));
    }
}
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.