Aşırı alaycı ihtiyaç nedeniyle kırılgan birim testleri


21

Ekibimde uyguladığımız birim testlerimizle ilgili giderek daha sinir bozucu bir problemle mücadele ediyorum. İyi tasarlanmış olmayan eski kodlara birim testleri eklemeye çalışıyoruz ve testlerin gerçek eklenmesi konusunda herhangi bir zorluk yaşamadıkça, testlerin nasıl sonuçlanacağıyla mücadele etmeye başlıyoruz.

Sorunun bir örneği olarak, uygulamasının bir parçası olarak diğer 5 yöntemi çağıran bir yönteminiz olduğunu varsayalım. Bu yönteme yönelik bir test, adı verilen bu diğer 5 yöntemden birinin sonucu olarak bir davranış meydana geldiğini doğrulamak olabilir. Bu nedenle, bir birim testi sadece bir nedenden dolayı başarısız olması ve sadece bir nedenden ötürü, bu diğer 4 yöntemi çağırmanın neden olduğu olası sorunları ortadan kaldırmak ve onları alay etmek istiyorsunuz. Harika! Birim testi gerçekleştirilir, alay edilmiş yöntemler yoksayılır (ve davranışları diğer birim testlerinin bir parçası olarak onaylanabilir) ve doğrulama çalışır.

Ancak yeni bir sorun var - ünite testi, davranışın ve imzadaki herhangi bir değişikliğin gelecekteki diğer 4 yöntemden herhangi birinde veya “ana yönteme” eklenmesi gereken yeni yöntemlerin ne olduğunu onayladığınız konusunda kesin bir bilgiye sahip olacaktır. olası arızaları önlemek için ünite testini değiştirmek zorunda kalın.

Doğal olarak, sorun biraz daha az davranışla sonuçlanan daha fazla yönteme sahip olarak azaltılabilirdi, ancak belki de daha şık bir çözüm bulunabileceğini umuyordum.

İşte problemi yakalayan örnek bir test.

Kısa bir not olarak 'MergeTests' test ettiğimiz sınıftan miras kalan ve gerektiğinde davranışı geçersiz kılan ünite test sınıfıdır. Bu, dış sınıflara / bağımlılıklara yapılan çağrıları geçersiz kılmamıza izin vermek için testlerimizde kullandığımız bir 'kalıptır'.

[TestMethod]
public void VerifyMergeStopsSpinner()
{
    var mockViewModel = new Mock<MergeTests> { CallBase = true };
    var mockMergeInfo = new MergeInfo(Mock.Of<IClaim>(), Mock.Of<IClaim>(), It.IsAny<bool>());

    mockViewModel.Setup(m => m.ClaimView).Returns(Mock.Of<IClaimView>);
    mockViewModel.Setup(
        m =>
        m.TryMergeClaims(It.IsAny<Func<bool>>(), It.IsAny<IClaim>(), It.IsAny<IClaim>(), It.IsAny<bool>(),
                         It.IsAny<bool>()));
    mockViewModel.Setup(m => m.GetSourceClaimAndTargetClaimByMergeState(It.IsAny<MergeState>())).Returns(mockMergeInfo);
    mockViewModel.Setup(m => m.SwitchToOverviewTab());
    mockViewModel.Setup(m => m.IncrementSaveRequiredNotification());
    mockViewModel.Setup(m => m.OnValidateAndSaveAll(It.IsAny<object>()));
    mockViewModel.Setup(m => m.ProcessPendingActions(It.IsAny<string>()));

    mockViewModel.Object.OnMerge(It.IsAny<MergeState>());    

    mockViewModel.Verify(mvm => mvm.StopSpinner(), Times.Once());
}

Geri kalanınız bununla nasıl başa çıktı ya da bununla baş etmenin harika bir 'basit' yolu yok mu?

Güncelleme - Herkesin geri bildirimini takdir ediyorum. Ne yazık ki, bu gerçekten de sürpriz değil, eğer test edilen kod zayıfsa, birim testinde izleyebileceğiniz harika bir çözüm, kalıp veya pratik görünmüyor. Bu basit gerçeği en iyi yakalayan cevabı işaretledim.


Vay canına, sadece sahte kurulumu görüyorum, SUT örneği yok ya da başka bir şey göremiyorum, buradaki gerçek uygulamaları test ediyor musunuz? StopSpinner'ı kim arayacak? OnMerge? Söyleyebileceği herhangi bir bağımlılıkla alay etmelisin ama kendisinin değil ..
Joppe

Bunu görmek biraz zor ama Mock <MergeTests> SUT. CallBase bayrağını 'OnMerge' yönteminin asıl nesnede çalışmasını sağlamak için koyarız, ancak 'OnMerge' adı verilen ve bağımlılık sorunları nedeniyle testin başarısız olmasına neden olacak yöntemleri alay ederiz. Testin amacı son satırdır. - bu durumda, iplikçiyi durdurduğumuzu doğrulamak için.
PremiumTier

MergeTests, üretimde yaşayan bir karışıklıktan ziyade başka bir enstrümanlı sınıf gibi ses çıkarır.
Joppe


1
Diğer sorunlarınızın yanı sıra, SUT'unuzun bir Sahte <MergeTests> olması bana yanlış geliyor. Neden bir Mock test ettin? Neden MergeTest sınıfının kendisini test etmiyorsunuz?
Eric King,

Yanıtlar:


18
  1. Daha iyi tasarlanmış olması için kodu düzeltin. Testlerinizde bu sorunlar varsa, bir şeyleri değiştirmeye çalıştığınızda kodunuz daha kötü sorunlara sahip olacaktır.

  2. Yapamazsan, o zaman belki daha az ideal olmalısın. Yöntemin öncesi ve sonrası koşullarına karşı test edin. Diğer 5 yöntemi kullanıyorsanız kimin umurunda? Muhtemelen testlerde başarısızlığa neden olanın nedenini açıkça belirten kendi birim testlerine sahipler.

"birim testlerinin başarısız olmasının tek bir nedeni olmalı" iyi bir rehber, ancak benim tecrübeme göre pratik değil. Testleri yazmak zor yazılmaz. Kırılgan testlere inanılmıyor.


Kodun tasarımının düzeltilmesine tamamen katılıyorum, ancak sıkı zaman çizelgeleri olan büyük bir şirket için daha az gelişmekte olan ideal dünyada, geçmiş ekiplerin ya da kötü kararların verdiği teknik borcun 'ödenmesini' sağlamak zor olabilir. bir Zamanlar. İkinci noktaya kadar, alayın çoğu, sadece testin bir nedenden dolayı başarısız olmasını istediğimiz için değil - bu, yürütülmekte olan kodun, ilk önce bu kod içinde yaratılan çok sayıda bağımlılığı kullanmadan yürütülmesine izin vermemesi nedeniyledir. . Buradaki hedef yazılarını taşıdığım için üzgünüm.
PremiumTier

Daha iyi bir tasarım gerçekçi değilse, 'Diğer 5 yöntemi kullanıyorsanız kimin umurunda?' Yöntemin, bunu nasıl yaptığını değil gerekli işlevi gerçekleştirdiğini doğrulayın.
Kwebble

@Kwebble - Anlaşıldı, ancak sorunun amacı, testi çalıştırmak için yöntemde adı geçen diğer davranışlarla da uğraşmanız gerektiğinde, bir yöntem için davranışı doğrulamak için basit bir yol olup olmadığını belirlemekti. 'Nasıl' kaldırmak istiyorum ama nasıl yapılacağını bilmiyorum :)
PremiumTier

Sihirli gümüş mermi yok. Zayıf kodu test etmenin "basit bir yolu" yok. Ya test edilen kodun yeniden gözden geçirilmesi gerekiyor ya da test kodunun kendisi de zayıf olacaktır. İç ayrıntılara fazla spesifik olacak çünkü girmek, ya da gibi Ya testi, fakir olacak btilly önerilen bir çalışma ortamı karşı testler, ancak daha sonra testler çok daha yavaş ve daha karmaşık olacaktır. Her iki durumda da, testler yazmak daha zor, sürdürmek daha zor ve yanlış negatiflere eğilimli olacaktır.
Steven Doggart

8

Büyük yöntemleri daha odaklı küçük yöntemlere bölmek kesinlikle en iyi yöntemdir. Birim test davranışını doğrulamada acı olarak görüyorsunuz, ancak acıyı başka şekillerde de yaşıyorsunuz.

Bu bir sapkınlık olduğunu söyledi ama ben şahsen gerçekçi geçici test ortamları yaratma hayranıyım. Diğer bir deyişle, bu diğer yöntemlerin içine gizlenmiş olan her şeyi alay etmek yerine, tüm bunları çalıştırmanıza olanak tanıyan geçici bir ortam (özel veritabanları ve şemalar - SQLite burada yardımcı olabilir) kurulduğundan emin olun. Test ortamının nasıl oluşturulacağını / parçalanacağını bilme sorumluluğu, onu gerektiren kodla birlikte yaşar, böylece değiştiğinde varlığına bağlı olarak tüm birim test kodunu değiştirmek zorunda kalmazsınız.

Ama bunun benim açımdan bir sapkınlık olduğunu not ediyorum. Ağırlıklı olarak birim testine giren kişiler "saf" birim testlerini savunuyor ve benim "entegrasyon testleri" olarak adlandırdıklarını söylüyorlar. Şahsen bu ayrım için endişelenmiyorum.


3

Sahtekarlıkları hafifletmeyi düşünür ve sadece çağırdığı yöntemleri içerebilecek testleri formüle ederim.

Test etmeyin how test neyi . Önemli olan, gerektiğinde alt yöntemleri içermesidir.

Başka bir açıdan, bir test formüle edebilir, büyük bir yöntemle, refactor ile geçmesini ve yeniden yapılanmadan sonra bir yöntemler ağacını oluşturmasını sağlayabilirsiniz. Her birini ayrı ayrı test etmeniz gerekmez. Önemli olan son sonuç.

Eğer alt yöntemler bazı yönleri test etmeyi zorlaştırıyorsa, onları ayrı sınıflara ayırmayı düşünün, böylece sınıfınız ağır şekilde uygulanmış / dikişsiz olarak test edilmeden onları daha temiz bir şekilde alay edebilirsiniz. Örnek testinizde somut bir uygulamayı gerçekten test edip etmediğinizi söylemek zor.


Sorun şu ki, 'neyi' test etmek için 'nasıl' alay etmemiz gerekiyor. Kodun tasarımının getirdiği bir sınırlamadır. Testin kırılgan hale gelmesinin bu şekilde nasıl 'alay etmesini' istediğim kesinlikle yok.
PremiumTier

Yöntem adlarına baktığımda, test edilen sınıfınızın sadece çok fazla sorumluluk aldığını düşünüyorum. Tek sorumluluk ilkesi hakkında bilgi edinin. MVC'den borç almak biraz yardımcı olabilir, sınıfınız hem UI, hem altyapı hem de işle ilgili endişeleri ele alıyor gibi görünüyor.
Joppe

Evet :( Bahsettiğim kötü tasarlanmış eski kod bu olurdu. Yeniden tasarım ve refactor üzerinde çalışıyoruz, ancak ilk önce kaynağı test etmenin en iyisi olacağını düşündük.
PremiumTier
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.