首先提前致谢!如果您需要其他任何东西,例如 pytest 固定装置或其他东西,请告诉我。我不想给这个问题添加不必要的噪音。抱歉,如果我没有找到一种方法来做到这一点,但我已经在 google、SO、github 和 Discord.py 文档上搜索了大约 3 天,但我一直无法让它工作。
我正在尝试进行 pytest 单元测试,以确保当用户调用命令并发生
on_command_error()
时,discord 机器人通过 CommandInvokeError
发送消息。我已经使用 invoke(ctx)
在没有必需参数的命令上实现了这一点,但是,invoke()
只能将 ctx
作为参数,而不是像 invoke(ctx, words=10)
这样的东西。如果传递了一个参数,它会引发一个 TypeError
,如下所示:
TypeError: Command.invoke() got an unexpected keyword argument 'words'
此单元测试有效:
@pytest.mark.asyncio
async def test_request_leaderboard_no_game(bot: client.WordDebtBot):
ctx = AsyncMock()
game_cmds_cog = bot.get_cog("Core Gameplay Module")
cmd_err_cog = bot.get_cog("Command Error Handler")
game_cmds_cog.game = None
cmd_err_cog.game = None
with pytest.raises(commands.CommandInvokeError) as err:
await game_cmds_cog.leaderboard.invoke(ctx)
await cmd_err_cog.on_command_error(ctx, err.value)
ctx.send.assert_called_with(String() & Regex("Game not loaded.*"))
此单元测试因上述内容而失败
TypeError
:
@pytest.mark.asyncio
async def test_submit_words_no_game(
bot: client.WordDebtBot, player: game_lib.WordDebtPlayer
):
ctx = AsyncMock()
ctx.author.id = player.user_id
game_cmds_cog = bot.get_cog("Core Gameplay Module")
cmd_err_cog = bot.get_cog("Command Error Handler")
game_cmds_cog.game = None
cmd_err_cog.game = None
with pytest.raises(commands.CommandInvokeError) as err:
await game_cmds_cog.log.invoke(ctx, words=10)
cmd_err_cog.on_command_error(ctx, err.value)
ctx.send.assert_called_with(String() & Regex("Game not loaded.*"))
game_commands cog 中的日志命令如下所示:
@commands.command(name="log")
async def log(self, ctx, words: int):
new_debt = self.game.submit_words(str(ctx.author.id), words)
self.journal({"command": "log", "words": words, "user": str(ctx.author.id)})
await ctx.send(f"Logged {words:,} words! New debt: {new_debt:,}")
on_command_error()
在 cmd_err_handler cog 中看起来像这样:
@commands.Cog.listener()
async def on_command_error(self, ctx, err: commands.CommandError):
...Other if branches...
elif isinstance(err.__cause__, AttributeError) and not self.game:
await ctx.send("Game not loaded....")
...Other if branches...
我尝试过捕获引发的
AttributeError
(由于游戏为“无”),如果仅使用.log(ctx, word=10)
而不是使用invoke()
,然后制作CommandInvokeError(AttributeError)
并调用on_command_error(ctx, CmdInvkErr)
,但这会给出:
AssertionError: expected call not found.
对于ctx.send.assert_called_with(String() & Regex("Game not loaded.*"))
我也尝试过使用
.callback()
做类似的事情,并得到类似的结果。
此外,我尝试使用
.dispatch()
尝试手动触发 on_command_error()
的事件,但我再次得到了类似的结果。
目的是我希望单元测试引发异常,然后手动触发事件
on_command_error()
或触发事件以捕获然后确保它发送正确的“游戏未加载...”消息。对于具有必需参数的机器人命令,我该如何执行此操作?
注意:机器人按预期工作,但单元测试失败。
可能有更好的方法来做到这一点,我发现的解决方案可能看起来很混乱,但我能够通过以下方式解决这个问题:
创建围绕正在测试的命令的包装命令。
通过包装器命令将所需的参数传递给正在测试的命令。
将包装的命令添加到包含原始命令的齿轮中。
调用在 AsyncMock 上下文中传递的包装命令。
解决方案如下所示:
@pytest.mark.asyncio
async def test_submit_words_no_game(
bot: client.WordDebtBot, player: game_lib.WordDebtPlayer
):
ctx = AsyncMock()
ctx.author.id = player.user_id
game_cmds_cog = bot.get_cog("Core Gameplay Module")
cmd_err_cog = bot.get_cog("Command Error Handler")
game_cmds_cog.game = None
cmd_err_cog.game = None
@bot.command()
async def log_test_10(ctx, words=10):
log = bot.get_command("log")
await log(ctx, words)
game_cmds_cog.log_test_10 = log_test_10
with pytest.raises(commands.CommandInvokeError) as err:
await game_cmds_cog.log_test_10.invoke(ctx)
await cmd_err_cog.on_command_error(ctx, err.value)
ctx.send.assert_called_with(String() & Regex("Game not loaded.*"))
在我看来,这基本上是在子命令提升它的
CommandInvokeError
时使用包装命令的 AttributeError
实例。