Test edilmiş sınıfta casusluk yapmak kötü bir uygulama mıdır?


14

Sınıf içi çağrıların olağan olduğu bir proje üzerinde çalışıyorum ancak sonuçlar birçok kez basit değerlerdir. Örnek ( gerçek kod değil ):

public boolean findError(Set<Thing1> set1, Set<Thing2> set2) {
  if (!checkFirstCondition(set1, set2)) {
    return false;
  }
  if (!checkSecondCondition(set1, set2)) {
    return false;
  }
  return true;
}

Sadece kondisyon sistemini test etmek istediğim için bu tür kodlar için birim testleri yazmak gerçekten zor, gerçek koşulların uygulanmasını değil. (Bunu ayrı testlerde yapıyorum.) Aslında koşulları uygulayan fonksiyonlardan geçersem daha iyi olurdu ve testlerde sadece alay ediyorum. Bu yaklaşımla ilgili sorun gürültülüdür: jenerikleri çok kullanıyoruz .

Çalışan bir çözüm; ancak, test edilen nesneyi bir casus yapmak ve dahili işlevlere yapılan çağrıları alay etmektir.

systemUnderTest = Mockito.spy(systemUnderTest);
doReturn(true).when(systemUnderTest).checkFirstCondition(....);

Buradaki endişe, SUT'un uygulamasının etkili bir şekilde değiştirilmiş olması ve testlerin uygulama ile senkronize tutulmasının sorunlu olabileceğidir. Bu doğru mu? Dahili yöntem çağrılarının bu tahribatından kaçınmak için en iyi uygulama var mı?

Bir algoritmanın bölümleri hakkında konuştuğumuzu unutmayın, bu yüzden birkaç sınıfa ayırmak istenen bir karar olmayabilir.

Yanıtlar:


15

Birim testleri, test ettikleri sınıfları kara kutu olarak ele almalıdır. Önemli olan tek şey, kamusal yöntemlerinin beklendiği gibi davranmasıdır. Sınıfın bunu iç devlet ve özel yöntemlerle nasıl başardığı önemli değildir.

Bu şekilde anlamlı testler yapmanın imkansız olduğunu düşünüyorsanız, sınıflarınızın çok güçlü ve çok fazla şey yaptığının bir işaretidir. İşlevlerinden bazılarını ayrı ayrı test edilebilen ayrı sınıflara taşımayı düşünmelisiniz.


1
Uzun zaman önce birim testi fikrini anladım ve bir demet başarıyla yazdım. Sadece bir şeyin kağıt üzerinde basit göründüğünü aldatmak, kodda daha kötü görünüyor ve sonunda gerçekten basit bir arayüze sahip bir şeyle karşı karşıya kaldım, ancak girişlerin etrafında dünyanın yarısını alay etmemi gerektiriyor.
allprog

@allprog Çok fazla alay yapmanız gerektiğinde, sınıflarınız arasında çok fazla bağımlılığınız var gibi görünüyor. Aralarındaki bağlantıyı azaltmaya çalıştınız mı?
Philipp

@allprog Bu durumda iseniz, sınıf tasarımı suçlamaktır.
itsbruce

Baş ağrısına neden olan veri modelidir. ORM kurallarına ve diğer birçok koşula uymak zorundadır. Saf iş mantığı ve durum bilgisi olmayan kodla, birim testlerini doğru yapmak çok daha kolaydır.
allprog

3
Birim testlerinin mutlaka SUT'u backbox olarak ele alması gerekmez. Bu yüzden birim testleri denir. Bağımlılıkları alay ederek çevreyi etkileyebilirim ve alay etmem gereken şeyleri bilmek için içsel bazılarını da bilmeliyim. Ancak bu elbette SUT'un herhangi bir şekilde değiştirilmesi gerektiği anlamına gelmez. Ancak casusluk bazı değişikliklere izin verir.
allprog

4

Her iki takdirde findError()ve checkFirstCondition()vb sınıfının kamu yöntemlerdir, sonra findError()zaten aynı API edinilebilir işlevsellik için bir cephe etkin şekilde olduğunu. Bunda yanlış bir şey yok, ama zaten mevcut testlere çok benzeyen testler yazmanız gerektiği anlamına geliyor. Bu çoğaltma, genel arabiriminizdeki çoğaltmayı yansıtır. Bu yöntemi diğerlerinden farklı bir şekilde tedavi etmek için bir neden yoktur.


Dahili yöntemler, test edilebilir olmaları gerektiğinden herkese açık hale getirilir ve SUT'u alt sınıflara ayırmak veya SUT sınıfındaki birim testlerini statik bir iç sınıf olarak dahil etmek istemiyorum. Ama anladım. Ancak, bu tür durumlardan kaçınmak için iyi rehber hatları bulamadım. Öğreticiler her zaman gerçek yazılımla hiçbir ilgisi olmayan temel düzeyde kalmıştır. Aksi takdirde, casusluk sebebi test kodunun tekrarlanmasından kaçınmak ve test ünitesini kapsamasını sağlamaktır.
allprog

3
Uygun birim testi için yardımcı yöntemlerin halka açık olması gerektiğine katılmıyorum. Bir yöntemin sözleşmesi, çeşitli koşulları kontrol ettiğini belirtirse, aynı genel yönteme karşı, her "alt sözleşme" için bir tane olmak üzere birkaç test yazmanın yanlış bir yanı yoktur. Birim testlerin amacı, 1: 1 yöntem testi yazışması yoluyla genel yöntemlerin yüzeysel bir kapsamını elde etmek değil, tüm kodların kapsamını elde etmektir.
Kilian Foth

Test için yalnızca genel API'yi kullanmak, dahili parçaları tek tek test etmekten çok daha karmaşıktır. Tartışmıyorum, bu yaklaşımın en iyisi olmadığını ve sorumun gösterdiği kendi bakış açısına sahip olduğunu düşünüyorum. En büyük sorun, işlevlerin Java'da oluşturulamaması ve geçici çözümlerin son derece kısa olmasıdır. Ancak gerçek birim testi için başka bir çözüm yok gibi görünüyor.
allprog

4

Birim testleri sözleşmeyi test etmelidir; onlar için tek önemli şey bu. Sözleşmenin bir parçası olmayan her şeyi test etmek sadece zaman kaybı değildir, aynı zamanda potansiyel bir hata kaynağıdır. Bir geliştiricinin, bir uygulama ayrıntısını değiştirdiğinde testleri değiştirdiğini her gördüğünüzde, alarm zillerinin çalması gerekir; geliştirici (kasıtlı olsun olmasın) hatalarını gizliyor olabilir. Uygulama ayrıntılarını kasıtlı olarak test etmek, bu kötü alışkanlığı zorlayarak hataların maskelenme olasılığını artırır.

Dahili aramalar bir uygulama detayıdır ve yalnızca performansın ölçülmesiyle ilgilenmelidir . Bu genellikle birim testlerin işi değildir.


Harika görünüyor. Ama gerçekte, "string" yazmam ve kodu çağırmam gerekiyor, fonksiyonlar hakkında çok az şey bilen bir dilde. Teorik olarak bir problemi kolayca tanımlayabilir ve basitleştirmek için burada ve orada ikameler yapabilirim. Kodda beni kullanmaktan alıkoyan bu esnekliği elde etmek için çok sözdizimsel gürültü eklemek zorunda. Yöntem aynı sınıfta bir ayönteme çağrı içeriyorsa , testlerinin . Ve parametre olarak geçmediği sürece bunu değiştirmenin bir yolu yok ama görüyorum ki başka bir çözüm yok. babba
allprog

1
Eğer bkamu arayüzünün bir parçasıdır, yine de test edilmelidir. Değilse, test edilmesine gerek yoktur. Test etmek istediğiniz için herkese açık hale getirdiyseniz, yanlış yaptınız.
itsbruce

@ Philip'in cevabı hakkındaki yorumuma bakın. Henüz bahsetmedim ama veri modeli kötülüğün kaynağı. Saf, vatansız kod çocuk oyuncağı.
allprog

2

İlk olarak, yazdığınız örnek işlev hakkında test etmenin zor olduğunu merak ediyorum. Görebildiğim kadarıyla, sadece çeşitli girişleri geçebilir ve doğru boolean değerinin döndürüldüğünden emin olabilirsiniz. Neyi kaçırıyorum?

Casuslara gelince, casus ve alay kullanan "beyaz kutu" denilen tür, sadece yazmak için çok daha fazla test kodu olduğu için değil, uygulamanın her zaman değiştiğinde, testleri de değiştirmeniz gerekir (arayüz aynı kalsa bile). Ve bu tür testler kara kutu testinden daha az güvenilirdir, çünkü tüm bu ekstra test kodunun doğru olduğundan emin olmanız gerekir ve kara kutu birim testlerinin arayüzle eşleşmezlerse başarısız olacağına güvenebilirsiniz. , kod aşırı kullanım alayları konusunda buna güvenemezsiniz çünkü bazen test çok fazla gerçek kodu test etmez - sadece alay eder. Alaycılar yanlışsa, olasılıklar testlerinizin başarılı olması, ancak kodunuzun hala bozuk olmasıdır.

Beyaz kutu testi ile deneyimi olan herkes, yazmak ve korumak için kıçlarında bir ağrı olduğunu söyleyebilir. Daha az güvenilir olmaları gerçeğiyle birlikte, beyaz kutu testi çoğu durumda çok daha düşüktür.


Not için teşekkürler. Örnek işlev, karmaşık bir algoritmaya yazmak zorunda olduğunuz her şeyden daha basit büyüklük sıralarıdır. Aslında, soru daha çok benziyor: çeşitli bölümlerde casuslarla algoritmaları test etmek sorunlu mu? Bu durum bilgisi olan bir kod değildir, tüm durum giriş argümanlarına ayrılmıştır. Sorun, alt fonksiyonlar için aklı başında parametreler sağlamak zorunda kalmadan örnekte karmaşık fonksiyonu test etmek istiyorum.
allprog

Java 8'deki fonksiyonel programlamanın başlangıcı ile bu biraz daha zarif hale geldi, ancak yine de işlevselliği tek bir sınıfta tutmak, farklı (tek başına kullanışlı değil) parçaları "bir kez kullan" haline getirmek yerine algoritmalarda daha iyi bir seçim olabilir. sadece test edilebilirlik nedeniyle sınıflar. Bu açıdan, casuslar alaylarla aynı şeyi yaparlar, ancak tutarlı kodu görsel olarak patlatmak zorunda kalmadan. Aslında, alaylarla aynı kurulum kodu kullanılır. Aşırı uçlardan uzak durmayı seviyorum, her tür test belirli yerlerde uygun olabilir. Bir şekilde test etmek, hiç olmamasından çok daha iyi. :)
allprog

"Ben karmaşık fonksiyonu test etmek istiyorum .. alt fonksiyonları için aklı başında parametreleri sağlamak zorunda kalmadan" - orada ne demek istediğini anlamıyorum. Hangi alt fonksiyonlar? 'Karmaşık fonksiyon' tarafından kullanılan dahili fonksiyonlardan mı bahsediyorsunuz?
BT

Benim durumumda casusluk işe yarar. İç fonksiyonların kontrolü oldukça karmaşıktır. Kod yüzünden değil, mantıksal olarak karmaşık bir şey uyguladıkları için. Farklı bir sınıftaki şeyleri taşımak doğal bir seçenektir, ancak bu işlevler tek başına yararlı değildir. Bu nedenle, sınıfı bir arada tutmak ve casus işlevselliği ile kontrol etmek daha iyi bir seçenek haline geldi. Neredeyse bir yıl boyunca kusursuz bir şekilde çalıştı ve model değişikliklerine kolayca dayanabildi. O zamandan beri bu kalıbı kullanmadım, sadece bazı durumlarda geçerli olduğunu belirtmenin iyi olduğunu düşündüm.
allprog

@allprog "mantıksal olarak karmaşık" - Karmaşıksa, karmaşık testlere ihtiyacınız vardır. Bunun hiçbir yolu yok. Casuslar sizin için daha zor ve karmaşık hale getirecek. Başka bir işlev içindeki özel davranışlarını test etmek için casus kullanmak yerine, kendi başlarına test edebileceğiniz anlaşılabilir alt işlevler oluşturmalısınız.
BT
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.