読者です 読者をやめる 読者になる 読者になる

コンポジットオブジェクトのテスト

コンポジットオブジェクトが含むオブジェクト全てを走査して、等しいかどうか見れるような__eq__があったら、テストしやすいはずです。更に__repr__で文字列にして、どのようなオブジェクトが含まれるか確認できるともっと便利です。そのようなメソッドは実装することができるでしょうか。
例として、ファイルシステムを考えてみることにします。ファイルシステムのディレクトリとファイルは次のようになっています。

  #!/usr/bin/env python2.6
  import unittest
  import itertools
  import sys
  class Entry(object):
    def __init__(self, *fields):
      assert(len(fields) == len(self.fields))
      for field, value in itertools.izip(self.fields, fields):
        setattr(self, field, value)
    def get_size(self):
      pass
  class File(Entry):
    fields = ('name', 'size')
    def get_size(self):
      return self.size
    def get_name(self):
      return self.name
  class Directory(Entry):
    fields = ('name', 'entries')
    def get_size(self):
      size = 0
      for entry in self.entries:
        size += entry.get_size()
      return size
    def add(self, entry):
      self.entries.append(entry) 

このように、フィールドをtupleで制御する方法をとるのは少々奇妙ですが、そのおかげで__eq__はclassそれぞれにでなく、class Entryに__eq__を1つ定義するだけで、うまく定義することができます。Entryに定義されたフィールドを返すジェネレーターiter_fieldsと__eq__を追加します。

  class Entry:
    def __eq__(self, other):
      return type(self) == type(other) and\
           tuple(self.iter_fields()) == tuple(other.iter_fields())
    def iter_fields(self):
      for field in self.fields:
        yield getattr(self, field)

これで全てのファイルの中身を走査し、全く同じであるか確認してくれる__eq__が定義できました。例えば、次のようなコードでテストできます。

  files = []
  files.append(File('one', 1))
  files.append(File('two', 2))
  d1 = Directory('d1', files)
  entries = [d1]
  entries.append(File('three', 3))
  entries.append(File('four', 4))    
  d = Directory('d2', entries)   
  expect = Directory('d2', [
      Directory('d1', [
      File('one', 1),
      File('two', 2)
      ]),
      File('three', 3),
      File('four', 4)
    ])
  assert(expect == d)

これくらいならまだ良いのですが、==が成り立たないときにどうずれているのか把握するのが大変です。__repr__をiter_fieldsを使って再定義します。

  class Entry:
    def __repr__(self):
      return '%s(%s)' %(self.__class__.__name__,
        ', '.join(['%s=%r' % (field, getattr(self, field)) for field in self.fields]))