Python 中测试驱动开发的澄清

Clarification of Test Driven Development in Python

我想知道我是否可以得到一些关于 TDD 和一般测试的说明。我正在使用 python 编写我的程序,并使用 pytest 作为我的测试框架。

到目前为止,我一直这样做的方式,首先编写这些测试,已经开始让我的大脑对某些任务采取务实的方法。

  1. 除了 3 个 class 变量之外的所有内容都是在编写测​​试之前编写的,这是否意味着我在技术上没有遵循 TDD?
  2. 为清楚起见,我是否应该先为未实现的方法编写整个测试,然后创建方法和功能以使测试通过?
  3. 我的主要 objective 是否遵循 TDD 让每一个测试都通过,无论我在测试数据中做了什么更改?(它做了什么)(因此消除了潜在的错误?)
  4. class 的测试是否应该有很大数量(取决于 class 的大小)?
  5. 根据下面的代码,你能告诉我我的路径是否正确吗

代码供您参考

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

很难说。然而,有几个想法很突出。

大多数实际工作包括“复杂的代码”和“难以测试的代码”。一个有用的技巧是设计你的代码,使复杂的代码易于测试,而难以测试的代码非常简单,显然没有任何缺陷。

“难以测试”包括依赖于共享可变状态(如文件系统)的行为。所以我们通常会选择文件系统代码太简单而无法破解的设计,将所有分支放在其他地方,然后彻底测试分支代码。

此外,测试设计是一回事。特别是,我们通常希望优先考虑使测试一目了然。反过来,这通常意味着您会将一个测试的所有代码放在一起,而不是在一个地方放置一堆“设置”代码,然后在其他地方断言。