TDD'de yeniden tasarlandıktan sonra bu yöntem özel olduğunda yöntemlerin testlerinde ne olur?


29

Diyelim ki diğer karakterlere ve bu tür şeylere saldıran karakterlerle bir rol oyunu geliştirmeye başladım.

TDD'yi uygulayarak, mantık içinde Character.receiveAttack(Int)yöntemini test etmek için bazı test durumları yapmak . Bunun gibi bir şey:

@Test
fun healthIsReducedWhenCharacterIsAttacked() {
    val c = Character(100) //arg is the health
    c.receiveAttack(50) //arg is the suffered attack damage
    assertThat(c.health, is(50));
}

Diyelim ki 10 yöntem test receiveAttackyöntemim var. Şimdi bir yöntem ekliyorum Character.attack(Character)( yöntem çağırıyor receiveAttack) ve bazı TDD döngüleri test ettikten sonra bir karar vereceğim: Character.receiveAttack(Int)olmalıdır private.

Önceki 10 test vakasında ne olur? Onları silmeli miyim? Yöntemi saklamalı mıyım public(sanmıyorum)?

Bu soru, özel yöntemlerin nasıl test edileceği ile ilgili değildir, ancak TDD uygulanırken yeniden tasarlandıktan sonra bunlarla nasıl başa çıkılacağıyla ilgilidir



10
Özelse, test etmiyorsun, o kadar kolay. Çıkarın ve yapmak refactor dansı
Kayess

6
Muhtemelen burdaki tahılı kullanıyorum. Ancak, genellikle ne pahasına olursa olsun özel yöntemlerden kaçınırım. Daha az testten daha çok test tercih ederim. İnsanların ne düşündüğünü biliyorum "Ne yani, tüketiciye maruz bırakmak istemediğiniz hiçbir işlevselliğe sahip değilsiniz?" Evet, açığa vurmak istemediğim bir sürü şey var. Bunun yerine, özel bir yöntemim varken bunun yerine kendi sınıfını yeniden alıştırırım ve belirtilen sınıfı orijinal sınıftan kullanırım. Yeni sınıf, internalaçığa çıkmasını önlemek için dilinizle eşdeğer olarak işaretlenebilir . Aslında Kevin Cline'ın cevabı bu tür bir yaklaşımdır.
user9993

3
@ user9993 geriye doğru sahip gibi görünüyor. Daha fazla test yaptırmanız sizin için önemliyse, önemli bir şeyi kaçırmadığınızdan emin olmanın tek yolu kapsama alanı analizi yapmaktır. Kapsama araçları için, yöntemin özel veya kamu veya başka bir şekilde olması önemli değildir. Halka açıklanmanın bir şekilde kapsama alanı analizinin eksikliğini telafi edeceğini ummak, yanlış bir güvenlik hissi veriyor
gnat

2
@gnat Ama "kapsama alanı olmaması" hakkında hiçbir şey söylemedim? "Daha az testten daha fazla test tercih ediyorum" hakkındaki yorumum bunu açıkça ortaya koymalıydı. Tam olarak ne elde ettiğinden emin değilim, tabii ki çıkardığım kodu da test ediyorum. Bütün mesele bu.
kullanici9993

Yanıtlar:


52

TDD'de, testler tasarımınızın çalıştırılabilir dokümantasyonu olarak hizmet eder. Tasarımınız değişti, bu yüzden de belgeniz de öyle olmalı!

TDD'de, attackyöntemin ortaya çıkmasının tek yolunun, başarısız bir test geçişi yapmasının sonucudur. Bu, attackbaşka bir test tarafından test edildiği anlamına gelir . Bunun anlamı dolaylı receiveAttack olarak attacktestlerin kapsamıdır. İdeal olarak, yapılacak değişikliklerin testlerden receiveAttacken az birini kırması gerekir attack.

Ve değilse, o zaman receiveAttackartık gerekli olmayan ve artık olmaması gereken bir işlevsellik var!

Bu nedenle, receiveAttackzaten test edildiğinden attack, testlerinizi yapıp yapmamanız önemli değil. Eğer sizin test çerçevesi kolaylaştırır özel yöntemler test etmek ve eğer sen özel yöntemler test etmeye karar, o zaman tutabilirsiniz. Ancak, test kapsamını ve güvenini kaybetmeden de silebilirsiniz.


14
Bu iyi bir cevaptır, "Test çerçeveniz özel yöntemleri test etmeyi kolaylaştırıyorsa ve özel yöntemleri test etmeye karar verirseniz, bunları saklayabilirsiniz." Özel yöntemler uygulama detaylarıdır ve asla doğrudan test edilmemelidir.
David Arno

20
@DavidArno: Katılıyorum, bu nedenle bir modülün iç kısımları hiçbir zaman test edilmemelidir. Bununla birlikte, bir modülün iç kısımları çok karmaşık olabilir ve bu nedenle her bir iç işlevsellik için birim testlerinin yapılması değerli olabilir. Birim testler, bir işlevsellik değişmezlerini kontrol etmek için kullanılır , eğer özel bir yöntem değişmezler içeriyorsa (ön koşullar / post-koşullar) bir birim testi değerli olabilir.
Matthieu M.

8
" Bu nedenle bir modülün içindekileri asla test edilmemelidir ". Bu iç kısımlar asla doğrudan test edilmemelidir . Tüm testler yalnızca genel API’leri test etmelidir. Dahili bir öğeye genel bir API üzerinden erişilemiyorsa, hiçbir şey yapmadığı için silin.
David Arno

28
@DavidArno Bu mantıkla, çalıştırılabilir bir yapı oluşturuyorsanız (bir kütüphane yerine) hiç birim sınaması yapmamalısınız. - "İşlev çağrıları genel API'nin bir parçası değil! Yalnızca komut satırı argümanları! Programınızın dahili bir işlevine bir komut satırı argümanı aracılığıyla erişilemiyorsa, hiçbir şey yapmadığından silin." - Özel işlevler sınıfın genel API'sinin bir parçası olmasa da, sınıfın iç API'sinin bir parçasıdır. Ve bir sınıfın iç API'sini test etmeniz gerekmez, ancak yürütülebilir bir iç API'yi test etmek için aynı mantığı kullanabilirsiniz.
RM

7
@RM, eğer ben olmayan bir modüler biçimde yürütülebilir oluşturmak idi, o zaman yürütülebilir kullanarak entegrasyon testleri içlerinin kırılgan testler arasında seçerek veya yalnızca kullanarak zorla ve I / O'yu runtime olacaktır. Bu yüzden asıl mantığımla, strawman versiyonunuz yerine, onu modüler bir şekilde (örneğin, bir dizi kütüphane aracılığıyla) oluşturacağım. Bu modüllerin genel API'leri daha sonra kırılgan olmayan bir şekilde test edilebilir.
David Arno

23

Yöntem teste ihtiyaç duyacak kadar karmaşıksa, bazı sınıflarda genel olmalıdır. Demek siz refactor:

public class X {
  private int complexity(...) {
    ...
  }
  public void somethingElse() {
    int c = complexity(...);
  }
}

için:

public class Complexity {
  public int calculate(...) {
    ...
  }
}

public class X {
  private Complexity complexity;
  public X(Complexity complexity) { // dependency injection happiness
    this.complexity = complexity;
  }

  public void something() {
    int c = complexity.calculate(...);
  }
}

X.complexity için geçerli testi ComplexityTest'e taşıyın. Ardından, karmaşıklıkla alay ederek bir şeyler yaz.

Tecrübelerime göre, daha küçük sınıflara doğru yeniden yapılanma ve daha kısa yöntemler çok büyük faydalar sağlıyor. Anlaşmaları daha kolay, test etmek daha kolay ve sonuçta beklenenden daha fazla tekrar kullanılmaya başlandılar.


Cevabınız OP'nin sorusu hakkındaki yorumumda açıklamaya çalıştığım fikri çok daha net bir şekilde açıklıyor. Güzel cevap
kullanıcı9993

3
Cevabınız için teşekkürler. Aslında, almaAttack yöntemi oldukça basittir ( this.health = this.health - attackDamage). Belki başka bir sınıfa çıkartın, bu an için çok ince bir çözümdür.
Héctor

1
Bu, OP için kesinlikle çok büyük bir öneme sahip - mağazaya sürmek istiyor, aya uçmak değil - genel dava için iyi bir çözüm.

Eğer fonksiyon bu kadar basitse, belki de en başta bir fonksiyon olarak tanımlanması bile zorlayıcıdır.
David K,

1
Bugün fazla abartılmış olabilir, ancak 6 aylık sürede bu kodda bir ton değişiklik olduğu zaman faydalar açık olacaktır. Ve iyi bir IDE'de bu günlerde kesinlikle bazı kodların ayrı bir sınıfa çıkarılması, çalışma zamanı ikili kodunda aynı şekilde kaynatacağını düşünerek, pek de fazla zorlanmayan bir çözümde birkaç tuşa basma olmalıdır.
Stephen Byrne

6

Diyelim ki 10 yöntem metot alıp test ettim. Şimdi, bir yöntem Character.attack (Character) (almaAttack yöntemini çağıran) ekliyorum ve bazı TDD döngüleri test ettikten sonra bir karar verdim: Character.receiveAttack (Int) özel olmalı.

Burada akılda tutulması gereken bir şey, verdiğiniz kararın API'den bir yöntemi kaldırmak olduğudur . Geriye dönük uyumluluk nezaketleri

  1. Kaldırmanız gerekmiyorsa, API’de bırakın.
  2. Henüz çıkarmanız gerekmiyorsa , kullanımdan kaldırılacağı zaman mümkünse işaretleyin ve mümkünse belgeyi işaretleyin.
  3. Kaldırmanız gerekirse, büyük bir sürüm değişikliğine sahipsiniz.

API'niz artık yöntemi desteklemediğinde, testler kaldırıldı veya değiştirildi. Bu noktada, özel yöntem yeniden gözden geçirmeniz gereken bir uygulama detayıdır.

Bu noktada, test takımınızın uygulamalara doğrudan erişip erişmediği, tamamen genel API üzerinden etkileşimde bulunup bulunmadığı konusunda standart soruya geri döndünüz. Özel bir yöntem, test odasına girmeden değiştirmemiz gereken bir şeydir . Bu yüzden testlerden çiftin uzaklara gitmesini beklerdim - ya emekli oluyorlar ya da uygulamayla birlikte test edilebilir bir bileşene geçiyorlardı.


3
İtiraz her zaman bir endişe değildir. "Henüz geliştirmeye başlayalım diyelim ..." sorusundan, eğer yazılım henüz piyasaya sürülmediyse itiraz etmek sorun değil. Ayrıca: "rol oyunu", bunun yeniden kullanılabilir bir kütüphane olmadığını , ancak son kullanıcıları hedef alan ikili bir yazılım olduğunu belirtir. Bazı son kullanıcı yazılımlarının ortak bir API'si olsa da (örneğin, MS Office), çoğu yoktur. Hatta yazılım yapar bir kamu API tek bir eklentileri, komut dosyası için maruz bunun kısmını (LUA uzantılı örneğin oyunlar), ya da diğer işlevlere sahiptir. Yine de, OP'nin tanımladığı genel durum fikrini ortaya koymaya değer.
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.