python程序测试

5 views
Skip to first unread message

李现民

unread,
Jan 20, 2008, 10:10:32 PM1/20/08
to library
程序的测试

在Python中,测试就是反复的编辑和运行。

当你写一段程序的时候,你必须首先要清楚这段程序要解决什么问题,你可以先写一个文档,以 阐明这段程序要达到的目标,以便以后检查原程序是否满足要求,然后为它写一测试程序。比如,如果你想写一模块,它只有一个函数rect_area,这个函 数用来计算给定的长和宽的面积,在开始写这一模块之前,你应该写一如下测试程序来测试它是否能达到要求:

from area import rect_area
height = 3
width = 4
correct_answer = 12
answer = rect_area(height, width)
if answer == correct_answer:
    print 'Test passed '
else:
    print 'Test failed '


这个测试程序能够反映出你的模块是否能达到目标,如果它给出了错误提示,你可以根据提示来检查模块的代码。写测试程序也有利于日后程序的扩展和维护。

一、测试的一般步骤

1、明确你想要的功能。尽可能写一个文档,然后为它写一个测试程序。
2、为这些功能写一些框架代码(就是先把程序的基本结构写好,避免语法错误),以便程序能运行。
3、为你的框架写一些样品代码,仅用于满足测试,不必实现所要的功能,仅仅为了通过测试。
4、重写这些样品代码使它实现你要求的功能,并保证测试通过。

二、测试工具

1、doctest

doctest模块用于在文档字符串中找寻类似Python命令行会话的文本,并执行它们以检查它们是否和文档字符中所写的结果一样。示例如下:

def square(x):
    '''
    Squares a number and returns the result.
    >>> square(2)
    4
    >>> square(3)
    9
    '''
    return x*x


上面定义了一个计算平方的函数,其中文档字符串中:
>>> square(2)
4
>>> square(3)
9

就是命令行会话。

然后我们再写一个名为my_math.py的模块用来测试函数的正确性:

if __name__=='__main__':
import doctest, my_math
doctest.testmod(my_math)


然后我们执行如下命令:

>>> python my_math.py
>>>


没有输出何结果,这是因为检查通过,也就是说square(2)的值是4,square(3)和值是9。如果没有成功,将给出错误信息,根据错误信息可以很容易地找到错误。

如果我们要看详细的检查情况的话,可以如下:
>>> python my_math.py -v
输出结果如下:
Running my_math.__doc__
0 of 0 examples failed in my_math.__doc__
Running my_math.square.__doc__
Trying: square(2)
Expecting: 4
ok
Trying: square(3)
Expecting: 9
ok
0 of 2 examples failed in my_math.square.__doc__
1 items had no tests:
test
1 items passed all tests:
2 tests in my_math.square
2 tests in 2 items.
2 passed and 0 failed.
Test passed.


unittest

虽然doctest使用非常简单,但是unittest更灵活、更强大。它可以用来写非常大而完善的测试。下面我们举一个例子,我们将为一个名为 my_math的模块写一个测试程序,该my_math模块包含一个用于计算的名为product的函数。我们的测试程序名为 test_my_math.py,其中使用了unittest模块中的TestCase类。

test_my_math.py如下:

import unittest, my_math

class ProductTestCase(unittest.TestCase):
    def testIntegers(self):
        for x in xrange(-10, 10):
            for y in xrange(-10, 10):
                p = my_math.product(x, y)
                self.failUnless(p == x*y, '整数乘法失败')

    def testFloats(self):
        for x in xrange(-10, 10):
            for y in xrange(-10, 10):
                x = x/10.0
                y = y/10.0
                p = my_math.product(x, y)
                self.failUnless(p == x*y, '浮点数乘法失败')

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


函 数unittest.main()运行测试,它将实例化TestCase的所有子类并运行名字以test开头的方法。如果你定义了名为startUp和 tearDown的方法,那么它们将在以test开头的方法之前和之后被执行,因此你可以利用它们做一些初始化和清理工作。

在我们还没有 写my_math.py模块之前,如果你运行test_my_math.py,这将只给出一个关于my_math不存在的异常,unittest模块能够 区别异常和测试失败。failUnless方法检查给定的条件(判断p==x*y是否成立)以确定所给的测试是成功还是失败,失败则给出后面的信息,如 '整数乘法失败'。TestCase类中还有许多其它的方法,这里不一一列举。

下一步我们写出my_math.py的基本框架:

def product(x, y):
    pass

有了上面的这个基本框架,测试就不会出现异常了,只会失败。如果现在你运行测试程序,你将得到两条失败消息:

FF
======================================================================
FAIL: testFloats (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
    File "test_my_math.py", line17, in testFloats
        self.failUnless(p == x*y,'浮点数乘法失败')
AssertionError: 浮点数乘法失败
======================================================================
FAIL: testIntegers (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
    File "test_my_math.py", line9, in testIntegers
        self.failUnless(p == x*y,'整数乘法失败')
AssertionError: 整数乘法失败
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=2)



最上面的FF表示有两个失败。

下一步我们写上函数所要完成的功能:

def product(x, y):
    return x * y


现在我们再测试一下,就会成功了,测试信息如下:

..
----------------------------------------------------------------------
Ran 2 tests in 0.015s

OK


最上面的两点表示两个测试成功。

我们再把product函数改一下,以至于在参数为7和9时测试失败:

def product(x, y):
    if x == 7 and y == 9:
        return '存在潜在的缺陷'
    else:
        return x * y


然后我们再进行测试,得到的信息如下:

.F
======================================================================
FAIL: testIntegers (__main__.ProductTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
    File "test_my_math.py", line 9, in testIntegers
        self.failUnless(p == x*y, '整数乘法失败')
AssertionError: 整数乘法失败
----------------------------------------------------------------------
Ran 2 tests in 0.005s
FAILED (failures=1)

源代码检查工具


doctest和unittest都是检查功能的。现在我们介绍两个检查源码的工具,以便提高你的代码的质量。它们分别是PyChecker( http://pychecker.sf.net)和PyLint(http://logilab.org/projects/pylint)。

用PyChecker检查文件的方法如下:

pychecker file1.py file2.py

用PyLint检查文件的方法如下:

pylint module

下面给出使用这两个工具的一个测试程序的例子:

import unittest, my_math
from subprocess import Popen, PIPE

class ProductTestCase(unittest.TestCase):
    # Insert previous tests here
    def testWithPyChecker(self):
        cmd = 'pychecker', '-Q', my_math.__file__.rstrip('c')
        pychecker = Popen(cmd, stdout=PIPE, stderr=PIPE)
        self.assertEqual(pychecker.stdout.read(), '')

    def testWithPyLint(self):
        cmd = 'pylint', '-rn', 'my_math'
        pylint = Popen(cmd, stdout=PIPE, stderr=PIPE)
        self.assertEqual(pylint.stdout.read(), '')

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


在上面的代码中,我们使用了一些选项如'-Q'、'-rn',这样可以避免无关的输出以防止干扰我们的测试。
上面的代码中的所定义了两个函数何以任意去掉一个,我只是为了方便。

下面我们改写一下my_math.py:

"""
A simple math module.
"""
__revision__ = '0.1'

def product(factor1, factor2):
    'The product of two numbers'
    return factor1 * factor2


执行测试不会任何错误。如果我们将factor1 * factor2改为x*y的话,功能测试仍然成功,但源码测试将警告说:变量名太短。

优化检查工具


标准库提供了一个好的用于优化检查的模块,名为profile,通过检查你可以优化代码以提高代码的运行速度。使用profile非常简单,只需要使用run方法并带一个字符串参数即可。示例如下:

>>> import profile
>>> from my_math import product
>>> profile.run('product(1, 2)')


这将给你一个关于不同的函数和方法调用了多少次以及不同的函数花费了多少时间。如果你给run函数提供了第二个参数,例如'my_math.profile',那么检查结果将被保存到文件中。稍后你可以使用pstats模块来检查保存在文件中的结果,示例如下:

>>> import pstats
>>> p = pstats.Stats('my_math.profile')


好了,通过功能检查、源码检查和优化检查,相信我们的程序可以得到更好的完善。

Reply all
Reply to author
Forward
0 new messages