【问题标题】:How to inject pygame events from pytest?如何从 pytest 注入 pygame 事件?
【发布时间】:2020-11-30 03:18:18
【问题描述】:

如何将事件从 pytest 测试模块注入到正在运行的 pygame 中?

以下是一个 pygame 的最小示例,它在按下 J 时绘制一个白色矩形,并在按下 Ctrl-Q 时退出游戏。

#!/usr/bin/env python
"""minimal_pygame.py"""

import pygame


def minimal_pygame(testing: bool=False):
    pygame.init()
    game_window_sf = pygame.display.set_mode(
            size=(400, 300), 
        )
    pygame.display.flip()
    game_running = True
    while game_running:
        # Main game loop:
        # the following hook to inject events from pytest does not work:
        # if testing:
            # test_input = (yield)
            # pygame.event.post(test_input)
        for event in pygame.event.get():
            # React to closing the pygame window:
            if event.type == pygame.QUIT:
                game_running = False
                break
            # React to keypresses:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_q:
                    # distinguish between Q and Ctrl-Q
                    mods = pygame.key.get_mods()
                    # End main loop if Ctrl-Q was pressed
                    if mods & pygame.KMOD_CTRL:
                        game_running = False
                        break
                # Draw a white square when key J is pressed:
                if event.key == pygame.K_j:
                    filled_rect = game_window_sf.fill(pygame.Color("white"), pygame.Rect(50, 50, 50, 50))
                    pygame.display.update([filled_rect])
    pygame.quit()


if __name__ == "__main__":
    minimal_pygame()

我想编写一个pytest 模块来自动测试它。我有read 可以将事件注入到运行pygame 中。 Here 我读到yield from 允许双向通信,所以我认为我必须为从pytest 模块注入的pygame.events 实现某种钩子,但这并不像我想象的那么简单,所以我注释掉了。如果我取消注释while game_running 下的测试挂钩,pygame 甚至不会等待任何输入。

这里是 pytest 的测试模块:

#!/usr/bin/env python
"""test_minimal_pygame.py"""

import pygame
import minimal_pygame


def pygame_wrapper(coro):
    yield from coro


def test_minimal_pygame():
    wrap = pygame_wrapper(minimal_pygame.minimal_pygame(testing=True))
    wrap.send(None) # prime the coroutine
    test_press_j = pygame.event.Event(pygame.KEYDOWN, {"key": pygame.K_j})
    for e in [test_press_j]:
        wrap.send(e)

【问题讨论】:

    标签: python pygame pytest


    【解决方案1】:

    Pygame 可以响应自定义用户事件,而不是按键或鼠标事件。这是一个工作代码,其中pytestpygame 发送用户事件,pygame 对其做出反应并将响应发送回pytest 进行评估:

    #!/usr/bin/env python
    """minimal_pygame.py"""
    
    import pygame
    
    
    TESTEVENT = pygame.event.custom_type()
    
    
    def minimal_pygame(testing: bool=False):
        pygame.init()
        game_window_sf = pygame.display.set_mode(
                size=(400, 300), 
            )
        pygame.display.flip()
        game_running = True
        while game_running:
            # Hook for testing
            if testing:
                attr_dict = (yield)
                test_event = pygame.event.Event(TESTEVENT, attr_dict)
                pygame.event.post(test_event)
            # Main game loop:
            pygame.time.wait(1000)
            for event in pygame.event.get():
                # React to closing the pygame window:
                if event.type == pygame.QUIT:
                    game_running = False
                    break
                # React to keypresses:
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        # distinguish between Q and Ctrl-Q
                        mods = pygame.key.get_mods()
                        # End main loop if Ctrl-Q was pressed
                        if mods & pygame.KMOD_CTRL:
                            game_running = False
                            break
                # React to TESTEVENTS:
                if event.type == TESTEVENT:
                    if event.instruction == "draw_rectangle":
                        filled_rect = game_window_sf.fill(pygame.Color("white"), pygame.Rect(50, 50, 50, 50))
                        pygame.display.update([filled_rect])
                        pygame.time.wait(1000)
                        if testing:
                            # Yield the color value of the pixel at (50, 50) back to pytest
                            yield game_window_sf.get_at((50, 50))
        pygame.quit()
    
    
    if __name__ == "__main__":
        minimal_pygame()
    

    这是测试代码:

    #!/usr/bin/env python
    """test_minimal_pygame.py"""
    
    import minimal_pygame
    import pygame
    
    
    def pygame_wrapper(coro):
        yield from coro
    
    
    def test_minimal_pygame():
        wrap = pygame_wrapper(minimal_pygame.minimal_pygame(testing=True))
        wrap.send(None) # prime the coroutine
        # Create a dictionary of attributes for the future TESTEVENT
        attr_dict = {"instruction": "draw_rectangle"}
        response = wrap.send(attr_dict)
        assert response == pygame.Color("white")
    

    它可以工作,但是,pytest 作为无状态单元测试而非集成测试的工具,会使 pygame 在获得第一个响应(拆卸测试)后退出。在当前的 pygame 会话中无法继续并执行更多测试和断言。 (只是尝试复制测试代码的最后两行重新发送事件,它会失败。)Pytest 不是向 pygame 注入一系列指令以使其达到前置条件然后执行一系列测试的正确工具.

    这至少是我在 pygame discord 频道上从人们那里听到的。对于自动化集成测试,他们建议使用 BDD 工具,如 Cucumber(或 behave 用于 python)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      相关资源
      最近更新 更多