Python unittest - assertRaises karşısında mı?


374

Belirli bir durumda bir İstisna oluşturulmadığını tespit etmek için bir test yazmak istiyorum.

İstisna eğer testine basittir edilir kaldırdı ...

sInvalidPath=AlwaysSuppliesAnInvalidPath()
self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath) 

... ancak bunu nasıl tersini .

Böyle bir şey peşindeyim ben ...

sValidPath=AlwaysSuppliesAValidPath()
self.assertNotRaises(PathIsNotAValidOne, MyObject, sValidPath) 

6
Her zaman testte çalışması gereken her şeyi yapabilirsiniz. Bir hata ortaya çıkarsa, bu görünecektir (hata yerine hata olarak sayılır). Tabii ki, bu sadece tanımlanmış bir hata türü yerine herhangi bir hata yaratmadığını varsayar. Bunun dışında sanırım kendi yazılarınızı yazmak zorunda kalacaksınız.
Thomas K


Aslında assertNotRaises, kodunun / davranışının% 90'ını assertRaisesyaklaşık ~ 30-ish kod satırında paylaşan bir yöntem uygulayabileceğiniz ortaya çıkıyor . Ayrıntılar için aşağıdaki cevabıma bakın .
tel

Bunu istiyorum, böylece hypothesisorijinalin bir istisna yarattığı durumları göz ardı ederken, her türlü giriş için aynı çıktıyı ürettiklerinden emin olmak için iki işlevi karşılaştırabilirim . assume(func(a))çıktı belirsiz belirsiz bir değere sahip bir dizi olabilir çünkü çalışmıyor. Bu yüzden sadece bir işlevi çağırmak ve Truebaşarısız olmazsa almak istiyorum. assume(func(a) is not None)eserler sanırım
endolith

Yanıtlar:


394
def run_test(self):
    try:
        myFunc()
    except ExceptionType:
        self.fail("myFunc() raised ExceptionType unexpectedly!")

32
@hiwaylon - Hayır, aslında doğru çözüm bu. User9876 tarafından önerilen çözüm kavramsal olarak kusurludur: sözün yükselmemesini test ederseniz ValueError, ancak ValueErrorbunun yerine yükseltilirseniz, testiniz hata değil, başarısızlık koşuluyla çıkmalıdır . Öte yandan, aynı kodu çalıştırırsanız, KeyErrorbir hata değil, bir hata olur. Python'da - diğer dillerden farklı olarak - kontrol akışı için rutin olarak istisnalar kullanılır, bu yüzden except <ExceptionName>gerçekten sözdizimine sahibiz . Bu bağlamda, user9876'nın çözümü basitçe yanlıştır.
mac

@mac - Bu da doğru bir çözüm müdür? stackoverflow.com/a/4711722/6648326
MasterJoe2

Bunun testler için <% 100 kapsam göstermesi (hariç asla olmayacak) talihsiz bir etkisi vardır.
Shay

3
@Shay, IMO test dosyalarını her zaman kapsama raporlarından hariç tutmalısınız (neredeyse her zaman% 100 tanım olarak çalıştıklarından, raporları yapay olarak şişirirsiniz)
Orijinal Barbekü Sosu

@ original-barbekü sosu, beni istemeden atlanan testlere açık bırakmaz. Örneğin, test adındaki yazım hatası (ttst_function), pycharm'da yanlış çalıştırma yapılandırması vb.?
Shay

67

Merhaba - Belirli bir durumda bir İstisna oluşturulmadığını tespit etmek için bir test yazmak istiyorum.

Bu varsayılan varsayımdır - istisnalar artırılmaz.

Başka bir şey söylemezseniz, bu her testte varsayılır.

Bunun için herhangi bir iddiada bulunmak zorunda değilsiniz.


7
@IndradhanushGupta Kabul edilen cevap testi bundan daha pitonik yapar. Açık, örtük olmaktan iyidir.
0xc0de

17
Başka hiçbir yorumcu bu cevabın neden yanlış olduğunu işaret etmedi, ancak user9876'nın cevabının yanlış olmasının nedeni de budur: hatalar ve hatalar test kodunda farklı şeylerdir. Fonksiyonun Eğer vardı değil assert yapan bir test sırasında bir özel durum için test çerçevesi bir şekilde o davranacağını hata doğrusu değil assert bir -Hatadan.
coredumperror

@CoreDumpError Bir hata ve hata arasındaki farkı anlıyorum, ancak bu sizi her testi bir deneme / istisna yapısıyla sarmaya zorlamaz mı? Veya bunu yalnızca belirli bir koşul altında açıkça bir istisna oluşturan testler için yapmanızı önerir misiniz (temel olarak istisnanın beklendiği anlamına gelir ).
federicojasson

4
@federicojasson İkinci cümlede kendi sorunuzu oldukça iyi cevapladınız. Testlerdeki hatalar ve hatalar sırasıyla "beklenmedik çökmeler" ve "istenmeyen davranışlar" olarak tanımlanabilir. İşlevleriniz çöktüğünde testlerinizin bir hata göstermesini istersiniz, ancak farklı girdiler verildiğinde belirli girdiler verildiğinde bu özel durumun atılacağını bildiğinizde değil.
coredumperror

52

Sadece fonksiyonu çağırınız. Bir istisna ortaya çıkarsa, birim test çerçevesi bunu bir hata olarak işaretler. Yorum eklemek isteyebilirsiniz, örneğin:

sValidPath=AlwaysSuppliesAValidPath()
# Check PathIsNotAValidOne not thrown
MyObject(sValidPath)

35
Hatalar ve Hatalar kavramsal olarak farklıdır. Ayrıca, python'da istisnalar kontrol akışı için rutin olarak kullanıldığından, mantığınızı veya kodunuzu
mac

1
Ya testin geçiyor ya da geçmiyor. Eğer geçmezse, düzeltmek zorunda kalacaksınız. "Hata" veya "hata" olarak rapor edilip edilmemesi çoğunlukla önemsizdir. Bir fark var: Cevabımla yığın izini göreceksiniz, böylece PathIsNotAValidOne'un nereye atıldığını görebilirsiniz; kabul edilen cevapla bu bilgilere sahip olmayacaksınız, bu nedenle hata ayıklama daha zor olacaktır. (Py2 varsayarsak; Py3'ün bundan daha iyi olup olmadığından emin değilim).
user9876

19
@ user9876 - Hayır. Test çıkış koşulları 3'tür (pass / nopass / error), yanlış düşündüğünüz gibi 2 değil. Hatalar ve hatalar arasındaki fark büyüktür ve bunlara aynıymış gibi davranmak sadece zayıf programlamadır. Bana inanmıyorsanız, test koşucularının nasıl çalıştığına ve hata ve hatalardan hangi karar ağaçlarını uyguladıklarına bakın. Piton için iyi bir başlangıç noktasıdır dekoratör pytest içinde. xfail
mac

4
Sanırım bu birim testleri nasıl kullandığınıza bağlı. Ekibimin birim testleri kullanma şekli, hepsinin geçmesi gerekiyor. (Tüm birim testlerini yürüten sürekli bir entegrasyon makinesi ile çevik programlama). Test durumunun "başarılı", "başarısız" veya "hata" bildirebileceğini biliyorum. Ancak yüksek seviyede ekibim için asıl önemli olan "tüm birim testleri geçiyor mu?" (yani "Jenkins yeşil mi?"). Ekibim için "başarısız" ve "hata" arasında pratik bir fark yoktur. Birim testlerinizi farklı bir şekilde kullanırsanız farklı gereksinimleriniz olabilir.
user9876

1
@ user9876 'başarısız' ve 'hata' arasındaki fark, "onaycım başarısız oldu" ve "testim onaylayıcıya bile ulaşmadı" arasındaki farktır. Bu, benim için, testlerin düzeltilmesi sırasında faydalı bir ayrımdır, ancak sanırım, dediğin gibi, herkes için değil.
CS

14

Ben orijinal posteriyim ve yukarıdaki cevabı ilk olarak kodda kullanmadan DGH tarafından kabul ettim.

Bir kez kullandım ben aslında ne yapmak için ne yapmak için biraz tweaking gerektiğini fark (o "ya da benzer bir şey" dedi DGH adil olmak!).

Ben burada başkalarının yararına tweak göndermeye değer olduğunu düşündüm:

    try:
        a = Application("abcdef", "")
    except pySourceAidExceptions.PathIsNotAValidOne:
        pass
    except:
        self.assertTrue(False)

Burada yapmaya çalıştığım şey, ikinci bir argüman alanı olan bir Application nesnesini başlatmak için bir girişimde bulunulması halinde pySourceAidExceptions.PathIsNotAValidOne öğesinin yükseltilmesini sağlamaktı.

Yukarıdaki kodu (ağırlıklı olarak DGH'nin cevabına dayanarak) kullanmanın bunu yapacağına inanıyorum.


2
Sorunuzu açıklığa kavuşturduğunuzdan ve yanıtlamadığınızdan, düzenlemiş olmalısınız (cevaplamamış olmalısınız).
hiwaylon

13
Orijinal sorunun tam tersi gibi görünüyor. self.assertRaises(PathIsNotAValidOne, MyObject, sInvalidPath)bu durumda işi yapmalı.
Antony Hatchkins

8

Sen tanımlayabilirsiniz assertNotRaisesorijinal uygulanması% 90 yaklaşık yeniden kullanarak assertRaisesiçinde unittestmodül. Bu yaklaşımla, assertNotRaisesters başarısızlık durumunun yanı sıra aynı şekilde davranan bir yöntem elde edersiniz assertRaises.

TLDR ve canlı demo

Bir assertNotRaisesyöntem eklemek şaşırtıcı derecede kolay çıkıyor unittest.TestCase(bu yanıtı kod yazarken yazmak yaklaşık 4 kat sürdü). İşte metodun canlı bir demosuassertNotRaises . Sadece gibiassertRaises , ya bir çağrılabilir ve args geçebilir assertNotRaisesveya bir onu kullanabilirsiniz withaçıklamada. Canlı demo, assertNotRaisesamaçlandığı gibi çalıştığını gösteren bir test örneği içerir .

ayrıntılar

Uygulanması assertRaiseshalinde unittestoldukça karmaşık, fakat zeki subclassing birazcık ile geçersiz kılmak ve başarısızlık durumunu tersine çevirebilir.

assertRaisestemel olarak unittest.case._AssertRaisesContextsınıfın bir örneğini oluşturan ve onu döndüren kısa bir yöntemdir ( unittest.casemodülün tanımına bakın ). Kendi _AssertNotRaisesContextsınıfınızı yöntemini alt sınıflandırarak _AssertRaisesContextve geçersiz kılarak tanımlayabilirsiniz __exit__:

import traceback
from unittest.case import _AssertRaisesContext

class _AssertNotRaisesContext(_AssertRaisesContext):
    def __exit__(self, exc_type, exc_value, tb):
        if exc_type is not None:
            self.exception = exc_value.with_traceback(None)

            try:
                exc_name = self.expected.__name__
            except AttributeError:
                exc_name = str(self.expected)

            if self.obj_name:
                self._raiseFailure("{} raised by {}".format(exc_name,
                    self.obj_name))
            else:
                self._raiseFailure("{} raised".format(exc_name))

        else:
            traceback.clear_frames(tb)

        return True

Normalde, test senaryosu sınıflarını devralmalarını sağlayarak tanımlarsınız TestCase. Bunun yerine bir alt sınıftan miras alırsanız MyTestCase:

class MyTestCase(unittest.TestCase):
    def assertNotRaises(self, expected_exception, *args, **kwargs):
        context = _AssertNotRaisesContext(expected_exception, self)
        try:
            return context.handle('assertNotRaises', args, kwargs)
        finally:
            context = None

tüm test senaryolarınızda artık bu assertNotRaisesyöntemlerin kullanabileceği bir yöntem bulunacaktır .


Nerede senin olduğu tracebackiçin de elseaçıklamada gelen?
NOhs

1
@NOhs Bir eksik vardı import. Sabit
tel

2
def _assertNotRaises(self, exception, obj, attr):                                                                                                                              
     try:                                                                                                                                                                       
         result = getattr(obj, attr)                                                                                                                                            
         if hasattr(result, '__call__'):                                                                                                                                        
             result()                                                                                                                                                           
     except Exception as e:                                                                                                                                                     
         if isinstance(e, exception):                                                                                                                                           
            raise AssertionError('{}.{} raises {}.'.format(obj, attr, exception)) 

parametreleri kabul etmeniz gerekirse değiştirilebilir.

gibi ara

self._assertNotRaises(IndexError, array, 'sort')

1

Maymun yamasını unittestaşağıdaki gibi yararlı buldum :

def assertMayRaise(self, exception, expr):
  if exception is None:
    try:
      expr()
    except:
      info = sys.exc_info()
      self.fail('%s raised' % repr(info[0]))
  else:
    self.assertRaises(exception, expr)

unittest.TestCase.assertMayRaise = assertMayRaise

Bu, bir istisna olmadığında test yaparken niyeti açıklar:

self.assertMayRaise(None, does_not_raise)

Bu aynı zamanda sık sık kendimi yaparken bulduğum bir döngüde testi basitleştirir:

# ValueError is raised only for op(x,x), op(y,y) and op(z,z).
for i,(a,b) in enumerate(itertools.product([x,y,z], [x,y,z])):
  self.assertMayRaise(None if i%4 else ValueError, lambda: op(a, b))

Maymun yaması nedir?
ScottMcC

1
Bkz. En.wikipedia.org/wiki/Monkey_patch . Ekledikten sonra assertMayRaiseiçin unittest.TestSuitesize sadece bir 's kısmını taklit edebilir unittestkütüphanede.
Ocak 2018'de AndyJost

0

Bir Exception sınıfını iletirseniz assertRaises(), bir bağlam yöneticisi sağlanır. Bu, testlerinizin okunabilirliğini artırabilir:

# raise exception if Application created with bad data
with self.assertRaises(pySourceAidExceptions.PathIsNotAValidOne):
    application = Application("abcdef", "")

Bu, kodunuzdaki hata durumlarını test etmenizi sağlar.

Bu durumda, PathIsNotAValidOneUygulama yapıcısına geçersiz parametreler ilettiğinizde yükseltildiğini test edersiniz.


1
İstisna Hayır, bu sadece başarısız olacaktır değildir bağlam yöneticisi bloğu içinde büyüdü. 'Self.assertRaises ile (TypeError): geçen TypeError' ile kolayca test edilebilir.
Matthew Trevor

@MatthewTrevor İyi çağrı. Hatırladığım gibi, kod test etmek yerine doğru bir şekilde yürütülür, yani yükselmez, test hata durumlarını öneriyordum. Cevabı buna göre düzenledim. Umarım kırmızıdan kurtulmak için +1 kazanabilirim. :)
hiwaylon

Not, bu da Python 2.7 ve üstü: docs.python.org/2/library/…
qneill

0

böyle deneyebilirsiniz. try: self.assertRaises (Yok, işlev, arg1, arg2) hariç: kodu kodun içine koymazsanız deneyin 'AssertionError: Hiçbiri yükseltilmez' istisnasıyla geçer ve test durumu başarısız olur. içine koymak eğer beklenen davranış olan blok deneyin.


0

Nesnenin hatasız olarak başlatılmasını sağlamanın düz bir yolu, nesnenin tür örneğini sınamaktır.

İşte bir örnek :

p = SomeClass(param1=_param1_value)
self.assertTrue(isinstance(p, SomeClass))
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.