Reputation: 66320
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
Reputation: 1
def testMyMethodExists(self):
""" Does MyMethod() exist?"""
self.assertTrue(hasattr(self.test, 'myMethod'))
Upvotes: 0
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_exists()
method_exists()
- or better is_callable()
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
Reputation: 17178
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.
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
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
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