Houman
Houman

Reputation: 66320

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?

Upvotes: 3

Views: 7009

Answers (5)

Max Melnichuk
Max Melnichuk

Reputation: 1

def testMyMethodExists(self):
    """ Does MyMethod() exist?"""
    self.assertTrue(hasattr(self.test, 'myMethod'))

Upvotes: 0

Jens A. Koch
Jens A. Koch

Reputation: 41776

How to check if a method exists in TDD?

The question is tagged as Python, but TDD is everywhere. So here's a basic PHP example...

class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        // if the class is not existing, one would skip the method tests
        if (!class_exists('DatabaseHelper', false)) {
           $this->markTestSkipped(
              'DatabaseHelper class not available. Skipping all method tests.'
           );
        }

        // subject under test
        $database = new Database;

        // i wouldn't go so far, as to test the methods of the class, anyway..
        if(!is_callable(array($database, 'myMethod'))) {
            $this->markTestSkipped(
              'DatabaseHelper class does not contain method myMethod. '.
              'Skipping all method tests.'
            );
        }

        // this is very often used: if an PHP extension is not available
        if (!extension_loaded('mysqli')) {
            $this->markTestSkipped(
              'The MySQLi extension is not available.'
            );
        }
    }

    public function testConnection()
    {
        // do Db thing ...

        // assert method of a class
        $this->assertTrue(
           method_exists('DatabaseHelper', 'myHelperFunction'), 
          'Class DatabaseHelper does not have method myHelperFunction'
        );
    }
}

PHP functions used

  • Class: class_exists()
  • Method: method_exists() - or better is_callable()
  • Extension: extension_loaded()

It's also possible to use Reflection to check for Class and Function/Method existance. The following method is a pretty common helper function, because it works for methods and functions.

/**
 * Assert that a class has a method 
 *
 * @param string $class name of the class
 * @param string $method name of the searched method
 * @throws ReflectionException if $class don't exist
 * @throws PHPUnit_Framework_ExpectationFailedException if a method isn't found
 */
function assertMethodExist($class, $method) {
    $oReflectionClass = new ReflectionClass($class); 
    assertThat("method exist", true, $oReflectionClass->hasMethod($method));
}

Upvotes: -1

Henry Keiter
Henry Keiter

Reputation: 17178

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

Upvotes: 5

Ronny Andersson
Ronny Andersson

Reputation: 1687

Checking that a method exists can be done with hasattr as shown in

class A(object):
    def MethodInA(self):
        pass

print hasattr(A, 'MethodInA')
print hasattr(A, 'RandomMethodNameNotInA')

The output would be

True
False

This also works on classes or methods defined in modules, so if your class that you want to check if it exists is written in a module you can use the same approach, for example:

import logging

print hasattr(logging, "LogRecord")
print hasattr(logging, "LogRecords")

Upvotes: 2

hd1
hd1

Reputation: 34677

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?

Upvotes: 2

Related Questions