当使用 pytest 发生 CommandInvokeError 并且该命令具有 kwargs 时,如何确保不和谐机器人从 on_command_error() 发送消息?

问题描述 投票:0回答:1

首先提前致谢!如果您需要其他任何东西,例如 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()
或触发事件以捕获然后确保它发送正确的“游戏未加载...”消息。对于具有必需参数的机器人命令,我该如何执行此操作?

注意:机器人按预期工作,但单元测试失败。

python unit-testing discord.py pytest parameter-passing
1个回答
0
投票

可能有更好的方法来做到这一点,我发现的解决方案可能看起来很混乱,但我能够通过以下方式解决这个问题:

  1. 创建围绕正在测试的命令的包装命令。

  2. 通过包装器命令将所需的参数传递给正在测试的命令。

  3. 将包装的命令添加到包含原始命令的齿轮中。

  4. 调用在 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
实例。

© www.soinside.com 2019 - 2024. All rights reserved.