Birçok permütasyona sahip bir şey için TDD nasıl yapılır?


15

AI gibi çok hızlı bir şekilde çok farklı yollar alabilen bir sistem veya gerçekten birkaç farklı girişi olan herhangi bir algoritma oluştururken, olası sonuç kümesi çok sayıda permütasyon içerebilir.

Çok sayıda farklı sonuç permütasyonu veren bir sistem oluştururken TDD'yi kullanmak için ne yaklaşım gerekir?


1
AI sisteminin genel iyiliği genellikle bir kıyaslama girdi seti ile Precision-Recall testi ile ölçülür. Bu test kabaca "entegrasyon testleri" ile eşittir. Diğerlerinin de belirttiği gibi, daha çok "test odaklı tasarım " yerine "test odaklı algoritma araştırması " gibidir.
rwong

Lütfen "AI" ile ne demek istediğinizi tanımlayın. Herhangi bir program türünden daha fazla bir çalışma alanıdır. Belirli AI uygulamaları için, genellikle TDD yoluyla bazı şeyleri (örn: ortaya çıkan davranış) test edemezsiniz.
Steven Evers

@SnOrfus Bunu en genel, ilkel anlamda, bir karar verme makinesi olarak kastediyorum.
Nicole

Yanıtlar:


7

PDR'nin cevabına daha pratik bir yaklaşım getirmek . TDD tamamen testten ziyade yazılım tasarımı ile ilgilidir. İşinizi ilerlerken doğrulamak için birim testleri kullanırsınız.

Bu yüzden, bir birim test seviyesinde, birimleri tamamen belirleyici bir şekilde test edilebilecek şekilde tasarlamanız gerekir. Bunu, üniteyi belirsiz kılan (rasgele sayı üreteci gibi) yapan herhangi bir şeyi alıp soyutlandırarak yapabilirsiniz. Diyelim ki bir hamlenin iyi olup olmadığına karar veren saf bir yöntemimiz var:

class Decider {

  public boolean decide(float input, float risk) {

      float inputRand = Math.random();
      if (inputRand > input) {
         float riskRand = Math.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider();
d.decide(0.1337f, 0.1337f);

Bu yöntemi test etmek çok zordur ve birim testlerde gerçekten doğrulayabileceğiniz tek şey sınırlarıdır ... ancak bu sınırlara ulaşmak için çok fazla deneme gerektirir. Bunun yerine, bir arabirim ve işlevselliği saran somut bir sınıf oluşturarak rastgele parçayı soyutlayalım:

public interface IRandom {

   public float random();

}

public class ConcreteRandom implements IRandom {

   public float random() {
      return Math.random();
   }

}

DeciderSınıf artık yani Arayüz onun soyutlama yoluyla somut sınıfını kullanması gerekir. Bu şekilde yapmanın bağımlılık enjeksiyonu denir (aşağıdaki örnek yapıcı enjeksiyonunun bir örneğidir, ancak bunu bir ayarlayıcı ile de yapabilirsiniz):

class Decider {

  IRandom irandom;

  public Decider(IRandom irandom) { // constructor injection
      this.irandom = irandom;
  }

  public boolean decide(float input, float risk) {

      float inputRand = irandom.random();
      if (inputRand > input) {
         float riskRand = irandom.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider(new ConcreteRandom);
d.decide(0.1337f, 0.1337f);

Bu kod kodunun neden gerekli olduğunu kendinize sorabilirsiniz. Peki, yeni başlayanlar için, şimdi s "kontratını" Decidertakip eden bir bağımlılığa sahip olduğundan, algoritmanın rastgele bölümünün davranışını alay edebilirsiniz IRandom. Bunun için alaycı bir çerçeve kullanabilirsiniz, ancak bu örnek kendinizi kodlamak için yeterince basittir:

class MockedRandom() implements IRandom {

    public List<Float> floats = new ArrayList<Float>();
    int pos;

   public void addFloat(float f) {
     floats.add(f);
   }

   public float random() {
      float out = floats.get(pos);
      if (pos != floats.size()) {
         pos++;
      }
      return out;
   }

}

En iyi yanı, bunun "gerçek" somut uygulamanın tamamen yerini alabilmesidir. Kodun test edilmesi kolaylaşır:

@Before void setUp() {
  MockedRandom mRandom = new MockedRandom();

  Decider decider = new Decider(mRandom);
}

@Test
public void testDecisionWithLowInput_ShouldGiveFalse() {

  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandButLowRiskRand_ShouldGiveFalse() {

  mRandom.addFloat(1f);
  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandAndHighRiskRand_ShouldGiveTrue() {

  mRandom.addFloat(1f);
  mRandom.addFloat(1f);

  assertTrue(decider.decide(0.1337f, 0.1337f));
}

Umarım bu, uygulamanızı nasıl tasarlayacağınız konusunda fikir verir, böylece permütasyonlar zorlanabilir, böylece tüm uç durumları ve neyi test edemezsiniz.


3

Sıkı TDD, daha karmaşık sistemler için biraz parçalanma eğilimindedir, ancak pratik açıdan çok önemli değildir - tek tek girdileri izole edemedikten sonra, makul kapsam sağlayan ve bunları kullanan bazı test senaryoları seçin.

Bu, uygulamanın iyi ne yapacağına dair biraz bilgi gerektirir, ancak bu daha teorik bir sorundur - teknik olmayan kullanıcılar tarafından ayrıntılı olarak belirtilen bir yapay zeka inşa etme ihtimaliniz düşüktür. Test kodlarını test senaryolarına zorlayarak geçirme ile aynı kategoride - resmi olarak test spesifikasyon ve uygulama hem doğru hem de mümkün olan en hızlı çözümdür, ancak aslında asla gerçekleşmez.


2

TDD testle değil tasarımla ilgilidir.

Karmaşıklıktan ayrı düşmekten çok, bu koşullarda mükemmeldir. Sizi daha küçük parçalarda daha büyük bir sorunu düşünmeye itecek, bu da daha iyi bir tasarıma yol açacaktır.

Algoritmanızın her permütasyonunu test etmeye çalışmayın. Sadece testten sonra test oluşturun, testlerin çalışmasını sağlamak için en basit kodu yazın, bazlarınız kaplanana kadar. Sorunu çözmek için ne demek istediğimi görmelisiniz, çünkü diğer parçaları test ederken sorunun bir kısmını taklit etmeye teşvik edilecek ve 10 milyar permütasyon için 10 milyar test yazmak zorunda kalmayacaksınız.

Düzenleme: Bir örnek eklemek istedim, ancak daha önce vaktim yoktu.

Yerinde sıralama algoritmasını ele alalım. Devam edebilir ve dizinin üst ucunu, dizinin alt ucunu ve ortadaki her türlü garip kombinasyonları kapsayan testler yazabiliriz. Her biri için, bir tür nesneden oluşan eksiksiz bir dizi oluşturmak zorundayız. Bu zaman alacaktı.

Veya sorunu dört kısımda çözebiliriz:

  1. Diziyi hareket ettirin.
  2. Seçilen öğeleri karşılaştırın.
  3. Öğeleri değiştirin.
  4. Yukarıdaki üçü koordine edin.

Birincisi, sorunun tek karmaşık kısmıdır, ancak geri kalanından soyutlayarak, çok, çok daha basit hale getirdiniz.

İkincisi neredeyse kesinlikle nesnenin kendisi tarafından ele alınır, en azından isteğe bağlı olarak, birçok statik tip çerçevede, bu işlevselliğin uygulanıp uygulanmadığını gösteren bir arayüz olacaktır. Yani bunu test etmenize gerek yok.

Üçüncüsü test etmek inanılmaz derecede kolaydır.

Dördüncü sadece iki işaretçi ele alır, geçiş sınıfından işaretçileri hareket ettirmesini ister, bir karşılaştırma ister ve bu karşılaştırmanın sonucuna bağlı olarak, değiştirilecek öğeleri çağırır. İlk üç problemi çıkardıysanız, bunu kolayca test edebilirsiniz.

Burada nasıl daha iyi bir tasarıma yol açtık? Diyelim ki basit tuttunuz ve bir kabarcık türü uyguladınız. Çalışıyor, ancak üretime gittiğinizde ve bir milyon nesneyi işlemesi gerektiğinde, çok yavaş. Tek yapmanız gereken yeni geçiş işlevselliği yazmak ve takas etmek. Diğer üç sorunu ele alma karmaşıklığıyla uğraşmak zorunda değilsiniz.

Bu, birim test ve TDD arasındaki fark olduğunu göreceksiniz. Ünite test cihazı bunun testlerinizi kırılgan hale getirdiğini söyleyecektir, eğer basit girişleri ve çıktıları test ettiyseniz, şimdi yeni işlevselliğiniz için daha fazla test yazmak zorunda kalmayacaksınız. TDDer, kaygıları uygun bir şekilde ayırdığımı söyleyecek, böylece sahip olduğum her sınıf bir şey ve bir şey iyi yapıyor.


1

Bir hesaplamanın her bir permütasyonunu birçok değişkenle test etmek mümkün değildir. Ama bu yeni bir şey değil, oyuncak karmaşıklığının üzerindeki herhangi bir program için her zaman doğru olmuştur. Testlerin amacı , hesaplamanın özelliğini doğrulamaktır . Örneğin, bir listeyi 1000 numarayla sıralamak biraz çaba gerektirir, ancak herhangi bir çözüm çok kolay bir şekilde doğrulanabilir. Şimdi, 1000 olmasına rağmen! o progam için olası (sınıflar) girişler ve hepsini test edemezsiniz, sadece 1000 girişi rastgele oluşturmak ve çıktının gerçekten sıralandığını doğrulamak tamamen yeterlidir. Neden? Genel olarak doğru olmadan rastgele rastgele oluşturulmuş 1000 vektörü güvenilir bir şekilde sıralayan bir program yazmak neredeyse imkansız olduğu için (belirli sihirli girdileri manipüle etmek için kasıtlı olarak donatmadıkça ...)

Şimdi, genel olarak işler biraz daha karmaşıktır. Gerçekten var olan onlar Cuma adlarıyla birlikte bir 'f' ve haftanın günü varsa mailler kullanıcılara e-posta teslim olmaz hatalar olmuştur. Ama böylesi bir tuhaflığı tahmin etmeye çalışmak için harcanan çabayı düşünüyorum. Test takımınız, sistemin beklediğiniz girdilerde beklediğiniz şeyi yaptığı konusunda size sürekli bir güven sağlamalıdır. Bazı korkak durumlarda korkak şeyler yaparsa, ilk korkak davayı denedikten hemen sonra fark edeceksiniz ve daha sonra bu davaya karşı özel olarak bir test yazabilirsiniz (genellikle benzer vakaların bir sınıfını da kapsayacaktır).


Rastgele 1000 giriş oluşturduğunuzda, çıkışları nasıl test edersiniz? Elbette böyle bir test, kendi içinde test edilmeyen bir mantık içerecektir. Yani testi test ediyorsun? Nasıl? Mesele şu ki, durum geçişlerini kullanarak mantığı test etmelisiniz - X girişi verildiğinde çıktı Y olmalıdır. Mantığı içeren bir test, test ettiği mantık kadar hataya açıktır. Mantıksal olarak, bir argümanı başka bir argümanla gerekçelendirmek sizi şüpheci gerileme yoluna sokar - bazı iddialarda bulunmalısınız . Bu iddialar sizin testlerinizdir.
Izhaki

0

Kenar kasalarını ve bazı rastgele girişleri alın.

Sıralama örneğini almak için:

  • Birkaç rastgele listeyi sıralayın
  • Zaten sıralanmış bir listeyi alın
  • Ters sırada olan bir liste alın
  • Neredeyse sıralanmış bir liste alın

Bunlar için hızlı çalışıyorsa, tüm girdiler için çalışacağından emin olabilirsiniz.

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.