En İyi Uygulama: JUnit sınıf alanlarını setUp () 'da mı yoksa bildirimde mi başlatın?


120

Sınıf alanlarını böyle bir bildirimde başlatmalı mıyım?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

Veya bunun gibi setUp () içinde mi?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

İlk formu kullanma eğilimindeyim çünkü daha özlü ve son alanları kullanmama izin veriyor. Ben yoksa ihtiyaç seti makyaj için Kurulum () yöntemini kullanmak, hala kullanmak ve neden gerekir?

Açıklama: JUnit, test sınıfını test yöntemi başına bir kez başlatacaktır. Bu list, nerede beyan ettiğime bakılmaksızın test başına bir kez oluşturulacağı anlamına gelir . Aynı zamanda testler arasında geçici bir bağımlılık olmadığı anlamına gelir. Bu nedenle, setUp () kullanmanın hiçbir avantajı yok gibi görünüyor. Bununla birlikte, JUnit SSS'de setUp () 'da boş bir koleksiyon başlatan birçok örnek var, bu yüzden bir neden olması gerektiğini düşünüyorum.


2
Cevabın JUnit 4'te (bildirimde başlat) ve JUnit 3'te (setUp kullanın) farklı olduğuna dikkat edin; bu kafa karışıklığının köküdür.
Nils von Barth

Yanıtlar:


99

Temel test şablonu gibi JUnit SSS'deki örnekleri özellikle merak ediyorsanız, orada gösterilen en iyi uygulamanın test edilen sınıfın kurulum yönteminizde (veya bir test yönteminde) somutlaştırılması gerektiğini düşünüyorum. .

JUnit örnekleri, setUp yönteminde bir ArrayList oluşturduğunda, hepsi bu ArrayList'in davranışını testIndexOutOfBoundException, testEmptyCollection ve benzeri durumlarla test etmeye devam eder. Buradaki perspektif, bir sınıf yazan ve doğru çalıştığından emin olan biri.

Muhtemelen kendi sınıflarınızı test ederken de aynısını yapmalısınız: nesnenizi kurulumda veya bir test yönteminde oluşturun, böylece daha sonra bozarsanız makul çıktılar elde edebilirsiniz.

Öte yandan, test kodunuzda bir Java toplama sınıfı (veya bu konuda başka bir kitaplık sınıfı) kullanırsanız, bunun nedeni muhtemelen onu test etmek istemeniz değildir - bu yalnızca test fikstürünün bir parçasıdır. Bu durumda, amaçlandığı gibi çalıştığını güvenle varsayabilirsiniz, bu nedenle bildirimde başlatmak bir sorun olmayacaktır.

Değeri ne olursa olsun, oldukça büyük, birkaç yıllık, TDD tarafından geliştirilmiş bir kod tabanı üzerinde çalışıyorum. Onların beyanlarındaki şeyleri alışkanlık olarak test kodunda başlatıyoruz ve bu projede bulunduğum bir buçuk yıldır, bu hiçbir zaman bir soruna neden olmadı. Yani en azından bunun makul bir şey olduğuna dair bazı anekdotsal kanıtlar var.


45

Kendimi kazmaya başladım ve kullanmanın potansiyel bir avantajını buldum setUp(). Yürütme sırasında herhangi bir istisna atılırsa setUp(), JUnit çok yararlı bir yığın izi yazdıracaktır. Öte yandan, nesne oluşturma sırasında bir istisna atılırsa, hata mesajı sadece JUnit'in test durumunu başlatamadığını ve hatanın meydana geldiği satır numarasını görmediğinizi söyler, çünkü muhtemelen JUnit testi başlatmak için yansıma kullanır. sınıflar.

Bunların hiçbiri boş bir koleksiyon oluşturma örneği için geçerli değildir, çünkü bu asla fırlatmaz, ancak setUp()yöntemin bir avantajıdır .


18

Alex B'nin cevabına ek olarak.

Kaynakları belirli bir durumda başlatmak için setUp yöntemini kullanmak bile gereklidir. Bunu yapıcıda yapmak yalnızca bir zamanlama meselesi değildir, aynı zamanda JUnit'in testleri çalıştırma şekli nedeniyle, her test durumu bir tane çalıştırdıktan sonra silinecektir.

JUnit, ilk olarak her test yöntemi için testClass örneklerini oluşturur ve her örnek oluşturulduktan sonra testleri çalıştırmaya başlar. Test yöntemini çalıştırmadan önce, bazı durumların hazırlanabileceği kurulum yöntemi çalıştırılır.

Veritabanı durumu yapıcıda oluşturulacaksa, tüm örnekler, her testi çalıştırmadan önce db durumunu birbirinin hemen ardından başlatır. İkinci testten itibaren, testler kirli bir durumda çalışacaktı.

JUnits yaşam döngüsü:

  1. Her test yöntemi için farklı bir test sınıfı örneği oluşturun
  2. Her test sınıfı örneği için tekrarlayın: kurulumu çağırın + test yöntemini çağırın

İki test yöntemiyle bir testte bazı kayıtlarla şunları elde edersiniz: (sayı, karma koddur)

  • Yeni örnek oluşturma: 5718203
  • Yeni örnek oluşturma: 5947506
  • Kurulum: 5718203
  • TestOne: 5718203
  • Kurulum: 5947506
  • TestTwo: 5947506

3
Doğru ama konu dışı. Veritabanı esasen küresel durumdur. Bu karşılaştığım bir sorun değil. Ben sadece tamamen bağımsız testlerin uygulama hızıyla ilgileniyorum.
Craig P. Motlin

Bu başlatma sırası yalnızca önemli bir uyarı olduğu JUnit 3 için geçerlidir. JUnit 4'te test örnekleri tembel olarak oluşturulur, bu nedenle bildirimde veya bir kurulum yönteminde başlatma her ikisi de test zamanında gerçekleşir. Ayrıca tek seferlik kurulum @BeforeClassiçin JUnit 4'te kullanılabilir .
Nils von Barth

11

JUnit 4'te:

  • Test Altındaki Sınıf için, @Beforehataları yakalamak için bir yöntemde başlatın.
  • İçin diğer sınıfların , bildiriminde başlatmak ...
    • ... kısalık ve finaltam olarak soruda belirtildiği gibi alanları işaretlemek için,
    • ... başarısız olabilecek karmaşık başlatma olmadığı sürece , bu durumda @Beforehataları yakalamak için kullanın .
  • İçin küresel devlet (özellikle. Yavaş başlatma bir veritabanı gibi), kullanım @BeforeClass, ama dikkatli olun testler arasında bağımlılıkları.
  • Tek bir testte kullanılan bir nesnenin ilklendirilmesi elbette test yönteminin kendisinde yapılmalıdır.

Bir @Beforeyöntemde veya test yönteminde başlatma , arızalar hakkında daha iyi hata raporları almanıza olanak tanır. Bu, özellikle Test Edilen Sınıfın somutlaştırılması için kullanışlıdır (kırabileceğiniz), ancak dosya sistemi erişimi ("dosya bulunamadı") veya bir veritabanına bağlanma ("bağlantı reddedildi") gibi harici sistemleri çağırmak için de yararlıdır.

Öyle kabul edilebilir basit bir standart ve her zaman kullanım için @Beforebildiriminde daima initialize (net hatalar ancak ayrıntılı) ya da (Özlü ama kafa karıştırıcı hatalar verir) karmaşık kodlama kuralları takip etmek zor olduğundan, ve bu büyük bir anlaşma değil.

Başlatma setUp, tüm test örneklerinin hevesle başlatıldığı ve pahalı başlatma yaparsanız sorunlara (hız, bellek, kaynak tükenmesi) neden olan JUnit 3'ün bir kalıntısıdır. Bu nedenle en iyi uygulama, setUpyalnızca test yürütüldüğünde çalıştırılan pahalı başlatma yapmaktı . Bu artık geçerli değil, bu yüzden kullanımı çok daha az gerekli setUp.

Bu, lede'yi gömen diğer birkaç yanıtı, özellikle Craig P. Motlin (sorunun kendisi ve kendi kendine cevap), Moss Collum (test edilen sınıf) ve dsaff tarafından özetliyor.


7

JUnit 3'te, alan başlatıcılarınız, herhangi bir test çalıştırılmadan önce test yöntemi başına bir kez çalıştırılacaktır . Alan değerleriniz bellekte küçük olduğu, kurulum süresi çok az olduğu ve genel durumu etkilemediği sürece, alan başlatıcıları kullanmak teknik olarak iyidir. Ancak, bunlar tutmazsa, ilk test çalıştırılmadan önce alanlarınızı ayarlamak için çok fazla bellek veya zaman harcayabilir ve hatta muhtemelen belleğiniz tükenebilir. Bu nedenle, birçok geliştirici her zaman alan değerlerini setUp () yönteminde ayarlar; burada, kesinlikle gerekli olmasa bile her zaman güvenli olur.

JUnit 4'te, test nesnesi başlatmanın test çalıştırmadan hemen önce gerçekleştiğini ve bu nedenle alan başlatıcıların kullanılmasının daha güvenli ve önerilen stil olduğunu unutmayın.


İlginç. Yani ilk başta tarif ettiğiniz davranış sadece JUnit 3 için mi geçerli?
Craig P. Motlin

6

Sizin durumunuzda (bir liste oluşturmak) pratikte bir fark yoktur. Ancak genellikle setUp () kullanmak daha iyidir, çünkü bu Junit'in İstisnaları doğru şekilde raporlamasına yardımcı olur. Bir Testin yapıcısında / başlatıcısında bir istisna meydana gelirse, bu bir test hatasıdır . Bununla birlikte, kurulum sırasında bir istisna meydana gelirse, bunu testin kurulumunda bir sorun olarak düşünmek doğaldır ve junit bunu uygun şekilde rapor eder.


1
iyi söyledin. Her zaman setUp () içinde somutlaştırmaya alışın ve endişelenmeniz gereken bir soru daha var - örneğin fooBar'ımı nerede başlatmalıyım, koleksiyonum nerede. Bu, uymanız gereken bir tür kodlama standardıdır. Listelerle değil, diğer örneklerle size fayda sağlar.
Olaf Kock

@Olaf Kodlama standardı hakkında bilgi için teşekkürler, bunu düşünmemiştim. Moss Collum'un kodlama standardı fikrine daha çok katılıyorum.
Craig P. Motlin

5

Öncelikle, kurulum yöntemini kullanmayan okunabilirliği tercih ederim. Temel bir kurulum işlemi uzun sürdüğünde ve her testte tekrarlandığında bir istisna yapıyorum.
Bu noktada, bu işlevselliği,@BeforeClass ek açıklamayı taşıyorum (daha sonra optimize et).

@BeforeClassKurulum yöntemini kullanarak optimizasyon örneği : Bazı veritabanı işlevsel testleri için dbunit kullanıyorum. Kurulum yöntemi, veritabanının bilinen bir duruma getirilmesinden sorumludur (çok yavaş ... 30 saniye - veri miktarına bağlı olarak 2 dakika). Bu verileri, açıklamalı kurulum yöntemine yükledim @BeforeClassve ardından her bir testin içindeki veritabanını yeniden yüklemek / başlatmak yerine aynı veri setine karşı 10-20 test çalıştırıyorum.

Junit 3.8'i kullanmak (örneğinizde gösterildiği gibi TestCase'i genişletmek), bir açıklama eklemekten biraz daha fazla kod yazmayı gerektirir, ancak "sınıf kurulumundan önce bir kez çalıştır" yine de mümkündür.


1
+1 çünkü okunabilirliği de tercih ediyorum. Ancak, ikinci yolun bir optimizasyon olduğuna ikna olmadım.
Craig P. Motlin

@Motlin Kurulumla nasıl optimize edebileceğinizi açıklığa kavuşturmak için dbunit örneğini ekledim.
Alex B

Veritabanı esasen küresel durumdur. Dolayısıyla, db kurulumunu setUp () 'a taşımak bir optimizasyon değildir, testlerin düzgün şekilde tamamlanması gerekir.
Craig P. Motlin

@Alex B: Motlin'in dediği gibi, bu bir optimizasyon değil. Yalnızca kodun neresinde başlatılacağını değiştiriyorsunuz, ancak kaç kez ne de hızlı bir şekilde değil.
Eddie

"@BeforeClass" ek açıklamasının kullanımını ima etmeyi amaçladım. Açıklığa kavuşturmak için örneği düzenleme.
Alex B

2

Her test, nesnenin yeni bir örneğiyle bağımsız olarak yürütüldüğünden, Test nesnesinin dahili bir duruma sahip olduğuna dair setUp(), tek bir test ve tearDown(). Bu, (başkalarının belirttiği nedenlere ek olarak) kullanmanın iyi olmasının bir nedenidir.setUp() yöntemi .

Not: Bir JUnit test nesnesinin statik durumu sürdürmesi kötü bir fikirdir! Testlerinizde izleme veya teşhis amaçları dışında herhangi bir amaçla statik değişkeni kullanırsanız, JUnit'in amacının bir kısmını geçersiz kılarsınız, yani testler herhangi bir sırayla çalıştırılabilir, her test bir taze, temiz durum.

Kullanmanın avantajları setUp(), her test yönteminde başlatma kodunu kesip yapıştırmanız gerekmemesi ve yapıcıda test kurulum kodunun olmamasıdır. Sizin durumunuzda çok az fark var. Sadece boş bir liste oluşturmak, gösterdiğiniz şekilde veya önemsiz bir başlangıç ​​olduğu için yapıcıda güvenle yapılabilir. Bununla birlikte, sizin ve başkalarının da belirttiği gibi, olasılıkla bir hata yapabilecek herhangi bir şey Exceptionyapılmalıdır.setUp() başarısız olursa tanılama yığını dökümünü alabilmeniz için .

Sizin durumunuzda, sadece boş bir liste oluşturduğunuzda, önerdiğiniz şekilde yapacağım: Yeni listeyi bildirim noktasına atayın. Özellikle de bu şekilde final, test sınıfınız için mantıklıysa , işaretleme seçeneğiniz olduğu için.


1
+1, çünkü nesne yapımı sırasında listeyi nihai olarak işaretlemek için başlatmayı gerçekten destekleyen ilk kişi sizsiniz. Statik değişkenlerle ilgili şeyler konu dışıdır.
Craig P. Motlin

@Motlin: doğru, statik değişkenlerle ilgili şeyler biraz konu dışı. Bunu neden eklediğimden emin değilim, ama o zaman uygun görünüyordu, ilk paragrafta söylediklerimin bir uzantısı.
Eddie

Yine finalde soruda avantajından bahsediliyor.
Nils von Barth

0
  • Sabit değerler (fikstürlerde veya iddialarda kullanım) beyanlarında ve final(asla değişmeyecek şekilde) başlatılmalıdır.

  • test edilen nesne kurulum yönteminde başlatılmalıdır çünkü bir şeyler ayarlayabiliriz. Elbette şimdi bir şey ayarlamayabiliriz ama daha sonra ayarlayabiliriz. İnit yönteminde örnekleme, değişiklikleri kolaylaştıracaktır.

  • Test edilen nesnenin bağımlılıkları, eğer bunlarla alay ediliyorsa, kendi başınıza somutlaştırılmamalıdır: bugün sahte çerçeveler, yansıtma yoluyla onu somutlaştırabilir.

Alay etmeye bağımlı olmayan bir test şöyle görünebilir:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

İzole edilecek bağımlılıkları olan bir test şöyle görünebilir:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}
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.