Sayılar bir şey ifade etmiyorsa, birim testlerinde sihirli sayılar kabul edilebilir mi?


59

Birim testlerimde, ne yaptığını görmek için koduma rastgele değerler atarım. Örneğin, bunun foo(1, 2, 3)17'ye dönmesi gerektiğini biliyorsanız , şunu yazabilirim:

assertEqual(foo(1, 2, 3), 17)

Bu sayılar tamamen keyfidir ve daha geniş bir anlamı yoktur (bunlar üzerinde de test etmeme rağmen, örneğin sınır koşulları değildir). Bu numaralar için iyi isimler bulmakta zorlanıyorum ve böyle bir şey yazmanın const int TWO = 2;yararsız olduğu açık. Testleri böyle yazmak uygun mudur, yoksa sayıları sabitler olarak mı belirlemeliyim?

In Are tüm sihirli numaraları aynı yarattı? , anlamın bağlamdan açık olması durumunda, sihirli sayıların tamam olduğunu öğrendik, ancak bu durumda sayıların aslında hiçbir anlamı yoktur.


9
Değerleri koyuyorsanız ve aynı değerleri tekrar okuyabileceğinizi umuyorsanız, sihirli sayıların iyi olduğunu söyleyebilirim. Yani, 1, 2, 3daha önce değeri kaydettiğiniz 3B dizi endeksleri ise 17, o zaman bu testin nezaketsiz olacağını düşünüyorum (bazı negatif testleriniz olduğu sürece). Ancak bir hesaplamanın sonucuysa, bu testi okuyan birinin neden foo(1, 2, 3)olması gerektiğini anlayacağından 17ve sihir rakamlarının muhtemelen bu hedefe ulaşamayacağından emin olmalısınız .
Joe White

24
const int TWO = 2;sadece kullanmaktan bile daha kötü 2. Bu, onun ruhunu ihlal etme niyeti ile kuralın ifade edilmesine uygundur.
Ajan_L

4
"Hiçbir şey ifade etmeyen" sayı nedir? Bir anlamı olmasa neden kodunda olsun ki?
Tim Grant,

6
Elbette. Bir dizi testten önce, örneğin "manuel olarak belirlenmiş örneklerin küçük bir seçimi" gibi bir yorum bırakın. Bu, sınırları ve istisnaları açıkça test eden diğer testlerinizle ilgili olarak açık olacaktır.
davidbak

5
Örneğiniz yanıltıcıdır - işlev adınız gerçekten olduğunda foo, hiçbir şey ifade etmez ve bu yüzden parametreler. Ama gerçekte, ben işlevi o adı yok eminim, ve parametreler adları yok bar1, bar2ve bar3. İsimler daha gerçekçi bir örnek olun var bir anlam, o zaman test veri değerleri bir isim gerekiyorsa da, konuşacak çok daha mantıklı.
Doktor Brown,

Yanıtlar:


81

Gerçekten ne zaman hiçbir anlamı olmayan numaralara sahipsin?

Genellikle, sayılar herhangi bir anlama geldiğinde, kodu daha okunaklı ve kendi kendine açıklayan hale getirmek için bunları test yönteminin yerel değişkenlerine atamalısınız. Değişkenlerin adları, en azından değişkenin değerini değil, değişkenin ne anlama geldiğini yansıtmalıdır.

Örnek:

const int startBalance = 10000;
const float interestRate = 0.05f;
const int years = 5;

const int expectedEndBalance = 12840;

assertEqual(calculateCompoundInterest(startBalance, interestRate, years),
            expectedEndBalance);

İlk değişkenin adlandırılmadığını HUNDRED_DOLLARS_ZERO_CENT, fakat startBalancedeğişkenin anlamının ne olduğunu, ancak değerinin herhangi bir şekilde özel olduğunu belirtmediğini unutmayın.


3
@Kevin - hangi dilde test ediyorsunuz? Bazı test çerçeveleri, test için bir dizi değer dizisi döndüren veri sağlayıcıları ayarlamanıza izin verir
HorusKol

10
Ben bir düşüncede olsa, yanlışlıkla gibi bir değer ayıklamak eğer gibi bu uygulama, çok yeni hatalar ortaya çıkarabilir olduğunu dikkat 0.05fbir etmek int. :)
Jeff Bowman

5
+1 - harika şeyler. Sadece belirli bir değerin ne olduğu umrunda değil, bu hala sihirli bir sayı olmadığı anlamına gelmez ...
Robbie Dee

2
@ PieterB: Bir constdeğişken kavramını resmileştiren C ve C ++ 'ın hatası olan AFAIK .
Steve Jessop

2
Değişkenlerinizi, adlandırılmış parametrelerle aynı şekilde calculateCompoundInterestmi adlandırdınız ? Öyleyse, ekstra yazma işlemi, test ettiğiniz işlevin belgelerini okuduğunuz veya en azından IDE'niz tarafından verilen adları kopyaladığınız bir çalışma kanıtıdır. Bunun okuyucuya kodun amacını ne kadar söyleyeceğinden emin değilim, ancak parametreleri yanlış sırada iletirseniz en azından neyin amaçlandığını söyleyebilirler.
Steve Jessop

20

Ne yaptıklarını görmek için rastgele sayılar kullanıyorsanız, gerçekten aradığınız şey muhtemelen rastgele oluşturulmuş test verileri veya özellik tabanlı testlerdir.

Örneğin, Hipotez , bu tür testler için harika bir Python kütüphanesidir ve QuickCheck'e dayanır .

Normal bir birim testini aşağıdaki gibi bir şey olarak düşünün:

  1. Bazı verileri ayarla.
  2. Veriler üzerinde bazı işlemler gerçekleştirin.
  3. Sonuç hakkında bir şey söyleyin.

Hipotez, bunun gibi görünen testleri yazmanıza izin verir:

  1. Bazı özelliklere uyan tüm veriler için.
  2. Veriler üzerinde bazı işlemler gerçekleştirin.
  3. Sonuç hakkında bir şey söyleyin.

Fikir, kendinizi kendi değerlerinizle sınırlamak değil, fonksiyonlarınızın özelliklerine uyduğunu kontrol etmek için kullanılabilecek rastgele olanları seçmek. Önemli bir not olarak, bu sistemler genel olarak başarısız olan herhangi bir girişi hatırlar ve ardından bu girişlerin gelecekte daima test edilmesini sağlar.

3. nokta bazı insanlarla kafa karıştırıcı olabilir, bu yüzden açıklığa kavuşturalım. Kesin cevabı verdiğiniz anlamına gelmez - bu keyfi bir giriş için yapmanız kesinlikle mümkün değildir. Bunun yerine, sonucun bir özelliği hakkında bir şey iddia ediyorsunuz . Örneğin, bir listeye bir şey ekledikten sonra boş kalmayacağını veya kendi kendini dengeleyen bir ikili arama ağacının gerçekten dengelendiğini (belirli bir veri yapısının sahip olduğu ölçütleri kullanarak) iddia edebilirsiniz.

Genel olarak, rastgele sayıları kendiniz seçmek muhtemelen oldukça kötü bir şey - bu gerçekten de tam bir katma değer oluşturmuyor ve okuyan birinin kafasını karıştırıyor. Bir grup rasgele test verisinin otomatik olarak üretilmesi ve etkin bir şekilde kullanılması iyi. Tercih ettiğiniz dil için bir Hipotez veya QuickCheck benzeri kütüphane bulmak, muhtemelen başkalarına anlaşılır kalırken hedeflerinize ulaşmak için daha iyi bir yoldur.


11
Rastgele testler, yeniden üretilmesi zor olan böcekleri bulabilir, ancak rastgele test etmek, yeniden üretilebilir böcekleri zorlukla bulur. Belirli bir tekrarlanabilir test durumuyla test hatalarını yakaladığınızdan emin olun.
JBRWilkinson

5
Birim testinizin "sonuç hakkında bir şey söylerken" tıkanmadığını nasıl bildiniz (bu durumda, hesaplananı yeniden foohesaplayın) ...? Eğer kodunuzun doğru cevabı verdiğinden% 100 emin olsaydınız, o kodu programa koyar ve test etmezdiniz. Eğer değilseniz, testi sınamanız gerekir ve bence herkes bunun nereye gittiğini görüyor.

2
Evet, rastgele girdileri bir işleve aktarırsanız, çıkışın doğru çalıştığını belirtmek için çıktının ne olacağını bilmeniz gerekir. Sabit / seçilen test değerleri ile elbette elle çalışabilirsiniz vb. Ancak sonucun doğru olup olmadığını kesin olarak tespit etmek için otomatik olarak herhangi bir otomatik yöntemle test ettiğiniz işlevle aynı sorunlara tabi olabilir. Sahip olduğunuz uygulamayı (çalışıp çalışmadığını test ettiğiniz için yapamadığınızdan dolayı) kullanırsınız ya da bir sorunlu olması muhtemel olan yeni bir uygulama yazarsınız (ya da doğru olanı yapma olasılığını daha fazla kullanırsınız. ).
Chris

7
@NajibIdrissi - mutlaka değil. Örneğin, test ettiğiniz işlemin tersini uygulamanın sonuca geri döndüğünü, başlangıçta başladığınız değeri geri verdiğini test edebilirsiniz. Veya beklenen değişmezleri test edebilirsiniz (örneğin, dgünlerdeki tüm faiz hesaplamaları için , dgün + 1 aydaki hesaplama bilinen bir aylık yüzde daha yüksek olmalıdır), vb.
Jules

12
@Chris - Çoğu durumda, sonuçların doğru olduğunu kontrol etmek, sonuçları oluşturmaktan daha kolaydır. Bu doğru olmasa da tüm şartlar, öyle çok. Örnek: Dengeli bir ikili ağa bir giriş eklemek, aynı zamanda dengelenmiş ve test edilmesi kolay, pratikte oldukça zor yeni bir ağaçla sonuçlanmalıdır.
Jules

11

Birim test adınız içeriğin çoğunu sağlamalıdır. Sabitlerin değerlerinden değil. Bir testin adı / dokümantasyonu, testte hangi büyü sayılarının bulunduğunu ve uygun bağlamı ve açıklamasını vermelidir.

Bu yeterli değilse, bir miktar dokümantasyon sağlayabilmelidir (değişken ismi veya dokümanı aracılığıyla). Fonksiyonun kendisinde umarım anlamlı adlara sahip parametrelere sahip olduğunu unutmayın. Argümanları adlandırmak için bunları testinize kopyalamak oldukça anlamsız.

Ve son olarak, eğer en içkisiz olanlar bunun zor / pratik olmadığı kadar karmaşıksa, muhtemelen çok fazla karmaşık fonksiyonlara sahip olabilirsiniz ve bunun neden böyle olduğunu düşünebilirsiniz.

Testleri ne kadar titizlikle yazarsanız, gerçek kodunuz o kadar kötü olur. Testi netleştirmek için test değerlerinizi isimlendirme ihtiyacı duyuyorsanız, gerçek yönteminizin daha iyi adlandırma ve / veya dokümantasyona ihtiyaç duyması şiddetle tavsiye edilir. Testlerde sabitleri isimlendirmek için bir ihtiyaç bulursanız, neden buna ihtiyaç duyduğunuza bakardım - muhtemelen sorun testin kendisi değil uygulamadır.


Bu cevap, testin amacını anlama zorluğuyla ilgili gibi görünüyor, oysa asıl soru yöntem parametrelerinde sihirli sayılarla ilgili ...
Robbie Dee

@RobbieBir testin adı / dokümantasyonu testte hangi sihirli sayıların bulunduğunu ve uygun bağlamı açıklamalıdır. Aksi takdirde, ya belgeler ekleyin ya da daha net olması için testi yeniden adlandırın.
enderland

Sihirli numaralara isim vermek daha iyi olurdu. Parametrelerin sayısı değişmişse, dokümantasyonun eski olma riski vardır.
Robbie Dee,

1
@RobbieDee, işlevin kendisinin umarım anlamlı adlara sahip parametrelere sahip olduğunu unutmayın. Argümanları adlandırmak için bunları testinize kopyalamak oldukça anlamsız.
enderland

"Umarım" ha? Neden sadece bir şeyi doğru şekilde kodlamıyorsunuz ve Philipp'in belirttiği gibi görünüşte sihirli bir sayıya dokunmuyorsunuz ...
Robbie Dee

9

Bu, büyük ölçüde test ettiğiniz işleve bağlıdır. Bireysel sayıların kendi başlarına özel bir anlama sahip olmadığı birçok durum biliyorum, ancak bir bütün olarak test durumu düşünceli bir şekilde oluşturulmuş ve özel bir anlamı var. Bir şekilde belgelenmesi gereken şey budur. Örneğin, eğer üç sayının üçgenin kenarlarının geçerli uzunlukları olup olmadığına karar veren foobir yöntem gerçekten ise testForTriangle, testleriniz şöyle görünebilir:

// standard triangle with area >0
assertEqual(testForTriangle(2, 3, 4), true);

// degenerated triangle, length of two edges match the length of the third
assertEqual(testForTriangle(1, 2, 3), true);  

// no triangle
assertEqual(testForTriangle(1, 2, 4), false); 

// two sides equal
assertEqual(testForTriangle(2, 2, 3), true);

// all three sides equal
assertEqual(testForTriangle(4, 4, 4), true);

// degenerated triangle / point
assertEqual(testForTriangle(0, 0, 0), true);  

ve bunun gibi. Bunu iyileştirebilir ve yorumları assertEqualtest başarısız olduğunda görüntülenecek olan bir mesaj parametresine dönüştürebilirsiniz . Daha sonra bunu daha da geliştirebilir ve bunu veri güdümlü bir teste yeniden değerlendirebilirsiniz (test çerçeveniz destekliyorsa). Yine de, bu rakamları neden seçtiğinizi ve bireysel vaka ile test ettiğiniz çeşitli davranışları kodun içine bir not yazarsanız, kendinize bir iyilik yaparsınız.

Elbette, diğer fonksiyonlar için, parametreler için bireysel değerler daha önemli olabilir, bu nedenle fooparametrelerin anlamını nasıl ele alacağınız sorulduğunda olduğu gibi anlamsız bir fonksiyon adı kullanmak muhtemelen en iyi fikir değildir.


Hassas çözüm
user1725145

6

Neden sayılar yerine adlandırılmış Sabitler kullanmak istiyoruz?

  1. KURU - 3 yerde değere ihtiyacım olursa, sadece bir kez tanımlamak istiyorum, böylece değişerse bir yerde değiştirebilirim.
  2. Rakamlara anlam verin.

Her biri 3 Sayı (startBalance, faiz, yıl) çeşitliliği olan birkaç birim testi yazarsanız, değerleri sadece birim testine yerel değişkenler olarak paketlerdim. Ait oldukları en küçük kapsam.

testBigInterest()
  var startBalance = 10;
  var interestInPercent = 100
  var years = 2
  assert( calcCreditSum( startBalance, interestInPercent, years ) == 40 )

testSmallInterest()
  var startBalance = 50;
  var interestInPercent = .5
  var years = 1
  assert( calcCreditSum( startBalance, interestInPercent, years ) == 50.25 )

Adlandırılmış parametrelere izin veren bir dil kullanıyorsanız, bu elbette süper. Orada sadece ham metodları çağırırdım. Bu ifadeyi daha özlü hale getiren herhangi bir yeniden düzenleme yapmayı hayal edemiyorum:

testBigInterest()
  assert( calcCreditSum( startBalance:       10
                        ,interestInPercent: 100
                        ,years:               2 ) = 40 )

Veya test senaryolarını bir dizi veya Harita formatında tanımlamanıza izin verecek bir test Çerçevesi kullanın:

testcases = { {
                Name: "BigInterest"
               ,StartBalance:       10
               ,InterestInPercent: 100
               ,Years:               2
              }
             ,{ 
                Name: "SmallInterest"
               ,StartBalance:       50
               ,InterestInPercent:  .5
               ,Years:               1
              }
            }

3

... fakat bu durumda, sayıların aslında hiçbir anlamı yoktur.

Rakamlar bir yöntem çağırmak için kullanılıyor, bu yüzden kesinlikle yukarıdaki öncül yanlıştır. Sayıların ne olduğu umrunda değil , ama konunun dışında. Evet, bazı IDE sihirbazları tarafından numaraların ne için kullanıldığına karar verebilirsiniz, ancak sadece değer adlarını vermiş olsanız daha iyi olurdu - sadece parametrelere uysalar bile.


1
Yine de bu doğru değil - yazdığım en son birim testinde olduğu gibi ( assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")). Bu örnekte, 42yalnızca kod tarafından üretilen ve test komut dosyası lvalue_operatorstarafından döndürüldüğünde kontrol edilen kod dosyasında üretilen bir yer tutucu değeridir . İki farklı yerde aynı değerin oluşması dışında hiçbir anlamı yoktur. Burada asıl yararlı bir anlam ifade eden uygun bir isim ne olabilir?
Jules

3

Bir test etmek isterseniz saf fonksiyonunu , sınır koşulları olmayan girdilerin bir sette o zaman neredeyse kesin bir üzerinde test etmek istiyorum sürü sınır koşulları olmayan (ve vardır) girdilerin setleri. Ve bana göre bu , fonksiyonu çağırmak için bir değerler tablosu ve bir döngü olması gerektiği anlamına gelir :

struct test_foo_values {
    int bar;
    int baz;
    int blurf;
    int expected;
};
const struct test_foo_values test_foo_with[] = {
   { 1, 2, 3, 17 },
   { 2, 4, 9, 34 },
   // ... many more here ...
};

for (size_t i = 0; i < ARRAY_SIZE(test_foo_with); i++) {
    const struct test_foo_values *c = test_foo_with[i];
    assertEqual(foo(c->bar, c->baz, c->blurf), c->expected);
}

Dannnno'nun cevabında önerildiği gibi araçlar , test edilecek değerler tablosunu oluşturmanıza yardımcı olabilir. bar, bazve Philipp'in cevabındablurf tartışıldığı gibi anlamlı isimlerle değiştirilmelidir .

(Burada tartışılabilir genel ilke: Sayılar her zaman "sihirli sayılar" dır, bunun yerine, sayılar veri olabilir . Sayılarınızı bir diziye, belki de bir kayıt dizisine koymak mantıklı olacaktır. Bunun tersine, elinizde veri bulunduğundan şüpheleniyorsanız, bir diziye yerleştirmeyi ve daha fazlasını edinmeyi düşünün.)


1

Testler üretim kodundan farklıdır ve en azından Spock'da yazılmış ünite testlerinde, kısa ve noktaya göre sihirli sabitleri kullanmakta sorunum yok.

Eğer bir test 5 satır uzunluğundaysa ve verilen / / / / sonrasında verilen şemaya uyuyorsa, bu gibi değerleri sabitlere çıkarmak kodun daha uzun sürmesini ve okunmasını zorlaştırır. Eğer mantık "Smith adında bir kullanıcı eklediğimde, Smith'in kullanıcı listesine geri döndüğü kullanıcıyı görüyorum" ise, "Smith" i sabitlemeye almanın bir anlamı yoktur.

Elbette, "verilen" (kurulum) blokta kullanılan değerleri "when" ve "then" bloklarında bulunan değerlerle kolayca eşleştirebiliyorsanız bu geçerlidir. Test kurulumunuz verinin kullanıldığı yerden ayrıldıysa (kodda), sabitleri kullanmak daha iyi olabilir. Ancak, testler kendi kendine yeten en iyiler olduğundan, kurulum genellikle kullanım yerine yakındır ve ilk durum geçerlidir, yani bu durumda sihirli sabitler oldukça kabul edilebilir.


1

Öncelikle, bir programcının yazdığı tüm otomatik testleri kapsayan “birim test” in sıkça kullanıldığını ve her testin ne denilmesi gerektiğini tartışmanın anlamsız olduğunu kabul edelim….

Yazılımın çok fazla girdi aldığı bir sistem üzerinde çalıştım ve diğer sayıları optimize ederken bazı kısıtlamaları yerine getirmek için bir “çözüm” geliştirdim. Doğru cevap yoktu, bu yüzden yazılım sadece makul bir cevap vermek zorunda kaldı.

Bunu bir başlangıç ​​noktası elde etmek için birçok rasgele sayı kullanarak, ardından sonucu iyileştirmek için bir “tepe tırmanıcı” kullanarak gerçekleştirdi. Bu birçok kez çalıştırıldı, en iyi sonucu alarak. Rasgele bir sayı üreteci ekilebilir, böylece her zaman aynı sayıları aynı sırada verir, bu nedenle test bir tohum hazırlarsa sonucun her çalışmada aynı olacağını biliyoruz.

Yukarıdakileri yapan çok sayıda test yaptık ve sonuçların aynı olup olmadığını kontrol ettik, bu bize sistemin yeniden yapılandırılması sırasında hatalı bir şekilde ne yaptığını değiştirmediğimizi söyledi . Bu durumun doğruluğu hakkında hiçbir şey söylemedi. sistemin o kısmı ne yaptı.

Bu testler, optimizasyon kodunda yapılan herhangi bir değişikliğin testleri kıracağından, ancak verileri daha önceden işleyen ve sonuçları sonradan işleyen çok daha büyük kodda bazı hatalar bulduğundan, bakımı maliyetlidir.

Veritabanını “alay ettiğimiz” için, bu sınamaları “birim sınamaları” olarak adlandırabilirsiniz, ancak “birim” oldukça büyüktü.

Çoğu zaman testsiz bir sistem üzerinde çalışırken, yukarıdaki gibi bir şey yaparsınız, böylece yeniden düzenlemenin çıktının değişmediğini onaylayabilirsiniz; Umarım yeni kod için daha iyi testler yazılmıştır!


1

Bu durumda, numaralara Magic Numbers yerine Rasgele Sayılar denmesi gerektiğini düşünüyorum ve satırı "rastgele test durumu" olarak yorumlayın.

Elbette, bazı Magic Numbers'lar da benzersiz "tutamaç" değerlerinde olduğu gibi isteğe bağlı olabilir (elbette adlandırılmış sabitlerle değiştirilmelidir), ancak "iki haftada bir artıklarda serbest bırakılmış bir Avrupa serisinin hava hızı" gibi önceden hesaplanmış sabitler de olabilir, Nümerik değer yorum veya faydalı bağlam olmadan takılı ise.


0

Kesin bir evet / hayır demek için girişimde bulunmayacağım, ama işte iyi olup olmadığına karar verirken kendinize sormanız gereken bazı sorular var.

  1. Rakamlar bir şey ifade etmiyorsa, neden ilk etapta oradalar? Başka bir şeyle değiştirilebilir mi? Değer iddiaları yerine yöntem çağrılarına ve akışına dayalı doğrulama yapabilir misiniz? Mockito'nun verify(), aslında bir değer ortaya koymak yerine, nesnelerle alay etmek için belirli yöntem çağrılarının yapılıp yapılmadığını kontrol eden bir yöntem düşünün .

  2. Sayılar bir şey ifade ediyorsa, uygun şekilde adlandırılmış değişkenlere atanmalıdırlar.

  3. Numarayı Yazma 2olarak TWObelirli bağlamlarda yararlı ve diğer durumlarda, çok fazla değil olabilir.

    • Örneğin: assertEquals(TWO, half_of(FOUR))kodu okuyan birine mantıklı geliyor. Ne test ettiğiniz hemen bellidir.
    • Ancak, eğer sınavın buysa assertEquals(numCustomersInBank(BANK_1), TWO), bu o kadar mantıklı değil. Neden gelmez BANK_1iki müşteriler içeriyor? Ne için test ediyoruz?
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.