标签:,, 发表于 Python开发 分类. 发表评论

虽然之前在Django项目中也不完整地用过单元测试工具, 但是今天是第一次用python的单元测试工具完整写出一个项目的测试样例, 于是小记录下~

需求: 测试网易微博API的python实现.

源代码可见: http://code.google.com/p/163microblog/source/browse/trunk/tests.py

话说对于偶这种初级民工, 写代码喜欢不需要动脑子地先搭个架子出来然后往里面填空, 不过偶连这个架子也是从python文档里面抄出来的:

import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def testlogin(self):
        self.session = Session(username="xiaket@163.com", password="notmypass")
        self.assertEqual(self.session.screen_name, "xiaket")

if __name__ == "__main__":
    unittest.main()

这个东西貌似是能够跑, 但是有个比较不爽的地方是密码必须放到测试样例里面去, 这个让偶很不爽. 于是网上搜了搜, 看到一个继承unittest.TestCase这个基类的__init__方法的办法:

import unittest

from session import T163Session as Session

class SessionTests(unittest.TestCase):

    def __init__(self, testname, username=None, password=None):
        super(SessionTests, self).__init__(testname)
        self.username = username
        self.password = password

    def testlogin(self):
        session = Session(username=self.username, password=self.password)
        self.assertEqual(session.screen_name, "xiaket")

这样你在另外一个文件里面就能够对这个类进行测试了:

import unittest

from t163.tests import SessionTests

if __name__ == "__main__":
    username = "xiaket@163.com"
    password = "notmypass"
    suite = unittest.TestSuite()
    suite.addTest(SessionTests("testlogin", username, password))
    unittest.TextTestRunner().run(suite)

架子搭好了, 剩下的就是苦力活, 往里面填测试样例了. 这个过程比较纠结比较繁复, 而且伴随着测试样例的逐渐完成, 我还fix了一群以前代码中的bug. 另外, 由于现在网易微博还比较不成熟, 我还不得不为一群server的错误擦屁股, 写了几个装饰器来修正API的行为. 例如, 有个微博的API能够给出follow某用户的用户的信息.当给出的用户的screen_name参数不正确时, 我们理应期待服务器返回错误代码. 但是服务器比较出人意料地返回了follow当前登录用户的用户的信息. 这导致所有查询都不是完全可靠的, 除非你在每次查询前确定你查询所使用的screen_name是有效的. 于是, 我写了这样一个装饰器加到已有函数的前面:

def check_screen_name(func):
    """
    This decorator would check the screen_name in the parameter of the original
    function.

    It is to be noted that the screen must be the first argument if we are
    using a positional parameter.
    """
    def morewrapped(func):
        def wrapped(kls, *args, **kwargs):
            if 'screen_name' in kwargs:
                _screen_name = kwargs['screen_name']
            elif len(args):
                _screen_name = args[0]
            else:
                _screen_name = None
            if _screen_name:
                # If the screen_name is set, we shall check if it is a valid
                # screen_name. We do this by visiting the homepage of this
                # screen_name:
                _url = "/%s" % _screen_name
                _message = "Specified user does not exist."
                _err_dict = {
                    404: (UserNotFound, _message),
                }
                kls.request(_url, errors=_err_dict)
            return func(kls, *args, **kwargs)
        return wrapped
    return morewrapped(func)

我们通过能否访问这个用户的主页来判断这个用户是否存在. 如果这个主页不能够被正常访问, 我们就抛出一个404, 这样就能相信followers这个API的结果了. 另外, 使用装饰器的好处在于, 哪天服务器端的程序更新了, 我们的代码不用做太大的修改就可以继续使用.

好吧, 话题扯回来, 总结下写了这么长时间的测试样例, 有啥经验:

  • 将测试代码分类, 这样什么时候改动了一个地方, 不需要测试所有的样例.
  • 这个项目主要测试的内容是异常的处理, 于是可以先写好所有的会抛异常的代码, 然后一边测试一边添加assert语句.
  • 考虑问题要全面, 当然这个比较废话了, 每个人都希望如此.
  • 如果测试需要网络交互, 或者速度较慢的话, 用”"”将测过的不影响逻辑的代码注释起来, 这样能有效地提高速度.
  • 注意添加注释, 这样你能够更有效地重用你自己的代码.
2010-05-16 22:59