difflib.ndiff() and assertMultiLineEqual() are very useful

Gitでdiffを行うとデフォルトでは、GNU diffの diff -u と同じ挙動なので行単位での差異がでます。 文章のdiffを取りたい場合には –word-diff というオプションをつけると、 [-word-]{+word+} という形式で違う箇所が表示されるので便利です。

さて本題。Pythonでテストコードを書いて、文字列の比較に assertEqual() を使うと、

_____________________________________ DebbuildTests.test_generate_batch_script ______________________________________

self = <debbuild.tests.test_debbuild.DebbuildTests testMethod=test_generate_batch_script>

    def test_generate_batch_script(self):
        """ unit test of generate_batch_script """
        debbuild.generate_batch_script(self.params)
        # self.assertMultiLineEqual(self.batch_content,
        self.assertEqual(self.batch_content,
>                        debbuild.generate_batch_script(self.params))
E       AssertionError: '#!/bin/sh -x\nexport DEBFULLNAME="Dummy Maintainer"\nexport DEBEMAIL=dummy@example.org\napt-get -y install curl devscripts quilt patch libdistro-info-perl fakeroot\napt-get -y build-dep shello\ndget -d http://example.org/debian/pool/main/s/shello/shello_0.1-1.dsc\ndpkg-source -x shello_0.1-1.dsc\n(\ncd shello-0.1\ndebuild -us -uc\n)\ncp -f shello_0.1-1.debian.tar.gz  shello_0.1.orig.tar.gz shello_0.1-1.dsc /home/mkouhei/debbuild/temp/\n' != '#!/bin/sh -x\nexport DEBFULLNAME="Dummy Maintainer"\nexport DEBEMAIL=dummy@example.org\napt-get -y install curl devscripts quilt patch libdistro-info-perl fakeroot\napt-get -y build-dep shello\ndget -d http://example.org/debian/pool/main/s/shello/shello_0.1-1.dsc\ndpkg-source -x shello_0.1-1.dsc\n(\ncd shello-0.1\ndebuild -us -uc\n)\ncp -f shello_0.1-1.debian.tar.gz shello_0.1.orig.tar.gz shello_0.1-1.dsc /home/mkouhei/debbuild/temp/\n'

tests/test_debbuild.py:220: AssertionError

とエラーは検出できてもどこが間違っているのか解読するのは困難です。なので、 assertMultiLineEqual() を使うとこの問題を解決できます。

(snip)
>                        debbuild.generate_batch_script(self.params))
E       AssertionError: '#!/bin/sh -x\nexport DEBFULLNAME="Dummy Maintainer"\nexport DEBEMAIL=dummy@exam [truncated].
.. != '#!/bin/sh -x\nexport DEBFULLNAME="Dummy Maintainer"\nexport DEBEMAIL=dummy@exam [truncated]...
   E         #!/bin/sh -x
   E         export DEBFULLNAME="Dummy Maintainer"
   E         export DEBEMAIL=dummy@example.org
   E         apt-get -y install curl devscripts quilt patch libdistro-info-perl fakeroot
   E         apt-get -y build-dep shello
   E         dget -d http://example.org/debian/pool/main/s/shello/shello_0.1-1.dsc
   E         dpkg-source -x shello_0.1-1.dsc
   E         (
   E         cd shello-0.1
   E         debuild -us -uc
   E         )
   E       - cp -f shello_0.1-1.debian.tar.gz  shello_0.1.orig.tar.gz shello_0.1-1.dsc /home/mkouhei/debbuild/temp/
   E       ?                                  -
   E       + cp -f shello_0.1-1.debian.tar.gz shello_0.1.orig.tar.gz shello_0.1-1.dsc /home/mkouhei/debbuild/temp/

上記の通り、cpコマンドの2つ目の引数の後ろにスペースが一つ余計に入っていることが、”-” で示してあるので分かりやすいです。 最近までこのメソッドの存在に気づいていませんでした。ちゃんとリファレンス読め、ワシ…。

自分で同様の処理を行うなら、 difflib.ndiff() を使うとできます。次のような関数を定義すれば assertMultiLineEqual() の代わりに通常のコードの中でも使えます。

import difflib

def worddiff(string1, string2):
    diff = difflib.ndiff(string1.splitlines(1), string2.splitlines(1))
    print(''.join(diff))

ググるとndiff()については結構ブログで書かれているみたいなのですが、assertMultiLineEqualの方はあまり見当たらないですね。