翻译:《实用的Python编程》08_01_Testing

目录 | 上一节 (7.5 装饰方法 | 下一节 (8.2 日志)html

8.1 测试

多测试,少调试(Testing Rocks, Debugging Sucks)

Python 的动态性质使得测试对大多数程序而言相当重要。编译器不会发现你的 bug,发现 bug 的惟一方式是运行代码,并确保尝试了全部的特性。python

断言(Assertions)

assert 语句用于程序的内部检查。若是表达式不为真,则会触发 AssertionError 异常。git

assert 语句语法:程序员

assert <expression> [, 'Diagnostic message']

示例:github

assert isinstance(10, int), 'Expected int'

assert 语句不该用于检查用户的输入(例如,在网页表单输入的数据)。 assert 语句旨在用于内部检查或者用于不变量(invariant,始终为 True 的条件)。express

契约式编程

契约式编程(contract programming)也称为契约式设计(Design By Contract),自由使用断言是一种软件设计方法。契约式编程规定软件设计人员应该为软件组件定义精确的接口规范。编程

例如,你能够在全部的函数输入中使用断言:bash

def add(x, y):
    assert isinstance(x, int), 'Expected int'
    assert isinstance(y, int), 'Expected int'
    return x + y

若是函数调用者没有使用正确的参数,那么检查输入能够当即捕捉到。函数

>>> add(2, 3)
5
>>> add('2', '3')
Traceback (most recent call last):
...
AssertionError: Expected int
>>>

内联测试

断言也能够用于简单的测试。工具

def add(x, y):
    return x + y

assert add(2,2) == 4

这样,你就能够将测试与代码包含在同一模块中。

好处:若是代码明显被破坏,那么尝试导入模块将会致使程序崩溃。

对于详尽的测试,不推荐这样作。这种作法更像是基本的“冒烟测试(smoke test)”。函数是否能够在全部的用例上正常工做?若是不能够,那么确定是有问题的。

unittest 模块

假设你有下面这样一段代码:

# simple.py

def add(x, y):
    return x + y

如今,你想对这些代码进行测试,请建立一个单独的测试文件,以下所示:

# test_simple.py

import simple
import unittest

而后定义一个测试类:

# test_simple.py

import simple
import unittest

# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
    ...

测试类必须继承自unittest.TestCase

在测试类中,定义测试方法:

# test_simple.py

import simple
import unittest

# Notice that it inherits from unittest.TestCase
class TestAdd(unittest.TestCase):
    def test_simple(self):
        # Test with simple integer arguments
        r = simple.add(2, 2)
        self.assertEqual(r, 5)
    def test_str(self):
        # Test with strings
        r = simple.add('hello', 'world')
        self.assertEqual(r, 'helloworld')

重要提示:每一个方法的名称必须以 test 开头。

使用 unittest

unittest 中内置了一些断言,每种断言对不一样的事情进行诊断。

# Assert that expr is True
self.assertTrue(expr)

# Assert that x == y
self.assertEqual(x,y)

# Assert that x != y
self.assertNotEqual(x,y)

# Assert that x is near y
self.assertAlmostEqual(x,y,places)

# Assert that callable(arg1,arg2,...) raises exc
self.assertRaises(exc, callable, arg1, arg2, ...)

上述列表并非一个完整的列表,unittest 模块还有其它断言。

运行 unittest

要运行测试,请把代码转换为脚本。

# test_simple.py

...

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

而后使用 Python 执行测试文件:

bash % python3 test_simple.py
F.
========================================================
FAIL: test_simple (__main__.TestAdd)
--------------------------------------------------------
Traceback (most recent call last):
  File "testsimple.py", line 8, in test_simple
    self.assertEqual(r, 5)
AssertionError: 4 != 5
--------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=1)

说明

高效的单元测试是一种艺术。对于大型应用而言,单元测试可能会变得很是复杂。

unittest 模块具备大量与测试运行器(test runners),测试结果集(collection of results)以及测试其余方面相关的选项。相关详细信息,请查阅文档。

第三方测试工具

虽然内置 unittest 模块的优点是能够随处使用——由于它是 Python 的一部分,可是许多程序员也以为 unittest 很是繁琐。另外一个流行的的测试工具是 pytest。使用 pytest,测试文件能够简化为如下形式:

# test_simple.py
import simple

def test_simple():
    assert simple.add(2,2) == 4

def test_str():
    assert simple.add('hello','world') == 'helloworld'

要运行测试,只须要输入一个命令便可,例如:python -m pytest 。它将会发现全部的测试并运行这些测试。

除了这个示例以外,pytest 还有不少内容。若是你决定尝试一下,一般很容易上手。

练习

在本次练习中,咱们将探索使用 Python unittest 模块的基本机制(mechanics)。

在前面的练习中,咱们编写了一个包含 Stock 类的 stock.py 文件。对于本次练习,假设咱们使用的是 练习7.9 中编写的与类型化属性相关的代码(译注:typedproperty.py)。若是由于某些缘由,练习 7.9 的代码没法正常工做,你能够从 Solutions/7_9 中复制 typedproperty.py 到工做目录中。

练习 8.1:编写单元测试

请建立一个单独的 test_stock.py 文件,为 Stock 编写单元测试集。为了让你入门,这里有一小段测试实例建立的代码:

# test_stock.py

import unittest
import stock

class TestStock(unittest.TestCase):
    def test_create(self):
        s = stock.Stock('GOOG', 100, 490.1)
        self.assertEqual(s.name, 'GOOG')
        self.assertEqual(s.shares, 100)
        self.assertEqual(s.price, 490.1)

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

运行单元测试,你应该能够得到一些像下面这有的输出:

.
----------------------------------------------------------------------
Ran 1 tests in 0.000s

OK

而后,编写其它单元测试来检查如下各项内容:

  • 确保 s.cost 属性返回正确的值(49010.0)。
  • 确保 s.sell() 方法正常工做。它应该相应地减少 s.shares
  • 确保 s.shares 属性只能设置为整数值。

对于最后一部分,你须要检查异常的触发。要作到这些,一种简单的方法是使用以下代码:

class TestStock(unittest.TestCase):
    ...
    def test_bad_shares(self):
         s = stock.Stock('GOOG', 100, 490.1)
         with self.assertRaises(TypeError):
             s.shares = '100'

目录 | 上一节 (7.5 装饰方法 | 下一节 (8.2 日志)

注:完整翻译见 https://github.com/codists/practical-python-zh