Unit testing for exceptions in Python constructor
Asked Answered
C

7

13

I'm just a beginner in Python and programming in general, and have some questions about the unittest module.

I have a class, and in the __init__ method I am doing some assertions to check for bad arguments. I would like to create a unittest which checks for such AssertionError when creating new instances.

In the unittest module, one can test (with assertRaises) for specific exception when a callable is called, but obviously that will apply to methods of the class. What is the proper way to run such test for the constructor?

I know that I can just try to create an instance of the class with bad arguments, and unittest will report a test failure, but that stops immediately after the first such exception, and even if I can wrap multiple tests in multiple test functions, it just doesn't seem elegant at all.

Chubby answered 15/6, 2009 at 17:10 Comment(0)
M
19

In the unittest module, one can test (with assertRaises) for specific exception when a callable is called, but obviously that will apply to methods of the class. What is the proper way to run such test for the constructor?

The constructor is itself a callable:

self.assertRaises(AssertionError, MyClass, arg1, arg2)

That being said, I want to echo nosklo's and S.Lott's concerns about doing type checking of arguments. Furthermore, you shouldn't be using assertions to check function arguments: assertions are most useful as sanity checks that won't get triggered unless something is internally wrong with your code. Also, assert statements are compiled out when Python is run in "optimized" -O mode. If a function needs to do some sort of checking of its arguments, it should raise a proper exception.

Massicot answered 15/6, 2009 at 17:34 Comment(2)
-1: Assertions are a great way to check arguments. Very short code: assert 0 <= n, "n is negative".Tanguay
I guess that's a matter of opinion, then.Massicot
T
7

Don't mess with assertRaises. It's too complicated.

Do this

class Test_Init( unittest.TestCase ):
    def test_something( self ):
        try:
            x= Something( "This Should Fail" )
            self.fail( "Didn't raise AssertionError" )
        except AssertionError, e:
            self.assertEquals( "Expected Message", e.message )
            self.assertEquals( args, e.args )

Any other exception will be an ordinary test error.

Also, don't mess with too much up-front error checking in an __init__ method. If someone provides an object of the wrong type, your code will fail in the normal course of events and raise a normal exception through normal means. You don't need to "pre-screen" objects all that much.

Tanguay answered 15/6, 2009 at 17:30 Comment(2)
Can you expand on "It's too complicated"? I've been having problems with assertRaises(assertionError, ...) (assertion is raised, but not caught by assertRaises), and it'd be interesting to know why...Coif
More annoying, this code doesn't really work with AssertionErrors, because they don't have a message property (at least in python 3).Coif
S
2

Don't know is this helps, but a problem I was having is as follows:

self.assertRaises(Exception, MyFunction())

Problem is I was not just passing MyFunction, but calling it as well, resulting in a fail and Exception being raised. Myfunction expects an argument, and I want it to fail if nothing is passed in. Frustrated me for a while till I figured it out:

self.assertRaises(Exception, MyFunction)

Works as expected.

Semeiology answered 9/4, 2013 at 18:36 Comment(0)
K
1

Well, as a start, checking for bad arguments is not a good idea in python. Python is dynamically strong typed for a reason.

You should just assume that the arguments are good arguments. You never know your class' users' intent so by checking for good arguments is a way to limit usage of your class in more generic instances.

Instead define a good API and document it very well, using docstrings and text, and leave errors of bad arguments flow automatically to the user.

Example:

def sum_two_values(value_a, value_b):
    return value_a + value_b

okay, this example is stupid, but if I check and assert the value as integer, the function won't work with floats, strings, list, for no reason other than my check, so why check in first place? It will automatically fail with types that it wouldn't work, so you don't have to worry.

Klinges answered 15/6, 2009 at 17:18 Comment(2)
Well, in my particular case I would like to assert that something has gone wrong early, since later I load some C libraries with ctypes and have to pass to them arguments from the constructor...Chubby
@asen_asenov: In that case, just wrap your ctypes call with the appropriate transformation e.g. ctypes.c_int(param). That will make sure the type is compatible and will raise an error for you on incompatible types automatically;Klinges
E
1

If you only want to check whether a constructor raises an exception, then it is much better to use a lambda:

    def testInsufficientArgs(self):
        self.assertRaises(ValueError, lambda: MyClass(0))

Like this, the constructor arguments are not set "magically" (like in @Miles' answer) and the IDE can always tell you where the constructor is used.

Etymologize answered 21/10, 2019 at 8:57 Comment(0)
S
0

S.Lott's answer is incorrect: self.fail() itself raises an exception, which will then be caused by the exception in the following line:

class NetworkConfigTest1(unittest.TestCase):
    def runTest(self):
        try:
            NetworkConfig("192.168.256.0/24")
            self.fail("Exception expected but not thrown")
        except Exception, error:
            printf("Exception caught: %s" % str(error)
            pass

The output was "Exception expected but not thrown", but the unit test was not marked as a failure, even though the code being tested had not been written!

A more valid way to check if a method raises an exception would be to use:

self.failUnlessRaises([error], [callable], [arguments to callable])

In my case, the class I'm testing is called NetworkConfig, and the constructor must throw an exception if the network descriptor is invalid. And what worked is:

class NetworkConfigTest1(unittest.TestCase):
    def runTest(self):
        self.failUnlessRaises(Exception, NetworkConfig, "192.168.256.0/24")

This works as desired and performs the correct test.

Soccer answered 21/9, 2009 at 14:4 Comment(0)
V
0

how to unit test a python function throwing error without using a test class or self argument :

def test_my_func():
    # if you are mocking some library make it throw error
    MagicMock().mock_function.side_effect = Exception("Something went wrong")
    # use unittest.TestCase() instead of self, ValueError is an example
    with unittest.TestCase().assertRaises(ValueError):
        my_func("hello world")
Varion answered 18/3 at 23:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.