2015-05-19 23 views
5

Son zamanlarda ünite testi en iyi uygulamalarında kendimi çok eğitmeye çalıştım. Çoğunluğu mükemmel bir anlam ifade ediyor, ama genellikle gözden kaçan ve/veya kötü bir şekilde açıklanmış bir şey var: Bir birim testinin nasıl bir işlevi var?Dekore edilmiş işlevler nasıl birim-test edilir?

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 


class A(object): 
    @stringify 
    def add_numbers(self, a, b): 
     """ 
     Returns the sum of `a` and `b` as a string. 
     """ 
     return a + b 

Açıkçası şu testler yazabilirsiniz:

def test_stringify(): 
    @stringify 
    def func(x): 
     return x 

    assert func(42) == "42" 

def test_A_add_numbers(): 
    instance = MagicMock(spec=A) 
    result = A.add_numbers.__wrapped__(instance, 3, 7) 
    assert result == 10 

Bu bana% 100 kapsama verir:

en Bu kodu olduğunu varsayalım biliyorum ki stringify() süslenmiş alır herhangi bir işlev sonucunu bir dize olarak alır ve oyulmamış A.add_numbers() işlevinin argümanlarının toplamını döndürdüğünü biliyorum. Bu nedenle, aktarma özelliğiyle, A.add_numbers()'un dekore edilmiş sürümü, argümanın toplamını bir dize olarak döndürmelidir. Her şey iyi görünüyor!

Ancak bundan tamamen memnun kalmıyorum: testlerim, yazdığım gibi başka bir dekoratör kullanacak olsam da yine de geçebilir (başka bir şey yaparsa, sonucu str'a çevirmek yerine 2 ile çarpın) . Benim fonksiyonum A.add_numbers artık doğru olmayacaktı, testler hala geçerdi. Harika değil.

A.add_numbers()'un dekore edilmiş sürümünü test edebilirdim, ancak dekoratörlerim zaten birim test edildiğinden beri ben fazla şey yapardım.

Burada bir şey eksik gibi hissediyorum. Birbirini dekore edilmiş işlevler için iyi bir strateji nedir?

cevap

1

Dekoratörlerimi ikiye ayırdım. Yani yerine sahip:

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 

Ben:

def to_string(value): 
    return str(value) 

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return to_string(func(*args)) 

    return wrapper 

beni daha sonra sade bir şekilde dekore fonksiyonu sahte-out to_string test ne zaman tanır hangi.

Açıkçası bu basit örnek olayda çok fazla görünebilir, ancak gerçekten karmaşık veya pahalı bir şey yapan bir dekoratör üzerinde kullanıldığında (bir DB'ye bağlantı açmak gibi), alay edebilmek çok güzel şey.

0

Kodunuzun genel arabirimini () sınayın. İnsanların sadece dekore edilmiş işlevleri çağırmasını bekliyorsanız, o zaman test etmeniz gereken şey budur. Dekoratör de halka açıksa, o zaman da test edin (test_stringify() ile yaptığınız gibi). Kişiler doğrudan onları aramadıkça sarılmış sürümleri sınayın.

+0

Soruyu biraz fazla hızlı okuduğunuzu düşünüyorum. Fonksiyonel testler yazmayı değil, birim testleri yapmayı düşünmüyorum. – ereOn

+1

'add_numbers'' stringify' tarafından dekore edilen başka bir işlev aslında bir uygulama detayıdır. 'Add_numbers' öğesinin amaçlandığı şekilde çalıştığını test edin ve farklı bir dekoratör (veya hiçbir dekoratör kullanmıyorsanız) fark etmez. – chepner

+0

@chepner: Birim testleri, uygulama ayrıntılarını test etmek için tam buradadır. Yine, eğer işlevsel/entegrasyon testleri hakkında konuşuyor olsaydık, kabul ederdim. Bu sadece konu dışı bir konu. – ereOn

1

Birim sınamasının en önemli yararlarından biri, yeniden kodlandırılan kodun daha önce yaptığınız gibi çalışmaya devam ettiği bir ölçüde güven ile yeniden hesaplamaya izin vermektir. Eğer

def add_numbers(a, b): 
    return str(a + b) 

def mult_numbers(a, b): 
    return str(a * b) 

ile başlamıştı varsayalım Size stringify yönetmeni kullanarak, her fonksiyonun ortak kısımlarını (bir dizede çıktı sarma) planı ayrı karar, Şimdi

def test_add_numbers(): 
    assert add_numbers(3, 5) == "8" 

def test_mult_numbers(): 
    assert mult_numbers(3, 5) == "15" 

gibi bazı testler olurdu .

def stringify(func): 
    @wraps(func) 
    def wrapper(*args): 
     return str(func(*args)) 

    return wrapper 

@stringify 
def add_numbers(a, b): 
    return a + b 

@stringify 
def mult_numbers(a, b): 
    return a * b 

Özgün testlerinizin bu yeniden işlemeden sonra çalışmaya devam ettiğini fark edeceksiniz. 'un'un nasıl olduğunu add_numbers ve mult_numbers; önemli olan, tanımlandığı gibi çalışmaya devam etmeleridir: istenen işlemin kesin bir sonucunu geri almak. senin test_stringify yapan bir dize olarak dekore fonksiyonun sonucunu döndürür:

kalmış tek testi yazmaya gerekstringify oyapmak amaçlanmaktadır yapar doğrulamak için biridir.


Kişisel sorun, açılmamış işlevini dekoratör, ve birimler olarak sarılmış fonksiyon ele alması gerektiğine gibi görünüyor. Ancak bu durumda, o zaman bir birim testini kaçırıyorsunuz: aslında add_wrapper'u çalıştıran ve sadece add_wrapper.__wrapped__ yerine çıktısını test eden. Sarılmış işlevi bir birim testi ya da bir entegrasyon testi olarak test etmeyi düşünürseniz gerçekten önemli değil, ama ne diyorsan onu yazman gerek, çünkü işaret ettiğin gibi, sadece sarılmamış fonksiyonu test etmek yeterli değildir. dekoratör ayrı ayrı.

+1

Korkarım ki çok basit bir örnek seçtim. Benim endişem şudur: stringify() 'bir şekilde yanlışsa ('str' yerine 'srt' yazdım 'dedim), şimdi benim dekore edilmiş işlevlerimin tüm testleri başarısız olur, fakat yapmamalılar: bunların uygulanması ** doğru ** henüz testleri başarısız. Bu, birim testlerine sahip olmanın bütün noktasını yenmesiyle kabul edilemez. – ereOn

+1

@ereOn: Ardından stringify() 'için ayrı bir test yapın. Bu da başarısız olur ve sonra bozulan dekoratör olduğunu bilirsiniz. – Kevin

+0

@Kevin: Tek satırlı bir değişiklik için başarısız olan birkaç ilgisiz testin olması, ilk etapta aşırı test yapıldığının açık bir işaretidir. Bu çok basit örnekte, ne kırdığı belli olacak. Çok daha karmaşık bir şekilde, çok değil. – ereOn

İlgili konular