Tek bir sınıfı tek tek veya birden fazla dosya test etmek için mi?


20

Kuruluşum için yönergeleri bir araya getirmeye yardımcı olacak birim test en iyi uygulamalarını araştırırken, test fikstürlerini (test sınıfları) ayırmanın veya tüm testleri tek bir sınıfta tek bir dosyada tutmanın daha iyi veya yararlı olup olmadığı sorusuyla karşılaştım.

Fwiw, saf anlamda tek bir sınıfı, test başına bir iddiayı, tüm bağımlılıkları alay etmeyi vb. Hedefleyen beyaz kutu testleri olduklarından "birim testler" den bahsediyorum.

Örnek senaryo, iki yöntemi olan bir sınıftır (Belge olarak adlandırın): CheckIn ve CheckOut. Her yöntem, davranışlarını kontrol eden çeşitli kurallar vb. Uygular. Test başına tek onaylama kuralına göre, her yöntem için birden fazla test yapacağım. Tüm testleri ya ve DocumentTestsgibi adlarla tek bir sınıfa yerleştirebilirim .CheckInShouldThrowExceptionWhenUserIsUnauthorizedCheckOutShouldThrowExceptionWhenUserIsUnauthorized

Veya iki ayrı test sınıfım olabilir: CheckInShouldve CheckOutShould. Bu durumda, test isimlerim kısalır, ancak belirli bir davranış (yöntem) için tüm testler bir arada olacak şekilde organize edilirler.

Eminim pro's ve con ya yaklaşım ve herkes birden fazla dosya ile rota gittiğini merak ediyorum ve eğer öyleyse, neden? Veya tek dosya yaklaşımını seçtiyseniz, neden daha iyi olduğunu düşünüyorsunuz?


2
Test yöntemi adlarınız çok uzun. Bir test yönteminin birden fazla iddia içerdiği anlamına gelse bile bunları basitleştirin.
Bernard

12
@Bernard: yöntem başına birden fazla iddia olması kötü uygulama olarak kabul edilir, ancak uzun test yöntemi adları kötü uygulama olarak DEĞİLDİR. Genellikle adın kendisinde yöntemin neyi test ettiğini belgeliyoruz. Örneğin constructor_nullSomeList (), setUserName_UserNameIsNotValid () vb ...
c_maker

4
@ Bernard: Ben de uzun test isimleri (ve sadece orada!) Kullanın. Onları seviyorum, çünkü neyin işe yaramadığı çok açık (isimleriniz iyi seçimler ise;)).
Sebastian Bauer

Aynı sonucu test ederse, birden fazla iddia kötü bir uygulama değildir. Örneğin. Eğer olmazdı testResponseContainsSuccessTrue(), testResponseContainsMyData()ve testResponseStatusCodeIsOk(). Bir single onları olurdu testResponse()üç iddia etti: assertEquals(200, response.status), assertEquals({"data": "mydata"}, response.data)veassertEquals(true, response.success)
Juha Untinen

Yanıtlar:


18

Nadirdir, ancak bazen test edilen belirli bir sınıf için birden fazla test sınıfına sahip olmak mantıklıdır. Genellikle farklı kurulumlar gerektiğinde ve testlerin bir alt kümesinde paylaşıldığında bunu yapardım.


3
Bu yaklaşımın bana ilk başta neden sunulduğunu gösteren güzel bir örnek. Örneğin, CheckIn yöntemini sınamak istediğimde, test edilen nesnenin her zaman bir şekilde ayarlanmasını istiyorum, ancak CheckOut yöntemini sınadığımda farklı bir kuruluma ihtiyacım var. İyi bir nokta.
SonOfPirate

14

Tek bir sınıf için bir testi birden çok test sınıfına ayırmanızın zorlayıcı bir nedenini gerçekten göremiyorum. Sürüş fikri uyumu sınıf düzeyinde tutmak olduğundan, bunun için test düzeyinde de çaba göstermelisiniz. Bazı rastgele nedenler:

  1. Kurulum kodunun kopyalanmasına (ve birden çok sürümünün korunmasına) gerek yoktur
  2. Bir test sınıfında gruplanmışlarsa, IDE'den bir sınıfa ait tüm testleri çalıştırmak daha kolaydır
  3. Test sınıfları ve sınıflar arasında bire bir eşleme ile daha kolay sorun giderme.

Güzel nokta. Test edilen sınıf korkunç bir çamursa, bazılarının yardımcı mantık içerdiği birkaç test sınıfını hariç tutmam. Çok katı kuralları sevmiyorum. Bunun dışında harika noktalar.
İş

1
Tek bir sınıfa karşı çalışan test armatürleri için ortak bir temel sınıf alarak yinelenen kurulum kodunu ortadan kaldırırdım. Emin değilim diğer iki noktaya hiç katılıyorum.
SonOfPirate

JUnit'te, örneğin, farklı sınıflara ihtiyaç duyulan farklı Koşucular kullanarak bir şeyler test etmek isteyebilirsiniz.
Joachim Nilsson

Her birinde karmaşık matematik ile çok sayıda yöntem içeren bir sınıf yazarken, 85 test buldum ve şimdiye kadar yöntemlerin% 24'ü için sadece yazılı testler buldum. Kendimi burada buluyorum, çünkü bu devasa sayıda testi ayrı kategorilere ayırmanın çılgınca olup olmadığını merak ediyordum, böylece alt kümeleri çalıştırabilirim.
Mooing Duck

7

Bir sınıf için birim testlerini birden fazla dosyaya bölmek zorundaysanız, bu sınıfın kendisinin kötü tasarlandığının bir göstergesi olabilir. Tek Sorumluluk İlkesine ve diğer programlama en iyi uygulamalarına makul şekilde uyan bir sınıf için birim testlerini bölmenin daha yararlı olacağı hiçbir senaryo düşünemiyorum.

Ayrıca, birim testlerinde daha uzun yöntem adlarına sahip olmak kabul edilebilir, ancak sizi rahatsız ediyorsa, adları kısaltmak için birim test adlandırma kuralınızı her zaman yeniden düşünebilirsiniz.


Ne yazık ki, gerçek dünyada tüm sınıflar SRP ve diğerlerine bağlı değildir ... ve eski kodu birim test ederken bu bir sorun haline gelir.
Péter Török

3
SRP'nin burada nasıl bir ilişki olduğundan emin değilim. Sınıfımda birden çok yöntem var ve davranışlarını yönlendiren tüm farklı gereksinimler için birim testleri yazmam gerekiyor. 50 test yöntemi ile bir test sınıfı veya test edilen sınıftaki belirli bir yöntemle ilgili 10 test içeren 5 test sınıfı olması daha mı iyi?
SonOfPirate

2
@SonOfPirate: Çok fazla teste ihtiyacınız varsa, yöntemlerinizin çok fazla yaptığı anlamına gelebilir. Yani SRP burada çok alakalı. SRP'yi sınıfların yanı sıra yöntemlere de uygularsanız, sınanması kolay çok sayıda küçük sınıfınız ve yönteminiz olur ve çok fazla sınama yöntemine ihtiyacınız olmaz. (Ama Péter Török'e katılıyorum, eski kodu test ettiğinizde, iyi uygulamaların kapı dışarıda olduğunu ve size verilmiş olanla yapabileceğiniz en iyisini yapmanız gerektiğini ...)
c_maker

Verebileceğim en iyi örnek, nesnenin teslim edileceği uygun durumda olup olmadığı, örneğin, kullanıma alma ve değişiklik olup olmadığı gibi kimin işlemi gerçekleştirmesine (yetkilendirme) izin verdiğini belirleyen iş kurallarına sahip olduğumuz CheckIn yöntemidir. yapıldı. Yetkilendirme kurallarının uygulandığını doğrulayan birim testlerin yanı sıra nesne teslim alınmadığında, değiştirilmediğinde CheckIn çağrılırsa yöntemin doğru davrandığını doğrulayan testlerimiz olacak. testleri. Bunun yanlış olduğunu mu söylüyorsun?
SonOfPirate

Bu konuda sert ve hızlı, doğru ya da yanlış cevaplar olduğunu düşünmüyorum. Yaptığınız şey sizin için çalışıyorsa, bu harika. Bu sadece genel bir kılavuz hakkındaki bakış açım.
FishBasketGordo

2

Testleri birden fazla sınıfa ayırmaya karşı yaptığım argümanlardan biri, ekipteki diğer geliştiricilerin (özellikle test meraklısı olmayanlar) mevcut testleri bulmanın zorlaşmasıdır ( Gee, bunun için zaten bir test olup olmadığını merak ediyorum Nerede olacağını merak ediyorum? ) ve ayrıca yeni testler nereye koyacağım ( bu sınıfta bu yöntem için bir test yazacağım, ancak bunları yeni bir dosyaya mı yoksa mevcut bir dosyaya mı koymam gerektiğinden emin değilim biri? )

Farklı testlerin büyük ölçüde farklı kurulumlar gerektirmesi durumunda, TDD'nin bazı uygulayıcılarının "fikstürü" veya "kurulumu" farklı sınıflara / dosyalara koyduğunu gördüm, ancak testlerin kendisini değil.


1

Keşke benimsemeyi seçtiğim tekniğin ilk gösterildiği bağlantıyı hatırlayabilseydim / bulabilsem. Temelde, test edilen her sınıf için, test edilen her üye için iç içe test fikstürleri (sınıflar) içeren tek, soyut bir sınıf oluşturuyorum. Bu, başlangıçta istenen ayrımı sağlar, ancak tüm testleri her konum için aynı dosyada tutar. Buna ek olarak, test çalıştırıcısında kolay gruplama ve sıralama sağlayan bir sınıf adı oluşturur.

İşte bu yaklaşımın orijinal senaryomda nasıl uygulandığına dair bir örnek:

public abstract class DocumentTests : TestBase
{
    [TestClass()]
    public sealed class CheckInShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }

    [TestClass()]
    public sealed class CheckOutShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }
}

Bu, test listesinde aşağıdaki testlerin görünmesine neden olur:

DocumentTests+CheckInShould.ThrowExceptionWhenUserIsNotAuthorized
DocumentTests+CheckOutShould.ThrowExceptionWhenUserIsNotAuthorized

Daha fazla test eklendikçe, kolayca sınıfta sınıflandırılırlar ve sınıf adına göre sıralanırlar.

Kaldıracağımı öğrendiğim bu yaklaşımın bir diğer avantajı da, bu yapının nesneye yönelik doğasıdır. içerdiği testler için iç içe sınıf.


1

Bir dakika daha düşünmeye çalışın.

Neden sınıf başına sadece bir test dersiniz olsun? Sınıf testi mi yoksa birim testi mi yapıyorsunuz? Derslere bile bağımlı mısınız?

Birim testi, belirli bir bağlamda belirli bir davranışı test ediyor olmalıdır. Sınıfınızın zaten bir CheckInaracı olması, öncelikle ona ihtiyaç duyan bir davranış olmalıdır.

Bu sahte kodu nasıl düşünürdünüz:

// check_in_test.file

class CheckInTest extends TestCase {
        /** @test */
        public function unauthorized_users_cannot_check_in() {
                $this->expectException();

                $document = new Document($unauthorizedUser);

                $document->checkIn();
        }
}

Şimdi checkInyöntemi doğrudan test etmiyorsunuz . Bunun yerine bir davranışı test ediyorsunuz (neyse ki kontrol etmek için;)) ve bunu yeniden yapılandırmanız Documentve farklı sınıflara ayırmanız veya başka bir sınıfı birleştirmeniz gerekiyorsa, test dosyalarınız hala tutarlı, hala mantıklı yeniden düzenleme yaparken mantığı asla değiştirmezsiniz, sadece yapı.

Bir sınıf için bir test dosyası, ihtiyacınız olduğunda yeniden düzenleme yapmayı zorlaştırır ve testler, kodun etki alanı / mantığı açısından da daha az mantıklıdır.


0

Genel olarak testleri bir yöntemden ziyade sınıfın davranışını test etmek olarak düşünmenizi tavsiye ederim. Yani, bazı testlerinizin beklenen davranışı test etmek için sınıfta her iki yöntemi de çağırması gerekebilir.

Genellikle üretim sınıfı başına bir birim test sınıfı ile başlıyorum, ancak sonunda bu birim test sınıfını test ettikleri davranışa göre çeşitli test sınıflarına bölebilir. Başka bir deyişle, test sınıfının CheckInShould ve CheckOutShould'a bölünmesine karşı değil, test edilen birimin davranışına göre bölünmesini öneriyorum.


0

1. Neyin yanlış gidebileceğini düşünün

İlk geliştirme sırasında, ne yaptığınızı oldukça iyi biliyorsunuz ve her iki çözüm de muhtemelen işe yarayacak.

Daha sonra bir değişiklikten sonra test başarısız olduğunda daha ilginç hale gelir. İki olasılık vardır:

  1. Cidden kırıldınız CheckIn(veya CheckOut). Yine, bu durumda hem tek dosya hem de iki dosya çözümü iyidir.
  2. Her ikisini CheckInve CheckOut(ve belki de testlerini) her biri için anlamlı olacak şekilde değiştirdiniz, ancak her ikisi için birlikte değil. Çiftin tutarlılığını kırdın. Bu durumda, testleri iki dosyaya bölmek sorunu anlamayı zorlaştıracaktır.

2. Hangi testlerin kullanıldığını düşünün

Testler iki ana amaca hizmet eder:

  1. Programın düzgün çalışıp çalışmadığını otomatik olarak kontrol edin. Testlerin tek bir dosyada veya birkaç dosyada olması bu amaç için önemli değildir.
  2. Bir insan okuyucunun programı anlamasına yardımcı olun. Yakından ilişkili işlevsellik testleri bir arada tutulursa, bu çok daha kolay olacaktır, çünkü bunların tutarlılıklarını görmek, anlaşılmanın hızlı bir yoludur.

Yani?

Her iki bakış açısı da testleri bir arada tutmak yardımcı olabilir, ancak incinmez.

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.