【问题标题】:How to check if a method exists in TDD?如何检查 TDD 中是否存在方法?
【发布时间】:2014-04-11 22:41:46
【问题描述】:

在严格的测试驱动开发方法中,每个步骤都需要事先进行测试。甚至一个类或其方法的存在 - 严格来说 - 在它们实际创建之前都需要进行测试。

我在编写测试以查看方法是否存在时遇到问题。

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)

在这两种情况下,如果类不存在,Python 就已经失败了。测试永远不会断言。有没有更好的方法来实现这一点?

【问题讨论】:

  • 你要在哪里测试类的存在?在一个模块中?
  • 你不知道它们是否存在的类型名称来自哪里?
  • Robert Martin 等一些 TDD 人员认为“编译错误”(或本例中的运行时错误)与失败的测试相同。您是否可以真正将其变为断言并不重要。只需将错误视为测试失败的同义词。

标签: python unit-testing python-2.7


【解决方案1】:

类型的存在

例外情况

如果一个类没有定义,尝试使用它会抛出一个非常具体的错误,所以解决这个问题的一种方法是捕获该错误(大概在setUp,或者它第一次使用的地方),并且当场失败。

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

请记住,这可能掩盖了一些错误的原因:例如,如果RandomGenClass.__init__ 引发NameError。因此,对于您的情况,您可能需要更仔细地查看引发的错误,以确保它是未定义的 "RandomGenClass",而不是更深层次的名称。


globals()

所以,也许一个更好的方法来完成这个(对于你的用例,无论如何)是通过在globals()返回的字典中查找你希望使用的类的名称,但我个人认为这有点更丑更容易出问题。但它不存在掩盖其他错误的问题。

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

hasattr

如果类存在于其他模块中(可能,但不一定如此),我们可以在模块对象上使用hasattr,就像我们测试方法一样(如下)。这可能是总体上最干净的方式。

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

根据类的来源,您实际上可能能够更早地捕捉到它。如果您要从定义它们的位置导入类,则可以显式导入它们,如果未定义类,则查找 ImportError


方法的存在

至于方法测试,那部分很简单。一旦您知道该类存在并且您拥有它的一个实例,您就可以使用hasattr 来确定是否为该对象定义了一个给定的名称。

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

当然,您也可以使用与测试类是否存在几乎完全相同的方式来执行此操作:继续执行此操作,并在错误发生时捕获错误。同样,这需要某种验证,以确保您捕获的属性错误实际上是您正在寻找的错误。

try:
    result = self.test.myMethod()
except AttributeError as e:
    pass # fail appropriately

【讨论】:

  • 谢谢,这将涵盖课堂部分。关于该方法的第二次测试有什么建议吗?
  • @Hooman 我已经编辑了我的答案以包含方法内容。
  • +1。但是,不确定“更好的方式”是否真的更好。可能在实际代码中,要测试的类不应该位于globals() 中,而是位于另一个模块中,并且需要hasattr() 来检查是否存在。基于异常的解决方案始终有效。
  • @Ellioh 是的,原始问题中仍有一些实现细节尚不清楚。他在示例代码中使用了不合格的类名,所以可能是from mod import * 的情况。无论哪种方式,我都会在课堂存在部分添加关于getattr 的注释。
  • 顺便说一句,您可以将对象创建与获取类分开。只需将该类分配给一个局部变量,然后在成功的情况下在try 块之外实例化它。
【解决方案2】:

documentation for unittest 包含一个调用 assertRaises 的示例:

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()

未知方法引发 AttributeError,也许将其作为 assertRaises 子句的一部分捕获是一种方法?

【讨论】:

  • 我之前试过self.assertRaises(AttributeError, test.MyMethod)。然而,这是我需要的相反。如果方法不存在,这里会吞下异常并让测试通过。你会如何否定self.assertRaises
【解决方案3】:

检查方法是否存在可以使用hasattr 来完成,如下所示

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

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

输出将是

True
False

这也适用于模块中定义的类或方法,因此如果您要检查它是否存在的类是在模块中编写的,您可以使用相同的方法,例如:

import logging

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

【讨论】:

    【解决方案4】:

    如何检查TDD中是否存在方法?

    这个问题被标记为 Python,但 TDD 无处不在。所以这里是一个基本的 PHP 示例...

    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函数

    • 类:class_exists()
    • 方法:method_exists() - 或更好的is_callable()
    • 分机号:extension_loaded()

    也可以使用Reflection 来检查类和函数/方法是否存在。 以下方法是一个非常常见的辅助函数,因为它适用于方法和函数。

    /**
     * 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));
    }
    

    【讨论】:

    • 太糟糕了,没有徽章“跨语言答案”:)
    猜你喜欢
    • 2011-11-26
    • 2014-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-06
    • 2019-09-09
    • 2011-10-26
    相关资源
    最近更新 更多