Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if a method exists in TDD?

In a strict Test Driven development methodology every step is required to be tested before hand. Even the existence of a class or of its methods have - strictly speaking - to be tested before they are actually even created.

I have a problem to make write tests to see if a method exists.

class BasicTests(unittest.TestCase):

    def setUp(self):
        self.test = RandomGenClass()        

    def testRandomGenClassExists(self):
        """ Does the class exist? """        
        self.assert_(self.test is not None)

    def testMyMethodExists(self):
        """ Does MyMethod() exist?"""
        result = self.test.myMethod()
        self.assert_(result is not None)

In both cases Python would already fail if the class didn't exist. The test never gets to assert. Is there a better way to achieve this?

like image 579
Houman Avatar asked Dec 12 '25 11:12

Houman


2 Answers

Existence of Types

With Exceptions

If a class isn't defined, trying to use it will throw a very specific error, so one way to go about this is to catch that error (in setUp, presumably, or wherever it's first used), and fail right then and there.

def setUp(self):
    try:
        self.test = RandomGenClass()
    except NameError as e:
        pass # fail appropriately here.

Bear in mind that this could mask the cause of some errors: for instance, if RandomGenClass.__init__ raises a NameError. So for your case, you'd probably have to look a little more closely at the error that's raised, to make sure that it's "RandomGenClass" that's undefined, and not some deeper name.


With globals()

So, perhaps a better way to accomplish this (for your use case, anyway) is by looking in the dictionary returned by globals() for the name of the class you wish to use, but personally I think this is a little uglier and more prone to issues. It doesn't have the problem of masking other errors though.

if not 'RandomGenClass' in globals():
    pass # fail appropriately

With hasattr

If the classes exist in other modules (likely, but not necessarily the case), we can use hasattr on the module objects in the same way as we'll test for methods (below). This is probably the cleanest way overall.

import some_module
if not hasattr(some_module, 'ClassName'):
    pass # fail appropriately

Depending on where the class originates, you might actually be able to catch it earlier. If you're importing classes from wherever they're defined, you could just import them all explicitly, and look for ImportError if a class is undefined.


Existence of Methods

As for method testing, that part's easy. Once you know that the class exists and you've got an instance of it, you can use hasattr to determine whether a given name is defined for that object.

if hasattr(self.test, 'method_name'):
    result = self.test.method_name()

Of course, you can also do this in almost exactly the same way as you tested for the existence of the class: by going ahead and doing it, and catching the error if it blows up. Again, this one would require some kind of validation that the attribute error you're catching is actually the one you're looking for.

try:
    result = self.test.myMethod()
except AttributeError as e:
    pass # fail appropriately
like image 149
Henry Keiter Avatar answered Dec 15 '25 10:12

Henry Keiter


The documentation for unittest contains an example where assertRaises is invoked:

import random
import unittest

class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
        self.seq = range(10)

    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
        random.shuffle(self.seq)
        self.seq.sort()
        self.assertEqual(self.seq, range(10))

        # should raise an exception for an immutable sequence
        self.assertRaises(TypeError, random.shuffle, (1,2,3)) 

    def test_choice(self):
        element = random.choice(self.seq)
        self.assertTrue(element in self.seq)

    def test_sample(self):
        with self.assertRaises(ValueError):
            random.sample(self.seq, 20)
        for element in random.sample(self.seq, 5):
            self.assertTrue(element in self.seq)

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

An unknown method raises an AttributeError, perhaps catching that as part of an assertRaises clause is an approach?

like image 38
hd1 Avatar answered Dec 15 '25 08:12

hd1



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!