Bir kalıbı yuvarlamanın kullanım durumunu kapsayan iyi birim testleri nelerdir?


18

Birim testi ile uğraşmaya çalışıyorum.

Varsayılan taraf sayısı 6'ya eşit olan bir kalıba sahip olduğumuzu varsayalım (ancak 4, 5 taraflı vb. Olabilir):

import random
class Die():
    def __init__(self, sides=6):
        self._sides = sides

    def roll(self):
        return random.randint(1, self._sides)

Aşağıdakiler geçerli / yararlı birim testleri olur mu?

  • 1-6 aralığında bir ruloyu 6 taraflı bir kalıp için test edin
  • 6 taraflı bir kalıp için 0 rulosunu test edin
  • 7'lik bir ruloyu 6 taraflı bir kalıp için test edin
  • 1-3 aralığında bir ruloyu 3 taraflı bir kalıp için test edin
  • 0 ruloyu 3 taraflı kalıp için test edin
  • 4 ruloyu 3 taraflı bir kalıp için test edin

Ben sadece bu rastgele modül yeterince uzun süre civarında olduğu gibi zaman kaybı olduğunu düşünüyorum ama sonra rasgele modülü (ben benim Python sürümünü güncelleyin demek) güncellenir sonra en azından ben kapalı düşünüyorum.

Ayrıca, örneğin bu durumda 3 olan diğer kalıp rulolarının varyasyonlarını test etmem gerekiyor mu yoksa başka bir başlatılmış kalıp durumunu kapsamak iyi mi?


1
Eksi-5-taraflı bir kalıp ya da boş-taraflı bir kalıp ne olacak?
JensG

Yanıtlar:


22

Haklısınız, testleriniz random modülün işini yaptığını ; unittest yalnızca sınıfın kendisini test etmelidir, diğer kodlarla nasıl etkileşime gireceğini değil (ayrı olarak test edilmelidir).

Kodunuzun random.randint()yanlış kullanması elbette tamamen mümkündür ; ya da random.randrange(1, self._sides)onun yerine çağırıyorsunuz ve ölmeniz asla en yüksek değeri atmıyor, ama bu farklı bir tür hata olurdu, bir unittest ile yakalayabileceğiniz bir hata değil. Bu durumda, die üniteniz olarak tasarlanmış çalışan, ancak tasarım kendisi hatalıydı.

Bu durumda, işlevi değiştirmek için alaycı kullanırım randint()ve yalnızca doğru şekilde çağrıldığını doğrularım . Python 3.3 ve üstü, bu tür testleri işlemek için unittest.mockmodülle birlikte gelir , ancak aynı işlevselliği elde etmek için harici mockpaketi eski sürümlere yükleyebilirsiniz

import unittest
try:
    from unittest.mock import patch
except ImportError:
    # < python 3.3
    from mock import patch


@patch('random.randint', return_value=3)
class TestDice(unittest.TestCase):
    def _make_one(self, *args, **kw):
        from die import Die
        return Die(*args, **kw)

    def test_standard_size(self, mocked_randint):
        die = self._make_one()
        result = die.roll()

        mocked_randint.assert_called_with(1, 6)
        self.assertEqual(result, 3)

    def test_custom_size(self, mocked_randint):
        die = self._make_one(sides=42)
        result = die.roll()

        mocked_randint.assert_called_with(1, 42)
        self.assertEqual(result, 3)


if __name__ == '__main__':
    unittest.main()

Alay ile, testiniz artık çok basit; gerçekten sadece 2 vaka var. 6 taraflı kalıp ve özel taraf kasa için varsayılan kasa.

randint()İşlevinin genel ad alanındaki işlevi geçici olarak değiştirmenin başka yolları da vardır Die, ancak mockmodül bunu en kolay hale getirir. Buradaki @mock.patchdekoratör , test durumundaki tüm test yöntemleri için geçerlidir ; her test yöntemine ek bir argüman, alaycı random.randint()işlev iletilir , bu yüzden gerçekten doğru çağrılıp çağrılmadığını görmek için alaycıya karşı test edebiliriz. return_valueO çağrıldığında biz olduğunu doğrulamaları, taklidinin dönen hangi argüman belirtir die.roll()yöntem aslında bize 'rastgele' sonuç döndürdü.

Burada başka bir Python unittesting en iyi uygulamasını kullandım: testin altındaki sınıfı testin bir parçası olarak içe aktarın. Bu _make_oneyöntem, bir test içinde içe aktarma ve örnekleme işlemini gerçekleştirir , böylece orijinal modülün içe aktarılmasını engelleyecek bir sözdizimi hatası veya başka bir hata yapmış olsanız bile test modülü yüklenir.

Bu şekilde, modül kodunun kendisinde bir hata yaptıysanız, testler yine de çalıştırılacaktır; sadece başarısız olurlar, size kodunuzdaki hatayı anlatırlar.

Açık olmak gerekirse, yukarıdaki testler aşırı derecede basittir. Buradaki amaç random.randint(), örneğin doğru argümanlarla çağrılmış olanları test etmektir. Bunun yerine amaç, belirli girdiler verildiğinde ünitenin doğru sonuçları ürettiğini test etmektir; bu girdiler, test edilmeyen diğer birimlerin sonuçlarını içerir . Alay ederekrandom.randint()Yöntemi kodunuzdaki başka bir giriş üzerinde kontrol sahibi olursunuz.

In gerçek dünya testlerinin, üniteniz-under-testinde gerçek kod daha karmaşık olacak; API'ya iletilen girdilerle ve diğer birimlerin nasıl çağrıldığına ilişkin ilişki yine de ilginç olabilir ve alay etmek size ara sonuçlara erişmenizi sağlar ve aynı zamanda bu aramalar için dönüş değerlerini ayarlamanıza olanak tanır.

Örneğin, kullanıcıların bir 3. taraf OAuth2 hizmetine (çok aşamalı bir etkileşim) karşı kimlik doğrulaması yapan kodda, kodunuzun bu 3. taraf hizmete doğru verileri iletip iletmediğini test etmek ve bunun 3. taraf hizmet, tam bir OAuth2 sunucusu kendiniz oluşturmak zorunda kalmadan farklı senaryoları simüle etmenize izin vererek geri dönecektir. Burada, ilk yanıttan gelen bilgilerin doğru bir şekilde işlendiğini ve ikinci aşama çağrısına aktarıldığını test etmek önemlidir, bu nedenle alay edilen hizmetin doğru bir şekilde çağrıldığını görmek istersiniz.


1
2'den fazla test vakanız var ... sonuçlar varsayılan değeri kontrol ediyor: alt (1), üst (6), alt (0), üst (7) ötesinde ve max_int gibi kullanıcı tanımlı sayılar için sonuçlar vb girişi de onaylanmamıştır, bu da bir noktada test edilmesi gerekebilir ...
James Snell

2
Hayır, bunlar randint()kod değil testler Die.roll().
Martijn Pieters

Aslında sadece randint'in doğru bir şekilde çağrılmamasını değil, sonucunun da doğru şekilde kullanılmasını sağlamanın bir yolu vardır: sentinel.dieörneğin (sentinel nesnesi unittest.mockde vardır) döndürmek için alay edin ve sonra rulo yönteminizden döndürülen şeyin ne olduğunu doğrulayın. Bu aslında test edilen yöntemi uygulamanın sadece bir yoluna izin verir.
aragaer

@aragaer: değerin değişmeden döndürüldüğünü doğrulamak istiyorsanız, sentinel.diebunu sağlamak için harika bir yol olacaktır.
Martijn Pieters

Neden mocked_randint'in belirli değerlerle çağrıldığından emin olmak istediğinizi anlamıyorum. Tahmin edilebilir değerleri döndürmek için randım alay etmek istediğini anlıyorum, ancak sadece öngörülebilir değerleri döndürdüğü ve hangi değerlerle çağrılmadığı endişesi değil mi? Bana öyle geliyor ki, denilen değerleri kontrol etmek gereksiz bir şekilde testi uygulamanın ince detaylarına bağlıyor. Ayrıca neden kalıbın tam bir randıman değeri döndürdüğünü umuyoruz? Gerçekten 1'den büyük ve maks. Değere eşit bir değer döndürmesi umrumda değil mi?
bdrx

16

Martijn'ın cevabı , random.randint adını verdiğinizi gösteren bir test yapmak istiyorsanız bunu nasıl yapacağınızdır. Ancak, "bu soruyu cevaplamıyor" olma riski altında, bunun hiç bir şekilde birim test edilmemesi gerektiğini hissediyorum. Alaycı randint artık kara kutu testi değildir - özellikle uygulamada bazı şeylerin devam ettiğini gösterirsiniz . Kara kutu testi bile bir seçenek değil - sonucun asla olmayacağını kanıtlayacak bir test yok 1'den az veya 6'dan fazla .

Alay edebilir misin randint? Evet yapabilirsin. Ama neyi kanıtlıyorsunuz? Buna argüman 1 ve taraflarla dediğiniz. Ne yapar o demek? Birinci kareye geri döndünüz - günün sonunda - resmen veya gayri resmi olarak - random.randint(1, sides)doğru bir zar attığını doğrulamak zorunda kalacaksınız .

Ben hepimiz birim testi yapıyorum. Onlar fantastik akıl kontrolü ve böcek varlığını ortaya çıkarmak. Bununla birlikte, yokluklarını asla kanıtlayamazlar ve test yoluyla hiç iddia edilemeyecek şeyler vardır (örneğin, belirli bir işlev asla bir istisna atmaz veya her zaman sona erer.) Bu özel durumda, dayanabileceğiniz çok az şey olduğunu hissediyorum. kazanç. Belirleyici olan davranışlar için, birim testleri mantıklıdır çünkü aslında beklediğiniz cevabın ne olacağını biliyorsunuzdur.


Ünite testleri kara kutu testleri değildir. Çeşitli parçaların tasarlandığı gibi etkileşime girdiğini görmek için entegrasyon testlerinin amacı budur. Bu bir görüş meselesi, elbette (çoğu test felsefesi), bkz. "Birim Testi" beyaz kutu veya kara kutu testi altına giriyor mu? ve Kara Kutu Ünitesi Bazı (Yığın Taşması) perspektifleri için test .
Martijn Pieters

@MartijnPieters "Entegrasyon testlerinin ne için olduğunu" kabul etmiyorum. Entegrasyon testleri, sistemin tüm bileşenlerinin doğru bir şekilde etkileşime girdiğini kontrol etmek içindir. Belirli bir bileşenin belirli bir girdi için doğru çıktıyı verdiğini test edecek yer değiller. Kara kutuya karşı beyaz kutu birimi testine gelince, beyaz kutu birimi testleri sonunda uygulama değişiklikleriyle kırılacak ve uygulamada yaptığınız varsayımlar muhtemelen teste tabi tutulacaktır. Bu doğrulama random.randintile çağrılır 1, sidesbunu yanlış şey olsa değersiz.
Doval

Evet, bu beyaz kutu birim testinin bir sınırlamasıdır. Bununla birlikte, testte [1, taraflar] (dahil) aralığındaki değerleri doğru bir şekilde döndürecek bir nokta yokturrandom.randint() , bu da randomünitenin doğru çalışmasını sağlamak için Python geliştiricilerine bağlıdır .
Martijn Pieters

Kendinizi söylediğiniz gibi, birim testi kodunuzun hatasız olduğunu garanti edemez; kodunuz diğer birimleri yanlış kullanıyorsa (diyelim ki random.randint()böyle davranmanız random.randrange()ve bu şekilde çağırmanız beklenirse random.randint(1, sides + 1), yine de batırılmış olursunuz.)
Martijn Pieters

2
@MartijnPieters Orada sana katılıyorum, ama itiraz ettiğim bu değil. Ben random.randint argümanları (1, taraf) ile çağrılıyor test etmek için itiraz ediyorum . Uygulamada bunun yapılacak doğru şey olduğunu varsaydınız ve şimdi bu varsayımı testte tekrarlıyorsunuz. Bu varsayım yanlışsa, test geçmiş olacaktır ancak uygulamanız yine de yanlıştır. Yazmak ve korumak için tam bir eşek ağrısının yarı yamalak bir kanıtıdır.
Doval

6

Rastgele tohum düzeltin. 1, 2, 5 ve 12 taraflı zarlar için, birkaç bin ruloun 0 ve N + 1 dahil olmak üzere 1 ve N dahil sonuçlar verdiğini doğrulayın. beklenen aralığı örtün, farklı bir tohuma geçin.

Alaycı araçlar iyidir, ancak bir şey yapmanıza izin verdikleri için, o şeyin yapılması gerektiği anlamına gelmez. YAGNI, özellikler kadar test fikstürleri için de geçerlidir.

Kilitlenmemiş bağımlılıklarla kolayca test edebiliyorsanız, hemen hemen her zaman yapmanız gerekir; bu şekilde testleriniz sadece test sayısını arttırmakla kalmayacak şekilde hata sayısını azaltmaya odaklanacaktır. Aşırı alay riskleri yanıltıcı kapsama figürleri yaratır, bu da gerçek testin daha sonraki bir aşamaya ertelenmesine yol açabilir, belki de asla ...


3

Ne Diedüşünüyorsun? - bir sargıdan daha fazlası değil random. Bu kapsüller random.randintve uygulamanın kendi söz varlığı açısından bunu relabels: Die.Roll.

Ben arasındaki soyutlama başka bir katman eklemek için alakalı bulmuyorum Dieve randomçünkü Diekendisi zaten dolaylama bu tabakasıdır uygulama ve platform arasındaki.

Konserve zar sonuçları istiyorsanız, sadece sahte Die, sahte değilrandom .

Genel olarak, harici sistemlerle iletişim kuran sarıcı nesnelerimi birim test etmiyorum, onlar için entegrasyon testleri yazıyorum. Bunlardan birkaçını yazabilirsiniz, Dieancak belirttiğiniz gibi, altta yatan nesnenin rastgele doğası nedeniyle, anlamlı olmayacaklardır. Ek olarak, burada bir yapılandırma veya ağ iletişimi yoktur, bu nedenle bir platform çağrısı dışında test edilecek çok fazla şey yoktur.

=> Bu Diesadece birkaç önemsiz kod satırları ve randomkendisi ile karşılaştırıldığında çok az mantık ekler düşününce, ben bu belirli örnekte test atlamak istiyorum.


2

Rastgele sayı üretecini tohumlama ve beklenen sonuçları doğrulama, görebildiğim kadarıyla geçerli bir test DEĞİLDİR. Zarınızın dahili olarak NASIL çalıştığına dair varsayımlar yapar, bu da yaramaz yaramazdır. Python geliştiricileri rastgele sayı üretecini değiştirebilir veya kalıp (NOT: "zar" çoğul, "kalıp" tekildir. Sınıfınız bir çağrıda birden fazla kalıp silindiri uygulamadığı sürece, muhtemelen "kalıp" olarak adlandırılmalıdır) farklı bir rastgele sayı üreteci kullanın.

Benzer şekilde, rasgele işlevin alay edilmesi, sınıf uygulamasının tam olarak beklendiği gibi çalıştığını varsayar. Neden böyle olmayabilir? Birisi varsayılan python rasgele sayı üretecinin kontrolünü ele geçirebilir ve bundan kaçınmak için, kalıbınızın gelecekteki bir sürümü daha rasgele verileri karıştırmak için birkaç rastgele sayı veya daha büyük rastgele sayılar alabilir. Benzer bir şema, NSA'nın CPU'lara yerleşik donanım rasgele sayı üreteçlerine müdahale ettiğinden şüphelendiklerinde FreeBSD işletim sisteminin yapımcıları tarafından kullanıldı.

Eğer ben olsaydım, 6000 rulo koştum, onları hesaplayın ve 1-6 arasındaki her sayının 500 ila 1500 kez yuvarlandığından emin olun. Ayrıca, bu aralığın dışındaki hiçbir sayının döndürülmediğini de kontrol ederim. Ayrıca ikinci bir 6000 rulo seti için, [1..6] frekans sırasına göre sipariş verirken, sonucun farklı olduğunu kontrol edebilirim (sayılar rasgele ise, 720 çalışmanın bir kez başarısız olur!). Kapsamlı olmak istiyorsanız, 1'in ardından, 2'den sonra vb. Sayıların sıklığını bulabilirsiniz; ancak örnek boyutunuzun yeterince büyük olduğundan ve yeterli varyansınız olduğundan emin olun. İnsanlar rastgele sayıların gerçekte olduğundan daha az örüntüye sahip olmasını bekler.

12 taraflı ve 2 taraflı bir kalıp için tekrarlayın (6 en çok kullanılan, bu nedenle bu kodu yazan herkes için en çok beklenen).

Son olarak, 1 taraflı bir kalıp, 0 taraflı bir kalıp, -1 taraflı bir kalıp, 2.3 taraflı bir kalıp, [1,2,3,4,5,6] taraflı bir kalıp ve "falan" taraflı bir kalıp. Elbette, bunların hepsi başarısız olmalıdır; yararlı bir şekilde başarısız olurlar mı? Bunlar muhtemelen yaratılışta başarısız olmalı, yuvarlanmada değil.

Ya da, belki de bunları farklı şekilde ele almak istersiniz - belki de [1,2,3,4,5,6] ile bir kalıp oluşturmak kabul edilebilir olmalıdır - ve belki de “filan”; bu 4 yüzlü bir kalıp olabilir ve her yüzünde bir harf vardır. Sihirli bir sekiz top gibi oyun "Boggle" akla yaylar.

Ve son olarak, bunu düşünmek isteyebilirsiniz: http://lh6.ggpht.com/-fAGXwbJbYRM/UJA_31ACOLI/AAAAAAAAAPg/2FxOWzo96KE/s1600-h/random%25255B3%255D.jpg


2

Gelgite karşı yüzme riski altında, bu problemi birkaç yıl önce şu ana kadar bahsedilmeyen bir yöntem kullanarak çözdüm.

Benim stratejim, RNG'yi tüm alanı kapsayan öngörülebilir bir değer akışı üreten bir modelle alay etmekti. (Diyelim) side = 6 ise ve RNG sırayla 0 ila 5 arasında değerler üretiyorsa, sınıfımın nasıl davranması gerektiğini ve birim testini buna göre nasıl yapacağını tahmin edebilirim.

Bunun mantığı, bunun sadece sınıftaki mantığı, RNG'nin sonunda bu değerlerin her birini üreteceği ve RNG'nin kendisini test etmediği varsayımıyla sınamasıdır.

Basit, deterministik, tekrarlanabilir ve hataları yakalar. Yine aynı stratejiyi kullanırdım.


Soru, bir RNG'nin varlığı göz önüne alındığında, testlerin ne olması gerektiğini, sadece test için hangi verilerin kullanılabileceğini açıklamıyor. Benim önerim sadece RNG ile alay ederek kapsamlı bir test yapmak. Neyin test edilmeye değer olduğu sorusu, soruda bulunmayan bilgilere bağlıdır.


Öngörülebilir olmak için RNG ile alay ettiğinizi varsayalım. O zaman ne test edersiniz? Soru "Aşağıdakiler geçerli / yararlı birim testleri olur mu?" 0-5 döndürmek için alay etmek bir test değil, test kurulumudur. "Buna göre birim testi" nasıl yapardınız? Nasıl "hataları yakalamak" anlamıyorum. 'Birim' testi için neye ihtiyacım olduğunu anlamakta zorlanıyorum.
bdrx

@bdrx: Bu bir süre önceydi: Şimdi farklı cevap verirdim. Ancak düzenlemeye bakın.
david.pfx

1

Sorunuzda önerdiğiniz testler, modüler bir aritmetik sayacı uygulama olarak algılamaz. Ve olasılık dağılımıyla ilgili kodda yaygın uygulama hatalarını tespit etmezler return 1 + (random.randint(1,maxint) % sides). Veya jeneratörde 2 boyutlu desenlerle sonuçlanan bir değişiklik.

Aslında, eşit dağıtılmış rastgele görünen sayılar oluşturduğunuzu doğrulamak istiyorsanız, çok çeşitli özellikleri kontrol etmeniz gerekir. Bu konuda oldukça iyi bir iş yapmak için oluşturulan numaralarınızda http://www.phy.duke.edu/~rgb/General/dieharder.php çalıştırabilirsiniz . Veya benzer şekilde karmaşık bir birim test paketi yazın.

Bu, birim testinin veya TDD'nin hatası değildir, rastgelelik sadece doğrulanması çok zor bir özelliktir. Ve örnekler için popüler bir konu.


-1

Bir kalıp rulosunun en kolay testi, onu birkaç yüz bin kez tekrarlamak ve olası her sonucun kabaca (1 / taraf sayısı) çarpıldığını doğrulamaktır. 6 taraflı bir kalıp söz konusu olduğunda, her olası değerin zamanın yaklaşık% 16,6'sına çarptığını görmelisiniz. Herhangi biri yüzde daha fazla kapalıysa, bir sorununuz var demektir.

Bu şekilde önlenirse, rastgele bir sayı üretmenin altında yatan mekaniği kolayca ve en önemlisi testi değiştirmeden yeniden düzenlemenizi sağlar.


1
bu test, önceden tanımlanmış bir sırada yan yana tek tek ilmek yapan tamamen rastgele olmayan bir uygulama için geçecektir
gnat

1
Eğer bir kodlayıcı kötü niyetle bir şeyi (bir kalıpta rastgele bir ajan kullanmamakla) uygulamaya niyetliyse ve sadece 'kırmızı ışıkların yeşile dönmesini sağlamak' için bir şeyler bulmaya çalışmak, birim testinin gerçekten çözebileceğinden daha fazla probleminiz var demektir.
ChristopherBrown
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.