为什么模拟数据对于测试来说是一种不好的做法
作者:互联网
模拟数据是测试依赖于外部数据源(例如数据库、Web 服务或 API)的软件的常用技术。模拟数据意味着创建模仿真实数据的虚假或模拟数据,但实际上不与外部源交互。模拟数据对于某些场景很有用,例如:
- 将被测单元与与测试无关的其他组件或依赖项隔离。
- 通过避免缓慢或不可靠的网络调用或数据库查询来加快测试执行速度。
- 通过创建可轻松验证的可预测且一致的数据来控制测试输入和输出。
然而,模拟数据也有一些严重的缺点和风险,例如:
- 在模拟数据和真实数据之间引入错误或不一致,这可能导致测试结果出现误报或漏报。
- 通过不测试外部源的实际行为和逻辑或与其交互来降低测试的覆盖范围和置信度。
- 通过需要额外的代码和逻辑来创建和管理模拟数据,增加了测试的维护和复杂性。
在这篇博文中,我将认为模拟数据对于测试软件来说是一种不好的做法,您应该尽可能避免它。我还将向您展示一些示例,说明如何使用 Python 和 PyTest 测试依赖于外部数据源的软件而不模拟数据。
模拟数据的示例
假设我们有一个函数可以根据在线商店的评论计算产品的平均评分。该函数将产品 ID 作为输入,并返回平均评分作为输出。该函数使用 API 从在线商店获取评论。
import requests def get_average_ rating ( product_id ): """ 根据在线商店的评论返回产品的平均评分。 """ url = f"https://online-store.com/api/reviews/ {product_id } " response = requests.get(url) if response.status_code == 200 : Reviews = response.json() ratings = [review[ " rating" ] for review in Reviews] average_ rating = sum (patings) / len ( ratings) 返回average_ rating else : raise Exception( f"无法获得产品{product_id}的评论" )
为了测试这个函数,我们可能会想使用像unittest.mock或pytest-mock这样的库来模拟API返回的数据。例如,我们可以编写这样的测试:
import pytest from pytest_mock import mocker fromaverage_ rating import get_average_ rating def test_get_average_ rating ( mocker ): # 排列 product_id = 123 mock_reviews = [ { " rating" : 5 , "comment" : "很棒的产品!" }, { "评分" : 4 , "评论" : "质量好。" }, { “评级” : 3 ,“评论”:“还不错。” } ] 预期平均评级 = 4.0 mocker.patch( "requests.get" ) requests.get.return_value.status_code = 200 requests.get.return_value.json.return_value = mock_reviews # 执行 actual_average_ rating = get_average_ rating(product_id) # 断言 assertactual_average_ rating == Expected_average_ rating
此测试为 requests.get 创建一个模拟对象,并设置其返回值以模仿 API 的成功响应以及一些虚假评论。然后它调用被测函数并断言它返回预期的平均评级。
模拟数据的问题
乍一看,这个测试似乎没问题。运行速度快,通过,并且涵盖了函数的主要逻辑。然而,该测试存在几个问题,使其不可靠且无效。
测试不是测试真实数据
第一个问题是测试不是测试来自API的真实数据,而只是测试它的模拟版本。这意味着模拟数据和真实数据之间可能存在差异或差异,这可能会影响函数的行为或结果。
例如,如果真正的 API 返回的字段不仅仅是评级和评论怎么办?如果函数需要或使用其中一些字段怎么办?如果其中一些字段的名称或类型与预期不同怎么办?如果其中一些字段具有无效或意外值怎么办?
所有这些场景都可能导致函数中的错误或错误,而测试无法检测到这些错误或错误,因为它仅使用数据的简化版本。即使函数在使用真实数据时失败,测试也会通过。
该测试不是测试与外部源的交互
第二个问题是测试不是测试与外部源的交互,而只是测试它的模拟版本。这意味着与 API 的通信或集成可能会出现问题或失败,从而影响函数的行为或结果。
例如,如果 API 关闭或不可用怎么办?如果 API 缓慢或无响应怎么办?如果 API 返回错误或状态代码与预期不同怎么办?如果 API 在没有通知的情况下更改其格式或结构怎么办?
所有这些场景都可能导致函数中出现测试无法检测到的错误或错误,因为它仅使用响应的模拟版本。即使函数在使用真实数据时失败,测试也会通过。
测试难以维护和更新
第三个问题是测试很难维护和更新,因为它需要额外的代码和逻辑来创建和管理模拟数据。这意味着测试代码本身可能存在错误或不一致,从而影响测试的可靠性或有效性。
例如,如果模拟数据已过时或不正确怎么办?如果模拟数据不完整或缺少某些案例怎么办?如果不同测试中的模拟数据重复或不一致怎么办?如果模拟数据难以阅读或理解怎么办?
所有这些情况都可能导致测试代码中出现错误或错误,从而影响测试结果。测试可能因错误原因而失败或通过。
如何在不模拟数据的情况下进行测试
那么,我们如何在不模拟数据的情况下测试依赖于外部数据源的软件呢?根据上下文和数据源的性质,有多种可能的解决方案。以下是一些一般准则和提示:
- 尽可能使用真实或实时数据源。这是确保您的测试正在测试真实数据以及与外部源的真实交互的最佳方法。然而,由于成本、可用性、安全性或隐私等因素,这可能并不总是可行或理想的。
- 尽可能使用本地或内存中的数据源。这是使用真实或实时数据源的一个很好的替代方案,因为它允许您创建和控制自己的数据,而无需依赖外部源。但是,这可能需要一些额外的设置和配置,并且它可能并不总是与外部源兼容或一致。
- 尽可能使用虚假或存根数据源。这是使用模拟数据源的一个很好的替代方案,因为它允许您模拟来自外部源的真实响应,而无需创建虚假数据。然而,这可能需要一些额外的代码和逻辑,并且可能并不总是准确或现实的。
让我们看看如何将这些解决方案应用到测试函数的示例中,该函数根据在线商店的评论计算产品的平均评分。
使用真实数据源
一种选择是使用提供产品评论的真实在线商店 API。例如,我们可以使用亚马逊的产品广告 API,它允许我们搜索产品并获取其评级和评论。要使用此 API,我们需要注册一个帐户并获取访问密钥和秘密密钥。
我们还需要稍微修改我们的函数以使用这个 API 而不是假的:
import requests from amazon.paapi import AmazonAPI def get_average_ rating ( product_id ): """根据来自 Amazon 的评论返回产品的平均评分。""" amazon = AmazonAPI(ACCESS_KEY, SECRET_KEY) Product = amazon.get_product(product_id) Reviews = Product.reviews ratings = [review[ " rating" ]用于评论中的评论] average_ rating = sum ( ratings ) / len ( ratings ) returnaverage_ rating
然后我们可以编写这样的测试:
import pytest from Average_Rating import get_average_Rating def test_get_average_Rating (): # 排列 Product_id = "B07XJ8C8F5" # 来自 Amazon 的随机产品Expected_average_Rating = 4.6 # 来自 Amazon 的实际平均Rating # Actual_average_Rating = get_average_Rating(Product_id) # Assert assertactual_average_Rating == Expected_average_Ring
该测试的优点在于它使用真实数据以及与外部源的真实交互,这增加了其覆盖范围和置信度。缺点是它依赖于可能不可用或不可靠的外部源,这会降低其速度和稳定性。
使用本地数据源
另一种选择是使用包含产品评论的本地数据库。例如,我们可以使用 SQLite,它是一个轻量级且独立的数据库引擎,可以在内存中运行。要使用 SQLite,我们需要安装它并创建一个包含一些示例数据的数据库文件。
我们还需要稍微修改我们的函数以使用 SQLite 而不是假 API:
import sqlite3 def get_average_ rating ( product_id ): """根据本地数据库的评论返回产品的平均评分。""" conn = sqlite3.connect( "reviews.db" ) Cursor = conn.cursor() #在数据库中查询产品的评论 query = "SELECT rating FROM Reviews WHERE Product_id = ?" Cursor.execute(query, (product_id,)) Reviews = Cursor.fetchall() # 计算平均评分 ratings = [review[ 0 ] for review in Reviews] average_ rating = sum( ratings) / len ( ratings) # 关闭连接 conn.close() returnaverage_ rating
然后我们可以编写这样的测试:
import pytest from Average_Rating import get_average_Rating def test_get_average_Rating (): # 排列 Product_id = 123 # 数据库中的随机产品 Expected_average_Rating = 4.0 # 数据库中的实际平均评级 # Actual_average_Rating = get_average_Rating(Product_id) # Assert assertactual_average_Rating == Expected_average_Rating
此测试使用数据库中的真实产品 ID,并检查函数返回的平均评分是否与数据库中存储的平均评分相同。
该测试的优点是它使用真实数据并与本地源进行真实交互,这提高了其速度和稳定性。缺点是它需要一些额外的设置和配置,并且可能与外部源不兼容或一致。