Python 的单元测试无法为 Discord 机器人测试传递参数“缺少 1 个必需的位置参数:'ctx'”?

Posted

技术标签:

【中文标题】Python 的单元测试无法为 Discord 机器人测试传递参数“缺少 1 个必需的位置参数:\'ctx\'”?【英文标题】:Python's unittest fails to pass in parameter "missing 1 required positional argument: 'ctx'" for Discord bot testing?Python 的单元测试无法为 Discord 机器人测试传递参数“缺少 1 个必需的位置参数:'ctx'”? 【发布时间】:2021-11-29 07:15:20 【问题描述】:

我正在尝试为我的 Discord.py 命令之一编写一个简单的测试用例:

https://github.com/NobleUplift/Hierarchies/blob/master/cogs/HierarchyManagement.py#L32

函数定义很简单:

async def list(self, ctx: discord.ext.commands.Context):

我正在尝试编写一个单元测试,暂时将 ctx.send 替换为 print,以验证我的 Discord 命令的输出,最终目标是添加验证命令输出的断言:

Hierarchies: 
 • us-army
 • admins

这是我的完整测试用例:

import unittest
from unittest.mock import Mock
from unittest.mock import patch

from cogs.HierarchyManagement import HierarchyManagement

class test_Hierarchies(unittest.IsolatedAsyncioTestCase):
    async def test_list(self):
        bot = Mock() # commands.Bot()
        ctx = Mock() # discord.ext.commands.Context()
        ctx.message.content = '^list'
        ctx.message.guild.id = 870166294127837205
        ctx.author.mention = '<@368187588340875284>'
        ctx.author.name = 'NobleUplift'
        ctx.author.discriminator = '1038'
        ctx.send = lambda retval: print(retval)
        print(ctx.message.guild.id)
        hierarchyManagement = HierarchyManagement(bot)
        print(ctx)
        print(hierarchyManagement)
        await hierarchyManagement.list(ctx)

if __name__ == '__main__':
    unittest.main()

这是我收到的错误:

Error
Traceback (most recent call last):
  File "C:\Program Files\Python38\lib\unittest\case.py", line 60, in testPartExecutor
    yield
  File "C:\Program Files\Python38\lib\unittest\case.py", line 676, in run
    self._callTestMethod(testMethod)
  File "C:\Program Files\Python38\lib\unittest\async_case.py", line 65, in _callTestMethod
    self._callMaybeAsync(method)
  File "C:\Program Files\Python38\lib\unittest\async_case.py", line 88, in _callMaybeAsync
    return self._asyncioTestLoop.run_until_complete(fut)
  File "C:\Program Files\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
    return future.result()
  File "C:\Program Files\Python38\lib\unittest\async_case.py", line 102, in _asyncioLoopRunner
    ret = await awaitable
  File "Hierarchies\cogs\test_HierarchyManagement.py", line 21, in test_list
    await hierarchyManagement.list(ctx)
  File "Python38\site-packages\discord\ext\commands\core.py", line 374, in __call__
    return await self.callback(*args, **kwargs)
TypeError: list() missing 1 required positional argument: 'ctx'

为什么它告诉我它需要ctx 当我很清楚地传入 ctx 变量时?

【问题讨论】:

【参考方案1】:

这里的问题是当你定义一个 discord.py 命令时,你需要使用 @command() 装饰器。装饰器自动进行一些转换并将HierarchyManagement.list 重新定义为代表list 命令的discord.Command 对象。由于在没有实例 self 的情况下运行装饰器,因此您的 list 方法将作为无自我函数存储在 hierarchyManagement.list._callback

当您调用hierarchyManagement.list() 时,您实际上是在调用重定向到hierarchyManagement.list._callback()commands.Command.__call__() dunder 方法。而且我们之前确定回调没有self 引用。

这就是为什么,你需要这样做:

await hierarchyManagement.list(hierarchyManagement, ctx)

discord.py 运行时它“神奇”地工作的原因是因为在命令参数解析期间,它们在运行时将self 注入到函数回调中。由于直接调用hierarchyManagement.list 会跳过所有解析,因此self 没有在您的情况下注入。

顺便说一句,ctx.send 是一个协程,但是当您在模拟 ctx.send = lambda retval: print(retval) 中定义它时,它是一个常规函数,稍后会引发错误。

【讨论】:

非常感谢!这让我发疯了。你知道什么好笑吗?我尝试同时执行 hierarchyManagement.list(ctx=ctx)hierarchyManagement.list(hierarchyManagement=hierarchyManagement, ctx=ctx) 但我没有听到错误告诉我使用位置参数而不是关键字参数并将 2+2 放在一起。 你知道如何将ctx.send 模拟为协程以便打印到控制台吗? @NobleUplift 不使用 lambda,而是使用 async def send(*args, **kwargs): print(args, kwargs) 并设置 ctx.send = send 我在周末开始工作并开始编写我的测试用例。谢谢您的帮助!很高兴终于开始使用这个机器人进行测试驱动开发。

以上是关于Python 的单元测试无法为 Discord 机器人测试传递参数“缺少 1 个必需的位置参数:'ctx'”?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 XML 输出运行 Python 单元测试

python单元测试中无法使用谷歌浏览器打开测试报告的解决办法

为啥 Discord bot 无法识别命令?

(Python) Discord 机器人代码返回“RuntimeError:无法关闭正在运行的事件循环”

Discord bot无法连接到语音通道(python)

Discord bot 无法使用 Python 在 Heroku 上运行