星期一, 十一月 19, 2007

python further than "unittest by permutation"

python Permutation 和 unittest 耗时测试隔离 中,讨论了使用排列来进行更全面的一个测试。这种方法的一个问题是耗时太长,P(6, 6)=720 耗时 ~45s,最好的情况也需要 ~30s,则 P(7, 7) 则需要耗时最少 ~3.5min,>P(8, 8) 看上去就让人无法接受了。所以在代码中增加一个判断:
    cond_len = len(names)
if cond_len > 7:
raise "Too many tests by permutation will be too time consuming."
接下来,我需要对 Tree.__call__() 和 Tree._one_node_set() 方法进行测试,对 __call__(),主要是看 value = root.trunk.node() 这种操作能否正确取值,以及 root.trunk.node('set', value) 能否正确赋值;而对 _one_node_set() 此时它涉及到的测试就比较多了,因此不适合于使用排列方法来做全面的测试。

同时,因为已经使用排列方法对 attribute 和 item 类型的赋值和取值进行过测试了(TestSetAttr 和 TestKeyIndex),那么只有能够保证它们的验证结论可以在这里直接使用,就可以使用另外一种方法来解决"始终保证后面的操作不会对前面的已有的树节点造成破坏"这样一个需求。

那么首先定义一个基类:
class TestTreeBase(TestTree):
def setUp(self):
TestTree.setUp(self)
self._setBaseCase()

def assertNodeValue(self, node, value):
self.failUnless(isinstance(node, Tree))
self.failUnlessEqual(node(), value)
self.failUnless(node() is node._Tree__node_value)
# This identity test is very important !!!
# it proves the results of the previous TestCases
# can be applied directly to the latter tests from now on.

def assertNodeIndexValue(self, node, key, value):
try:
indexed_node = node[key]
except KeyError:
self.fail("__getitem__ operator raises an unexpected KeyError")
self.failUnless(node[key] is node._Tree__node_items[key])
# Important identity test !!!
self.assertNodeValue(node[key], value)
这里对 node() 进行验证,而不像 TestTree 那样对 node._Tree__node_value 进行验证,但需要保证 node() 就是 node._Tree__node_value,因此是一个 identity 检查。

对 __call__() 方法进行测试,因为其 test* 方法比较少,所以仍然可以使用排列来进行。但对于 TestOneNodeSet,必须使用新方法。

我最初想到的一个办法是在每次调用一个 _set* 的时候,先调用"前面"的那个 _set* 方法,并在每个 test* 方法中调用"前面"那个 test* 方法(使用引号是因为 PyUnit 的各个 tests 之间实际上并不存在顺序,顺序只能由自己来定义,因此我按照编辑上从上到下的顺序来排列)。则代码象这样:
class TestOneNodeSet(TestTreeBase):
...
def _setCreateAttr(self):
self.root._one_node_set(('node_1',), 2)
self.root._one_node_set(['node_2'], "2")

def testCreateAttr(self):
if self._setToggle:
self._setCreateAttr()
self.assertNodeValue(self.root.node_1, 2)
self.assertNodeValue(self.root.node_2, "2")
# If the target node is assigned a Tree instance
self.failUnlessRaises(TreeExc, self.root._one_node_set, ('assign_Tree_directly',), Tree("two"))
self.assertPrevious("testBaseCase")

def _setCreateAttrWhenParentExisted(self):
self._setCreateAttr()
self.root.existed_parent = "existed_parent"
self.root.existed_parent.node1 = "existed/node1"
self.root._one_node_set(('existed_parent', 'node2'), "existed/node2")

def testCreateAttrWhenParentExisted(self):
# The parent nodes should not be affected
if self._setToggle:
self._setCreateAttrWhenParentExisted()
self.assertNodeValue(self.root.existed_parent, "existed_parent")
# Make sure the previous nodes are not affected, because Tree._one_node_set() is a recursive operation
self.assertNodeValue(self.root.existed_parent.node1, "existed/node1")
self.assertNodeValue(self.root.existed_parent.node2, "existed/node2")
self.assertPrevious("testCreateAttr")
assertPrevious() 是在 TestTree 中定义的:
class TestTree(unittest.TestCase):
...
def assertPrevious(self, test_name):
self._setToggle = 0
test_method = getattr(self, test_name)
test_method()
self._setToggle = 1
这样有几个问题,一是测试方法之间的模块性就没有那么好了,另一方面,一个 test* 方法每次调用"前面"那个 test*,而那个 test* 又要调用"它自己前面"的那个 test*,则测试方法需要耗费的时间也是会越来越长,虽然还是在一个可接受的范围之类,但似乎每次都测前面的有点多余。

实际上,完全可以在 setUp() 中就先建立所有应该已存在节点,因为 node() 已经做了 is identity 检查,所以每次只需要测试这些节点,实际上就表示已经对所有已存在的树节点进行了检查。

编辑代码如下:
class TestOneNodeSet(TestTreeBase):
def _setBaseCase(self):
# Since the strategy is changed,
# build some previous nodes first:
TestTreeBase._setBaseCase(self)
self.root['base'] = "(base)"
self.root['base'].data = "(base)/data"
self.root['base']['x'] = "(base|x)"
self.root['base']['x'].extra = "(base|x)/extra"
self.root['base']['x']['y'] = "(base|x|y)"
self.root.base = "base"
self.root.base['x'] = "base(x)"
self.root.base['x'].data = "base(x)/data"
self.root.base['x']['y'] = "base(x|y)"
self.root.base['x']['y'].extra = "base(x|y)/extra"
self.root.base['x']['y']['z'] = "base(x|y|z)"

def testBaseCase(self):
if self._setToggle:
self._setBaseCase()
# TestTreeBase.testBaseCase(self)
self.assertNodeIndexValue(self.root, 'base', "(base)")
self.assertNodeValue(self.root['base'].data, "(base)/data")
self.assertNodeIndexValue(self.root['base'], 'x', "(base|x)")
self.assertNodeValue(self.root['base']['x'].extra, "(base|x)/extra")
self.assertNodeIndexValue(self.root['base']['x'], 'y', "(base|x|y)")
self.assertNodeValue(self.root.base, "base")
self.assertNodeIndexValue(self.root.base, 'x', "base(x)")
self.assertNodeValue(self.root.base['x'].data, "base(x)/data")
self.assertNodeIndexValue(self.root.base['x'], 'y', "base(x|y)")
self.assertNodeValue(self.root.base['x']['y'].extra, "base(x|y)/extra")
self.assertNodeIndexValue(self.root.base['x']['y'], 'z', "base(x|y|z)")

def _setCreateAttr(self):
self.root._one_node_set(('node_1',), 2)
self.root._one_node_set(['node_2'], "2")

def testCreateAttr(self):
if self._setToggle:
self._setCreateAttr()
self.assertNodeValue(self.root.node_1, 2)
self.assertNodeValue(self.root.node_2, "2")
# If the target node is assigned a Tree instance
self.failUnlessRaises(TreeExc, self.root._one_node_set, ('assign_Tree_directly',), Tree("two"))
self.assertPrevious("testBaseCase")

def _setCreateAttrWhenParentExisted(self):
# self._setCreateAttr()
self.root.existed_parent = "existed_parent"
self.root.existed_parent.node1 = "existed/node1"
self.root._one_node_set(('existed_parent', 'node2'), "existed/node2")

def testCreateAttrWhenParentExisted(self):
# The parent nodes should not be affected
if self._setToggle:
self._setCreateAttrWhenParentExisted()
self.assertNodeValue(self.root.existed_parent, "existed_parent")
# Make sure the previous nodes are not affected, because Tree._one_node_set() is a recursive operation
self.assertNodeValue(self.root.existed_parent.node1, "existed/node1")
self.assertNodeValue(self.root.existed_parent.node2, "existed/node2")
# self.assertPrevious("testCreateAttr")
self.assertPrevious("testBaseCase")

...
setUp() 方法是在 TestTreeBase 中定义的:
class TestTreeBase(TestTree):
def setUp(self):
TestTree.setUp(self)
self._setBaseCase()
此时,注意 def _setCreateAttrWhenParentExisted(self): 中 self._setCreateAttr() 已经被注释,而 def testCreateAttrWhenParentExisted(self): 中也不再使用 self.assertPrevious("testCreateAttr"),而代之以 self.assertPrevious("testBaseCase") 即可。

__package__ = "caxes"
__revision__ = [259:262]

没有评论: