测试驱动开发(TDD)是程序员用来产生更高质量代码的一种方法。编码的 "典型 "程序一直是代码第一,测试第二。TDD通过在实际编码之前专注于测试来改变这种思维方式。这篇文章是对基本原理的简要概述,并举了一个小例子说明它是如何工作的。我们将使用Python,更确切地说,是标准Python库中提供的unittest 框架。虽然Python是这个例子中使用的语言,但TDD的过程与任何语言都无关。
过程

这张图显示了TDD的高级步骤。我们的目标是尽可能做最少的工作以使测试通过,并避免使过程过于复杂。使用一个例子,我们将通过每个步骤来展示它是如何工作的。如果你对Python的unittest 框架不熟悉,你可以查看这个教程以了解总体情况(或者你应该能够从代码的注释中获得很多功能)。
例子概述
我们想创建一个脚本来格式化街道地址列表。我们的主要要求是确保任何地址中没有小数。
第一步:编写测试
这第一个测试将检查我们的地址是否被成功加载到列表中。
# File: "test.py"
import unittest # Import the unittest framework
from my_code_block import format_addresses # Import the function from our script file
# Create our own test case class that inherits from the base TestCase class
class AddressTestCase(unittest.TestCase):
# Do an intial setup to make the addresses available to all tests
def setUp(self):
self.addresses = ['634 Tomato Way', '233 E. 500 S.', '1800 N. Python Lane']
def test_address_in_list(self):
# This assert checks that the first address is in the list by seeing if it's equal
first_address = format_addresses(self.addresses)[0]
self.assertEqual(first_address, '634 Tomato Way')
第2步:运行和失败测试
结果是测试失败,这是预料之中的,因为我们还没有写任何东西。
$ python -m unittest
======================================================================
ERROR: test_address_in_list (__main__.AddressTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File 'test.py', line 12, in test_address_in_list
first_address = format_addresses(self.addresses)[0]
NameError: name 'format_addresses' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
第3步:编写代码
现在我们可以实际写代码,使其成功。
# File: "my_code_block.py"
def format_addresses(addresses):
return addresses
第4步:运行并通过测试
$ python -m unittest
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
第5步:重构代码
在这一点上,没有太多的代码需要重构,因为这是一个非常随意的例子,但是在做完每一轮之后,花点时间看看有哪些代码可以合并,使之更有效率。
现在重复!
我们现在将应用同样的原则来格式化地址,使其不含任何小数。
第1步:编写测试
# File: "test.py"
import unittest
from my_code_block import format_addresses
class AddressTestCase(unittest.TestCase):
# Do an intial setup to make the addresses available to all tests
def setUp(self):
self.addresses = ['634 Tomato Way', '233 E. 500 S.', '1800 N. Python Lane']
def test_address_in_list(self):
first_address = format_addresses(self.addresses)[0]
self.assertEqual(first_address, '634 Tomato Way')
# ***************** Added decimal test ********************
def test_decimal(self):
no_decimals = True
formatted_addresses = format_addresses(self.addresses)
for value in formatted_addresses:
if '.' in value:
no_decimals = False
# Run an assert on whether or not there were decimals in one of the addresses.
self.assertTrue(no_decimals)
第2步:运行和失败测试
$ python -m unittest
======================================================================
FAIL: test_decimal (__main__.AddressTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File 'test.py', line 23, in test_decimal
self.assertTrue(no_decimals)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
第3步:编写代码
# File: "my_code_block.py"
def format_addresses(addresses):
formatted_addresses = []
for value in addresses:
value = value.replace('.', '')
formatted_addresses.append(value)
return formatted_addresses
第4步:运行并通过测试
$ python -m unittest
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
第5步:重构代码
然后再仔细检查如何改进你的代码。
重复!
对我来说,这是很难理解的。它似乎与我的天性相悖,即先测试。但是一旦我使用这种新的模式做了几个项目,我觉得我的代码变得更干净了。TDD允许你在问题存在之前就将其解决。