Domanda

Come si scrive un unestest che fallisce solo se una funzione non genera un'eccezione prevista?

È stato utile?

Soluzione

Usa TestCase.assertRaises (o TestCase.failUnlessRaises) dal modulo unittest, ad esempio:

import mymod

class MyTestCase(unittest.TestCase):
    def test1(self):
        self.assertRaises(SomeCoolException, mymod.myfunc)

Altri suggerimenti

A partire da Python 2.7 è possibile utilizzare il Gestore di contesto per ottenere l'oggetto dell'eccezione effettiva generato:

import unittest

def broken_function():
    raise Exception('This is broken')

class MyTestCase(unittest.TestCase):
    def test(self):
        with self.assertRaises(Exception) as context:
            broken_function()

        self.assertTrue('This is broken' in context.exception)

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

http://docs.python.org/dev/ biblioteca / unittest.html # unittest.TestCase.assertRaises


In Python 3.5 , devi avvolgere context.exception in str, altrimenti otterrai un TypeError

self.assertTrue('This is broken' in str(context.exception))

Il codice nella mia risposta precedente può essere semplificato in:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction)

E se afunction accetta argomenti, passali in assertRaises in questo modo:

def test_afunction_throws_exception(self):
    self.assertRaises(ExpectedException, afunction, arg1, arg2)
  

Come testare che una funzione Python genera un'eccezione?

     

Come si scrive un test che fallisce solo se una funzione non viene lanciata   un'eccezione prevista?

Risposta breve:

Utilizza il metodo self.assertRaises come gestore di contesto:

    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'

Manifestazione

L'approccio delle migliori pratiche è abbastanza facile da dimostrare in una shell Python.

La unittest biblioteca

In Python 2.7 o 3:

import unittest

In Python 2.6, puoi installare un backport della libreria assertRaises di 2.7, chiamato unittest2 e solo alias come 1:

import unittest2 as unittest

Test di esempio

Ora, incolla nella tua shell Python il seguente test di sicurezza del tipo di Python:

class MyTestCase(unittest.TestCase):
    def test_1_cannot_add_int_and_str(self):
        with self.assertRaises(TypeError):
            1 + '1'
    def test_2_cannot_add_int_and_str(self):
        import operator
        self.assertRaises(TypeError, operator.add, 1, '1')

Test one utilizza '1' come gestore di contesto, che garantisce che l'errore venga correttamente rilevato e ripulito, mentre è registrato.

Potremmo anche scriverlo senza il gestore di contesto, vedi test due. Il primo argomento sarebbe il tipo di errore che ci si aspetta di generare, il secondo argomento, la funzione che si sta testando e gli argomenti rimanenti e gli argomenti della parola chiave verranno passati a quella funzione.

Penso che sia molto più semplice, leggibile e gestibile solo per usare il gestore del contesto.

Esecuzione dei test

Per eseguire i test:

unittest.main(exit=False)

In Python 2.6, probabilmente ti serve quanto segue :

unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

E il tuo terminale dovrebbe produrre quanto segue:

..
----------------------------------------------------------------------
Ran 2 tests in 0.007s

OK
<unittest2.runner.TextTestResult run=2 errors=0 failures=0>

E vediamo che, come previsto, il tentativo di aggiungere un TypeError e un <=> risulta in un <=>.


Per un output più dettagliato, prova questo:

unittest.TextTestRunner(verbosity=2).run(unittest.TestLoader().loadTestsFromTestCase(MyTestCase))

Il tuo codice dovrebbe seguire questo schema (questo è un test di stile del modulo unittest):

def test_afunction_throws_exception(self):
    try:
        afunction()
    except ExpectedException:
        pass
    except Exception as e:
       self.fail('Unexpected exception raised:', e)
    else:
       self.fail('ExpectedException not raised')

Su Python < 2.7 questo costrutto è utile per verificare valori specifici nell'eccezione prevista. La funzione unittest assertRaises verifica solo se è stata sollevata un'eccezione.

da: http://www.lengrand.fr/2011 / 12 / pythonunittest-assertRaises-raise-error /

Innanzitutto, ecco la funzione corrispondente (ancora dum: p) nel file dum_function.py:

def square_value(a):
   """
   Returns the square value of a.
   """
   try:
       out = a*a
   except TypeError:
       raise TypeError("Input should be a string:")

   return out

Ecco il test da eseguire (è inserito solo questo test):

import dum_function as df # import function module
import unittest
class Test(unittest.TestCase):
   """
      The class inherits from unittest
      """
   def setUp(self):
       """
       This method is called before each test
       """
       self.false_int = "A"

   def tearDown(self):
       """
       This method is called after each test
       """
       pass
      #---
         ## TESTS
   def test_square_value(self):
       # assertRaises(excClass, callableObj) prototype
       self.assertRaises(TypeError, df.square_value(self.false_int))

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

Ora siamo pronti per testare la nostra funzione! Ecco cosa succede quando si tenta di eseguire il test:

======================================================================
ERROR: test_square_value (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_dum_function.py", line 22, in test_square_value
    self.assertRaises(TypeError, df.square_value(self.false_int))
  File "/home/jlengrand/Desktop/function.py", line 8, in square_value
    raise TypeError("Input should be a string:")
TypeError: Input should be a string:

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

TypeError è actullay sollevato e genera un errore di test. Il problema è che questo è esattamente il comportamento che volevamo: s.

Per evitare questo errore, esegui semplicemente la funzione usando lambda nella chiamata di prova:

self.assertRaises(TypeError, lambda: df.square_value(self.false_int))

L'output finale:

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Perfetto!

... e anche per me è perfetto !!

Grazie mille Julien Lengrand-Lambert

Puoi creare il tuo contextmanager per verificare se l'eccezione è stata sollevata.

import contextlib

@contextlib.contextmanager
def raises(exception):
    try:
        yield 
    except exception as e:
        assert True
    else:
        assert False

E quindi puoi usare raises in questo modo:

with raises(Exception):
    print "Hola"  # Calls assert False

with raises(Exception):
    raise Exception  # Calls assert True

Se stai usando pytest, questa cosa è già implementata. Puoi fare pytest.raises(Exception):

Esempio:

def test_div_zero():
    with pytest.raises(ZeroDivisionError):
        1/0

E il risultato:

pigueiras@pigueiras$ py.test
================= test session starts =================
platform linux2 -- Python 2.6.6 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python
collected 1 items 

tests/test_div_zero.py:6: test_div_zero PASSED

Uso doctest [1] quasi ovunque perché mi piace il fatto che documento e collaudo le mie funzioni allo stesso tempo.

Dai un'occhiata a questo codice:

def throw_up(something, gowrong=False):
    """
    >>> throw_up('Fish n Chips')
    Traceback (most recent call last):
    ...
    Exception: Fish n Chips

    >>> throw_up('Fish n Chips', gowrong=True)
    'I feel fine!'
    """
    if gowrong:
        return "I feel fine!"
    raise Exception(something)

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Se inserisci questo esempio in un modulo ed eseguilo dalla riga di comando, entrambi i casi di test vengono valutati e controllati.

[1] Documentazione Python: 23.2 doctest - Prova esempi interattivi di Python

Dai un'occhiata al assertRaises di il unittest modulo.

Ho appena scoperto che il Mock library fornisce un metodo assertRaisesWithMessage () ( nella sua sottoclasse unittest.TestCase), che controllerà non solo che venga sollevata l'eccezione prevista, ma anche che venga generata con il messaggio previsto:

from testcase import TestCase

import mymod

class MyTestCase(TestCase):
    def test1(self):
        self.assertRaisesWithMessage(SomeCoolException,
                                     'expected message',
                                     mymod.myfunc)

Puoi usare assertRaises dal modulo unittest

import unittest

class TestClass():
  def raises_exception(self):
    raise Exception("test")

class MyTestCase(unittest.TestCase):
  def test_if_method_raises_correct_exception(self):
    test_class = TestClass()
    # note that you dont use () when passing the method to assertRaises
    self.assertRaises(Exception, test_class.raises_exception)

Ci sono molte risposte qui. Il codice mostra come possiamo creare un'eccezione, come possiamo usare quell'eccezione nei nostri metodi e, infine, come puoi verificare in unittest, sollevare le eccezioni corrette.

import unittest

class DeviceException(Exception):
    def __init__(self, msg, code):
        self.msg = msg
        self.code = code
    def __str__(self):
        return repr("Error {}: {}".format(self.code, self.msg))

class MyDevice(object):
    def __init__(self):
        self.name = 'DefaultName'

    def setParameter(self, param, value):
        if isinstance(value, str):
            setattr(self, param , value)
        else:
            raise DeviceException('Incorrect type of argument passed. Name expects a string', 100001)

    def getParameter(self, param):
        return getattr(self, param)

class TestMyDevice(unittest.TestCase):

    def setUp(self):
        self.dev1 = MyDevice()

    def tearDown(self):
        del self.dev1

    def test_name(self):
        """ Test for valid input for name parameter """

        self.dev1.setParameter('name', 'MyDevice')
        name = self.dev1.getParameter('name')
        self.assertEqual(name, 'MyDevice')

    def test_invalid_name(self):
        """ Test to check if error is raised if invalid type of input is provided """

        self.assertRaises(DeviceException, self.dev1.setParameter, 'name', 1234)

    def test_exception_message(self):
        """ Test to check if correct exception message and code is raised when incorrect value is passed """

        with self.assertRaises(DeviceException) as cm:
            self.dev1.setParameter('name', 1234)
        self.assertEqual(cm.exception.msg, 'Incorrect type of argument passed. Name expects a string', 'mismatch in expected error message')
        self.assertEqual(cm.exception.code, 100001, 'mismatch in expected error code')


if __name__ == '__main__':
    unittest.main()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top