所有软件测试的基本原则是:准备、执行、断言
- 准备:为测试做准备,例如加载一些数据
- 执行:执行你想要测试的操作,例如将数据传递给一个函数
- 断言:检查函数的输出是否符合你的期望
- 你可以测试什么:
- 函数是否返回了某样东西?
- 它是否返回了正确类型的对象?
- 返回的对象是否具有正确的特性,例如正确的维度数量?
- 返回的对象是否合理,例如温度值是否在合理的范围内?
- 等等……
使用自动化测试框
在实践中,你需要一个自动化测试框架来:
- 设置一个Python解释器
- 在你的代码目录中查找测试
- 运行所有测试并报告结果
我非常推荐第三方库 pytest 而不是内置的单元测试模块:
- 使用 pytest 命名测试的语法很直观
- pytest 提供了许多实用的第三方扩展,例如计算测试覆盖率或并行运行测试
- pytest 具有更高级的功能,例如通过参数化测试遍历一系列数据组合,或使用 fixtures 为多个测试设置相同的数据
到目前为止,我只在某些功能(例如测试某个测试是否会返回异常)上使用了内置的单元测试模块。
简单测试示例
让我们看一个简单的示例。在这个示例中,我们:
- 读取一个CSV文件
- 将所有列名重命名为小写
- 将“survived”列从整数类型转换为分类类型
- 创建一个新的“family_size”列,它是兄弟姐妹数(sibsp)和父母/子女数(parch)的总和:
def loadAndCleanCSV():
df = pd.read_csv('data/titanic.csv')
df = df.rename(columns={col:col.lower() for col in df.columns})
df.loc[:, "survived"] = df["survived"].astype(pd.CategoricalDtype())
df['family_size'] = df.loc[:,['sibsp','parch']].sum(axis=1)
return df
在pytest框架中,我们通过添加一个新函数来进行测试,该函数以test_开头,或者在这个例子中为test_loadAndCleanCSV,这样我们就知道它针对的是哪个函数。
我们的测试原则是Arrange(准备)、Act(执行)、Assert(断言)。在这个简单示例中,Arrange和Act阶段很简单,因为我们不需要向函数传递任何参数:
def test_loadAndCleanCSV()
outputDf = loadAndCleanCSV()
现在是Assert阶段,问题是:我们要测试什么?
以下是我对数据科学代码进行的一些常见测试:
- 在几乎每个测试函数中,我都会测试函数输出的类型。这看起来可能太明显了,但有时你会发现,例如,你不经意间将DataFrame缩减为了Series。
- 我通常还会测试DataFrame的一些元数据——它是否有预期的行数和/或列数?
- 测试DataFrame的dtypes也是一个好主意——在这个例子中,我们将限制为检查“survived”列从整数到分类的转换是否成功。
def test_loadAndCleanCSV()
outputDf = loadAndCleanCSV()
assert isinstance(outputDf,pd.DataFrame)
assert df.shape == (1112,13)
assert pd.api.types.is_categorical_dtype(df["survived"])
对于许多数据科学对象的测试,如DataFrame,仅使用标准的Python断言语句就足够了。这个语句测试其后的条件是否为True。如果条件不满足,则会引发一个AssertionError。
许多数据科学库都带有方便的功能来测试它们生成的对象。在上面的函数最后一行中,我们使用了Pandas的内置函数:pd.api.types.is_categorical_dtype。在pd.api.types命名空间中还有许多这样的方便函数(这些函数在处理复杂的数据类型,如日期时间时特别有用)。
一旦我在src目录中的某个地方将测试写入自己的脚本中,我就可以使用pytest来运行它们。我通常使用python -m标志将其作为模块来运行:
python -m pytest src
然后,pytest会设置一个全新的Python解释器,找到所有以test_开头的测试函数并运行它们。
测试数据集
- 始终使用尽可能小的测试数据集
- 在项目一开始就创建测试数据集
- 轻松地在不同的测试数据集和完整数据集之间切换
- 拥有多个大小不同的测试数据集
- 可以显示当数据集变大时是否会出现新的问题
- 可以估算运行完整数据集所需的时间
- 除非首先在测试数据集上运行,否则永远不要在完整数据集上运行
测试数据集中包含什么?
应该具有最少的数据点以获得所需的输出
示例:
- 对于多年的时间序列,测试数据集应包含至少2年的数据点
- 对于空间趋势,具有多个网格点
- 如果数据集的部分包含特殊情况,请将这些作为额外的测试数据集
- 示例:分析海洋模型输出时,应有一个开阔海洋测试数据集和一个沿海测试数据集