Python 中测试驱动开发的澄清
Clarification of Test Driven Development in Python
我想知道我是否可以得到一些关于 TDD 和一般测试的说明。我正在使用 python
编写我的程序,并使用 pytest
作为我的测试框架。
到目前为止,我一直这样做的方式,首先编写这些测试,已经开始让我的大脑对某些任务采取务实的方法。
- 除了 3 个 class 变量之外的所有内容都是在编写测试之前编写的,这是否意味着我在技术上没有遵循 TDD?
- 为清楚起见,我是否应该先为未实现的方法编写整个测试,然后创建方法和功能以使测试通过?
- 我的主要 objective 是否遵循 TDD 让每一个测试都通过,无论我在测试数据中做了什么更改?(它做了什么)(因此消除了潜在的错误?)
- class 的测试是否应该有很大数量(取决于 class 的大小)?
- 根据下面的代码,你能告诉我我的路径是否正确吗
代码供您参考
import pytest
import os
import sys
path = os.path.dirname(os.path.abspath(__file__))
path = path.replace("\pytest", "")
sys.path.append(path)
path += "\pyffi"
sys.path.append(path)
from NifExplorer import NifExplorer
from NifExplorer import NifFormat
@pytest.fixture(autouse=True, scope='session')
def setup_nifExplorer():
Explorers = []
explorer = NifExplorer()
explorer.SetBlockType(NifFormat.NiNode)
explorer.SetResultPath("\pytest\results")
explorer.SetSearchPath("\pytest\nif\base")
explorer2 = NifExplorer()
explorer2.SetBlockType(NifFormat.ATextureRenderData)
explorer2.SetResultPath("\pytest\results")
explorer2.SetSearchPath("\pytest\nif\base")
explorer3 = NifExplorer()
explorer3.SetBlockType("NiNode")
explorer3.SetResultPath("\pytest\testResults")
explorer3.SetSearchPath("\pytest\nif\base")
Explorers.append(explorer)
Explorers.append(explorer2)
Explorers.append(explorer3)
return Explorers
@pytest.mark.usefixtures("setup_nifExplorer")
class TestNifExplorer:
def NifExlorer_BlockType_Is_Not_None(self, setup_nifExplorer):
assert setup_nifExplorer.BlockType != None
def NifExplorer_SearchPath_Is_Not_None(self, setup_nifExplorer):
assert setup_nifExplorer.SearchPath != None
def NifExplorer_ResultPath_Is_Not_None(self, setup_nifExlorer):
assert setup_nifExlorer.ResultPath != None
@pytest.mark.parametrize('funcs', (NifExplorer_SearchPath_Is_Not_None, NifExplorer_ResultPath_Is_Not_None, NifExlorer_BlockType_Is_Not_None))
def test_NifExplorer_Variables_Equal_Not_None(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
def NifExplorer_ResultPath_Directory_Exists(self, setup_nifExplorer):
assert os.path.exists(setup_nifExplorer.ResultPath) == True
def NifExplorer_SearchPath_Directory_Exists(self, setup_nifExplorer):
assert os.path.exists(setup_nifExplorer.SearchPath) == True
def NifExplorer_SearchPath_Directory_Contains_No_Forward_Slashes(self, setup_nifExplorer):
assert setup_nifExplorer.SearchPath.count('/') < 1
def NifExplorer_ResultPath_Directory_Contains_No_Forward_Slashes(self, setup_nifExplorer):
assert setup_nifExplorer.ResultPath.count('/') < 1
@pytest.mark.parametrize('funcs', [NifExplorer_ResultPath_Directory_Exists, NifExplorer_SearchPath_Directory_Exists, NifExplorer_SearchPath_Directory_Contains_No_Forward_Slashes, NifExplorer_ResultPath_Directory_Contains_No_Forward_Slashes])
def test_NifExplorer_Directories_Exist_And_Paths_Contain_No_Forward_Slashes(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
def NifExplorer_SearchPath_Contains_Nif_Files_Recursively(self, setup_nifExplorer):
assert setup_nifExplorer.DirectoryContainsNifRecursively(setup_nifExplorer.SearchPath) == True
@pytest.mark.parametrize('funcs', [NifExplorer_SearchPath_Contains_Nif_Files_Recursively])
def test_NifExplorer_SearchPath_Contains_Nif_Files(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
if __name__ == "__main__":
pytest.main()
Everything apart from 3 class variables were written before the tests were written, does that mean I'm technically not following TDD?
如果您在测试需要它之前编写任何“真实”代码,那么您就没有遵循 TDD。这没关系——TDD 不一定是适用于所有情况的最佳工具。但是当你处于TDD有意义的情况下,那么测试就先写了。
am I supposed to write the entire test for an unimplemented method first, then create the method and functionality so the tests pass?
这取决于您遵循谁的指导。 Robert Martin 建议您只编写足以让它失败的测试,然后通过那么多的测试,然后编写更多测试,然后通过,等等,直到测试完成。他称之为纳米循环。 Martin 建议将此作为重现与 Kent Beck 合作的经验的技术。
然而,当 Kent Beck 自己描述 TDD 时,他谈到编写一个完整的测试,然后一次一个地解决其中的问题。
我自己发现 Beck 的风格更适合我,尤其是在可以自动填充一些缺失代码的编码环境中。所以我会专注于测试的设计,直到我对它满意为止,然后让 IDE 创建我需要通过测试的框架代码,最后填写细节。
Is my main objective following TDD to have every single test pass
测试真的是“错误检测器”; objective 是创建无错误地交付价值的代码。
TDD 的好处之一是仪式大大减少了您在任何时候必须处理的“错误”数量。是的,这意味着纪律是我们几乎总是处于两种状态之一,或者 (a) 我们编写的所有测试都通过了,或者 (b) 除了我们编写的测试之一之外的所有测试都通过了。
Are tests for a class supposed to number greatly(depending on the size of the class)?
不一定尺寸;复杂度可能是一个更好的衡量标准——有多少不同的输入类?您通常需要至少从每一种行为中抽取一份样本,以确保您需要的所有行为都存在。
这里有一个常见的例子:假设一个函数以公历年作为输入,如果该年是闰年,则 returns 为真。我们关心的输入类
- 年份不能被 4 整除
- 年份可以被 4 整除,但不能被 100 整除
- 年份可以被 100 整除,但不能被 400 整除
- 年份能被 400 整除
那么四个“测试”?
can you tell me if I am on the correct path
很难说。然而,有几个想法很突出。
大多数实际工作包括“复杂的代码”和“难以测试的代码”。一个有用的技巧是设计你的代码,使复杂的代码易于测试,而难以测试的代码非常简单,显然没有任何缺陷。
“难以测试”包括依赖于共享可变状态(如文件系统)的行为。所以我们通常会选择文件系统代码太简单而无法破解的设计,将所有分支放在其他地方,然后彻底测试分支代码。
此外,测试设计是一回事。特别是,我们通常希望优先考虑使测试一目了然。反过来,这通常意味着您会将一个测试的所有代码放在一起,而不是在一个地方放置一堆“设置”代码,然后在其他地方断言。
我想知道我是否可以得到一些关于 TDD 和一般测试的说明。我正在使用 python
编写我的程序,并使用 pytest
作为我的测试框架。
到目前为止,我一直这样做的方式,首先编写这些测试,已经开始让我的大脑对某些任务采取务实的方法。
- 除了 3 个 class 变量之外的所有内容都是在编写测试之前编写的,这是否意味着我在技术上没有遵循 TDD?
- 为清楚起见,我是否应该先为未实现的方法编写整个测试,然后创建方法和功能以使测试通过?
- 我的主要 objective 是否遵循 TDD 让每一个测试都通过,无论我在测试数据中做了什么更改?(它做了什么)(因此消除了潜在的错误?)
- class 的测试是否应该有很大数量(取决于 class 的大小)?
- 根据下面的代码,你能告诉我我的路径是否正确吗
代码供您参考
import pytest
import os
import sys
path = os.path.dirname(os.path.abspath(__file__))
path = path.replace("\pytest", "")
sys.path.append(path)
path += "\pyffi"
sys.path.append(path)
from NifExplorer import NifExplorer
from NifExplorer import NifFormat
@pytest.fixture(autouse=True, scope='session')
def setup_nifExplorer():
Explorers = []
explorer = NifExplorer()
explorer.SetBlockType(NifFormat.NiNode)
explorer.SetResultPath("\pytest\results")
explorer.SetSearchPath("\pytest\nif\base")
explorer2 = NifExplorer()
explorer2.SetBlockType(NifFormat.ATextureRenderData)
explorer2.SetResultPath("\pytest\results")
explorer2.SetSearchPath("\pytest\nif\base")
explorer3 = NifExplorer()
explorer3.SetBlockType("NiNode")
explorer3.SetResultPath("\pytest\testResults")
explorer3.SetSearchPath("\pytest\nif\base")
Explorers.append(explorer)
Explorers.append(explorer2)
Explorers.append(explorer3)
return Explorers
@pytest.mark.usefixtures("setup_nifExplorer")
class TestNifExplorer:
def NifExlorer_BlockType_Is_Not_None(self, setup_nifExplorer):
assert setup_nifExplorer.BlockType != None
def NifExplorer_SearchPath_Is_Not_None(self, setup_nifExplorer):
assert setup_nifExplorer.SearchPath != None
def NifExplorer_ResultPath_Is_Not_None(self, setup_nifExlorer):
assert setup_nifExlorer.ResultPath != None
@pytest.mark.parametrize('funcs', (NifExplorer_SearchPath_Is_Not_None, NifExplorer_ResultPath_Is_Not_None, NifExlorer_BlockType_Is_Not_None))
def test_NifExplorer_Variables_Equal_Not_None(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
def NifExplorer_ResultPath_Directory_Exists(self, setup_nifExplorer):
assert os.path.exists(setup_nifExplorer.ResultPath) == True
def NifExplorer_SearchPath_Directory_Exists(self, setup_nifExplorer):
assert os.path.exists(setup_nifExplorer.SearchPath) == True
def NifExplorer_SearchPath_Directory_Contains_No_Forward_Slashes(self, setup_nifExplorer):
assert setup_nifExplorer.SearchPath.count('/') < 1
def NifExplorer_ResultPath_Directory_Contains_No_Forward_Slashes(self, setup_nifExplorer):
assert setup_nifExplorer.ResultPath.count('/') < 1
@pytest.mark.parametrize('funcs', [NifExplorer_ResultPath_Directory_Exists, NifExplorer_SearchPath_Directory_Exists, NifExplorer_SearchPath_Directory_Contains_No_Forward_Slashes, NifExplorer_ResultPath_Directory_Contains_No_Forward_Slashes])
def test_NifExplorer_Directories_Exist_And_Paths_Contain_No_Forward_Slashes(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
def NifExplorer_SearchPath_Contains_Nif_Files_Recursively(self, setup_nifExplorer):
assert setup_nifExplorer.DirectoryContainsNifRecursively(setup_nifExplorer.SearchPath) == True
@pytest.mark.parametrize('funcs', [NifExplorer_SearchPath_Contains_Nif_Files_Recursively])
def test_NifExplorer_SearchPath_Contains_Nif_Files(self, setup_nifExplorer, funcs):
for obj in setup_nifExplorer:
funcs(self,obj)
if __name__ == "__main__":
pytest.main()
Everything apart from 3 class variables were written before the tests were written, does that mean I'm technically not following TDD?
如果您在测试需要它之前编写任何“真实”代码,那么您就没有遵循 TDD。这没关系——TDD 不一定是适用于所有情况的最佳工具。但是当你处于TDD有意义的情况下,那么测试就先写了。
am I supposed to write the entire test for an unimplemented method first, then create the method and functionality so the tests pass?
这取决于您遵循谁的指导。 Robert Martin 建议您只编写足以让它失败的测试,然后通过那么多的测试,然后编写更多测试,然后通过,等等,直到测试完成。他称之为纳米循环。 Martin 建议将此作为重现与 Kent Beck 合作的经验的技术。
然而,当 Kent Beck 自己描述 TDD 时,他谈到编写一个完整的测试,然后一次一个地解决其中的问题。
我自己发现 Beck 的风格更适合我,尤其是在可以自动填充一些缺失代码的编码环境中。所以我会专注于测试的设计,直到我对它满意为止,然后让 IDE 创建我需要通过测试的框架代码,最后填写细节。
Is my main objective following TDD to have every single test pass
测试真的是“错误检测器”; objective 是创建无错误地交付价值的代码。
TDD 的好处之一是仪式大大减少了您在任何时候必须处理的“错误”数量。是的,这意味着纪律是我们几乎总是处于两种状态之一,或者 (a) 我们编写的所有测试都通过了,或者 (b) 除了我们编写的测试之一之外的所有测试都通过了。
Are tests for a class supposed to number greatly(depending on the size of the class)?
不一定尺寸;复杂度可能是一个更好的衡量标准——有多少不同的输入类?您通常需要至少从每一种行为中抽取一份样本,以确保您需要的所有行为都存在。
这里有一个常见的例子:假设一个函数以公历年作为输入,如果该年是闰年,则 returns 为真。我们关心的输入类
- 年份不能被 4 整除
- 年份可以被 4 整除,但不能被 100 整除
- 年份可以被 100 整除,但不能被 400 整除
- 年份能被 400 整除
那么四个“测试”?
can you tell me if I am on the correct path
很难说。然而,有几个想法很突出。
大多数实际工作包括“复杂的代码”和“难以测试的代码”。一个有用的技巧是设计你的代码,使复杂的代码易于测试,而难以测试的代码非常简单,显然没有任何缺陷。
“难以测试”包括依赖于共享可变状态(如文件系统)的行为。所以我们通常会选择文件系统代码太简单而无法破解的设计,将所有分支放在其他地方,然后彻底测试分支代码。
此外,测试设计是一回事。特别是,我们通常希望优先考虑使测试一目了然。反过来,这通常意味着您会将一个测试的所有代码放在一起,而不是在一个地方放置一堆“设置”代码,然后在其他地方断言。