자동화 테스트

Ren'Py에서는 게임에 자동화 테스트를 넣어 변경 사항이 기존 기능을 망가뜨리지 않는지 확인할 수 있습니다. 특히 규모가 크거나 자주 업데이트되는 게임에 유용합니다.

테스트 시스템의 두 핵심 구성 요소는 testcase 문과 testsuite 문입니다.

renpy.is_in_test() 함수로 현재 테스트 실행 중인지 확인할 수 있습니다.

빠른 시작

이 절에서는 실제 테스트 케이스에서 자주 사용하는 문으로 최소한의 예제를 보여 줍니다.

  • run Jump("...")로 정해진 레이블에서 시작합니다.

  • advance until screen "choice"로 메뉴까지 진행합니다.

  • click "Choice Text"로 메뉴 선택지를 선택합니다.

  • click id "..." until not screen "..."으로 스크린이 사라질 때까지 계속 클릭합니다.

먼저 게임 스크립트에 다음 Ren'Py 코드를 추가하세요. 테스트할 코드입니다.

screen quickstart_popup():
    modal True

    frame:
        xalign 0.5
        yalign 0.5

        vbox:
            spacing 12
            text "빠른 시작 팝업"

            textbutton "닫기":
                id "quickstart_close"
                action Hide("quickstart_popup")


label quickstart_demo:
    "테스트 케이스 빠른 시작 데모에 오신 것을 환영합니다."

    show screen quickstart_popup
    "계속하려면 팝업을 닫아 주세요."

    menu:
        "지도를 가져간다":
            "지도를 챙겼습니다."
        "두고 간다":
            "지도를 두고 갔습니다."

    "데모가 끝났습니다."
    return

다음으로 위 코드를 실행하고 예상대로 작동하는지 확인할 테스트 케이스를 추가합니다.

테스트 케이스는 위 스크립트와 같은 파일이나 다른 파일에 넣을 수 있습니다. 예를 들어 testcases.rpy라는 파일에 다음을 추가할 수 있습니다.

testsuite quick_start:
    before testcase:
        run Jump("quickstart_demo")
        advance until screen "quickstart_popup"

    testcase choose_map:
        pause 0.5
        click id "quickstart_close" until not screen "quickstart_popup"
        pause 0.5
        advance until screen "choice"
        click "지도를 가져간다"
        advance until "지도를 챙겼습니다."
        pause 0.5

    testcase leave_map:
        pause 0.5
        click id "quickstart_close" until not screen "quickstart_popup"
        pause 0.5
        advance until screen "choice"
        click "두고 간다"
        advance until "지도를 두고 갔습니다."
        pause 0.5

참고

이 데모의 일시 정지는 필수가 아닙니다. 동작을 확인할 수 있도록 테스트 실행을 늦추기 위한 것입니다. 실제 테스트에서는 제거하여 더 빠르게 실행할 수 있습니다.

파일을 저장한 뒤 테스트 케이스 실행을 참조하여 런처나 명령줄에서 실행하세요. 명령줄에서 실행한다면 명령은 다음과 같은 형태입니다.

./renpy.sh /path/to/game test quick_start

테스트 케이스 실행

런처

런처에서 프로젝트를 선택하고 “테스트 케이스 실행(Run Testcases)” 버튼을 누르세요. 버튼이 보이지 않으면 다음을 수행하세요.

  1. 게임에 테스트 케이스가 하나 이상 정의되어 있는지 확인합니다.

  2. “프로젝트 실행(Launch Project)” 버튼으로 게임을 정상 실행합니다.

  3. 런처의 “새로 고침(Refresh)” 버튼을 클릭합니다.

기본적으로 "global" 테스트 모음을 실행합니다.

명령줄

명령줄에서 실행하려면 Ren'Py SDK 디렉터리에서 터미널을 열고 test 명령을 사용하세요.

cd /path/to/renpy
./renpy.sh <basedir> test [<testcase>] [options...]
<basedir>

프로젝트 경로를 지정합니다.

<testcase>

실행할 테스트 케이스나 테스트 모음의 이름을 지정합니다. 지정하지 않으면 "global" 테스트 모음을 실행합니다.

--enable_all

enabled 속성과 관계없이 모든 테스트 케이스와 테스트 모음을 실행합니다.

--overwrite_screenshots

screenshot 문 실행 시 기존 스크린샷을 덮어씁니다.

--hide-header

테스트 실행 시작 시 머리말을 표시하지 않습니다.

--hide-execution {no|hooks|testcases|all}

테스트 실행 정보를 숨깁니다. --hide-execution hooks는 훅을, --hide-execution testcases는 테스트 케이스와 훅을, --hide-execution all은 모든 정보를 숨깁니다.

--hide-summary

테스트 실행 종료 시 요약을 표시하지 않습니다.

--report-detailed

실행 중 각 테스트의 자세한 정보를 표시합니다.

--report-skipped

건너뛴 테스트의 정보를 표시합니다. --report-detailed와 함께 사용해야 합니다.

testcase 문

testcase 문은 이름이 있는 테스트 케이스를 만듭니다. 각 케이스에는 테스트 문 블록(아래 참조)이 들어갑니다. 테스트 케이스는 Ren'Py 레이블과 비슷하지만 다음 차이가 있습니다.

  • Ren'Py label 문은 Ren'Py 코드를 받지만 testcase 문은 이 페이지에 나열한 테스트 문을 받습니다. 서로 바꾸어 사용할 수 없습니다.

  • testcase에는 return 문에 대응하는 문이 없습니다.

  • Ren'Py 코드는 레이블 밖에 있을 수 있지만, 테스트 문은 테스트 블록 밖에 있을 수 없습니다.

다음 속성을 받습니다.

description

테스트 케이스를 설명하는 문자열입니다. 테스트 보고서에서 사용합니다.

enabled

이 표현식의 평가 결과가 False이면 테스트를 건너뜁니다. 기본값은 True입니다.

예를 들어 지원하지 않는 플랫폼에서 테스트를 비활성화하는 등 조건에 따라 테스트를 끌 수 있습니다.

testcase windows:
    enabled renpy.windows
    ...

testcase not_on_mobile:
    enabled not renpy.mobile
    ...

자세한 내용은 테스트 케이스 건너뛰기를 참조하세요.

only

이 표현식의 평가 결과가 True이면 이 테스트 케이스와 only True인 다른 테스트만 실행합니다. 기본값은 False입니다.

자세한 내용은 테스트 케이스 건너뛰기를 참조하세요.

xfail

이 표현식의 평가 결과가 True이면 실패할 것으로 예상하는 테스트입니다. 실제로 실패하면 failed 대신 xfailed로 표시합니다. 기본값은 False입니다.

parameter

변수 이름(또는 변수 이름 튜플)과 값 목록(또는 값 튜플 목록)입니다. 목록의 각 값 또는 값 튜플에 대해 테스트를 한 번씩 실행합니다.

테스트에는 parameter 속성이 여러 개 있을 수 있으며, 이 경우 가능한 모든 값 조합에 대해 실행합니다.

자세한 내용은 매개변수화 테스트를 참조하세요.

testsuite 문

testsuite 문으로 테스트 케이스를 묶습니다. 테스트 모음에는 테스트 케이스, 다른 테스트 모음, 훅(아래 참조)을 넣을 수 있습니다.

기본 테스트 모음의 이름은 global이며, 사용자가 지정하지 않으면 Ren'Py가 자동 생성합니다. 게임의 나머지 모든 최상위 테스트 모음과 테스트 케이스를 포함합니다.

testcase 문과 같은 속성을 받습니다.

훅

testsuite 문에는 다음 훅을 넣을 수 있습니다.

setup

현재 테스트 모음 안의 테스트를 실행하기 전에 모음 시작 시 실행하는 테스트 문 블록입니다.

before testsuite

현재 모음 안의 각 테스트 모음을 실행하기 전에 실행하는 테스트 문 블록입니다.

before testcase

현재 모음 안의 각 테스트 케이스를 실행하기 전에 실행하는 테스트 문 블록입니다.

after testcase

현재 모음 안의 각 테스트 케이스를 실행한 뒤 실행하는 테스트 문 블록입니다. 테스트 케이스가 실패하거나 예외를 발생시켜도 실행됩니다.

after testsuite

현재 모음 안의 각 테스트 모음을 실행한 뒤 실행하는 테스트 문 블록입니다. 테스트 모음이 실패하거나 예외를 발생시켜도 실행됩니다.

teardown

현재 모음의 모든 테스트를 실행한 뒤 실행하는 테스트 문 블록입니다. 테스트가 실패하거나 예외를 발생시켜도 실행됩니다.

before * 및 after * 훅은 다음 속성을 받습니다.

depth

훅을 적용할 깊이를 지정하는 정수입니다.

테스트 케이스의 기본값은 -1이며, 중첩된 모든 테스트 모음과 테스트 케이스에 적용합니다.

테스트 모음의 기본값은 0이며, 현재 모음에 직접 포함된 테스트 모음에만 적용합니다.

자세한 내용은 테스트 실행 수명 주기를 참조하세요.

테스트 실행 수명 주기

이 절에서는 테스트 케이스와 테스트 모음의 실행 순서, 훅 호출 방식을 설명합니다. 다음 예제를 살펴보세요.

코드

실행 순서

label test_demo:
    "테스트 케이스를 위한 데모입니다."
    "몇 개의 메시지와 메뉴가 있습니다."
    menu:
        "첫 번째 선택지":
            "첫 번째 선택지를 골랐습니다."
        "두 번째 선택지":
            "두 번째 선택지를 골랐습니다."
        "세 번째 선택지":
            "세 번째 선택지를 골랐습니다."
    return

testsuite global:
    setup:
        $ print("global :: setup")
        skip until screen "main_menu"

    before testsuite:
        $ print("global :: before testsuite")
        if not screen "main_menu":
            run MainMenu(confirm=False)
        click "시작"

    before testcase:
        $ print("global :: before testcase")

    after testcase:
        $ print("global :: after testcase")

    after testsuite:
        $ print("global :: after testsuite")

    teardown:
        $ print("global :: teardown")
        exit

    testsuite basic:
        testcase first_testcase:
            $ print("basic :: first_testcase")
            advance

    testsuite test_choices:
        setup:
            $ print("test_choices :: setup")

        before testcase:
            $ print("test_choices :: before testcase")
            run Jump("test_demo")
            advance until screen "choice"

        after testcase:
            $ print("test_choices :: after testcase")

        teardown:
            $ print("test_choices :: teardown")

        testcase choice1:
            $ print("test_choices :: choice1")
            click "첫 번째 선택지"

        testcase choice2:
            enabled False
            $ print("test_choices :: choice2 (disabled)")
            click "두 번째 선택지"

        testcase choice3:
            $ print("test_choices :: choice3")
            click "세 번째 선택지"

global :: setup

global :: before testsuite

global :: before testcase

basic :: first_testcase

global :: after testcase

global :: after testsuite

global :: before testsuite

test_choices :: setup

global :: before testcase

test_choices :: before testcase

test_choices :: choice1

test_choices :: after testcase

global :: after testcase

global :: before testcase

test_choices :: before testcase

test_choices :: choice3

test_choices :: after testcase

global :: after testcase

test_choices :: teardown

global :: after testsuite

global :: teardown

global :: before testcase와 global :: after testcase는 중첩된 테스트 모음 안의 테스트 케이스를 포함하여 각 테스트 케이스의 실행 전후에 호출됩니다.

훅의 적용 범위를 제한하려면 depth 속성을 설정하세요. 0으로 설정하면 해당 훅이 있는 테스트 모음에 직접 포함된 테스트에만 실행됩니다.

예를 들면 다음과 같습니다.

testsuite global:
    before testcase:
        depth 0
        $ print("Starting a testcase.")

한편 before testsuite와 after testsuite 훅의 기본 depth는 0이므로, 해당 훅이 있는 모음에 직접 포함된 테스트 모음에만 실행됩니다.

중첩된 테스트 모음과 테스트 케이스까지 범위를 넓히려면 depth를 -1(무한 깊이) 또는 양의 정수(특정 깊이)로 설정하세요.

참고

테스트 모음의 실행이 끝나도 게임은 자동으로 종료되지 않습니다. 플레이어에게 제어를 돌려주고 사용자 입력을 기다립니다.

테스트 모음 실행 후 게임을 종료하려면 해당 모음의 after 훅에서 exit 테스트 문을 사용할 수 있습니다.

testsuite global:
    teardown:
        exit

테스트 케이스 건너뛰기

건너뛴 테스트 케이스는 실행되지 않습니다. 해당 테스트 케이스에 대한 테스트 모음의 before testcase와 after testcase 훅도 실행되지 않습니다.

테스트 모음의 모든 테스트를 건너뛰면 setup과 teardown 훅도 실행하지 않습니다. 상위 테스트 모음의 before testsuite와 after testsuite 훅도 실행하지 않습니다.

매개변수화 테스트

parameter 속성을 사용하면 다른 값으로 테스트 케이스를 여러 번 실행할 수 있습니다.

변수 이름과 값 목록을 지정하세요. 목록의 각 값에 대해 테스트를 한 번씩 실행합니다. 예를 들면 다음과 같습니다.

testcase click_buttons:
    parameter button_name = ["불러오기", "저장"]
    click expression button_name

이 예제는 두 번 실행됩니다. 먼저 "Load"를 클릭하고, 다음에는 "Save"를 클릭합니다.

매개변수는 여러 테스트 모음이나 테스트 케이스를 정의하는 것과 같다고 보면 됩니다. 각 값마다 setup과 teardown을 포함한 훅을 실행합니다.

매개변수 묶기

여러 변수를 괄호로 묶고 값 묶음의 목록을 지정하여 한꺼번에 설정할 수 있습니다. 예를 들면 다음과 같습니다.

testcase addition:
    parameter (x, y, z) = [ (1, 2, 3), (2, 3, 5), (3, 5, 8) ]
    assert eval (x + y == z)

다음 값으로 세 번 실행합니다. (x=1, y=2, z=3), (x=2, y=3, z=5), (x=3, y=5, z=8).

매개변수 조합

parameter 속성을 여러 개 지정하면 가능한 모든 값 조합에 대해 테스트 케이스를 실행합니다. 예를 들면 다음과 같습니다.

testcase combinations:
    parameter a = [1, 2]
    parameter b = [3, 4]
    parameter c = [5, 6]

    assert eval (a + b + c in [9, 10, 11, 12])

(a, b, c)의 각 조합에 대해 한 번씩, 총 여덟 번 실행합니다.

(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)

묶은 매개변수와 개별 매개변수를 함께 사용할 수 있습니다. 예를 들면 다음과 같습니다.

testcase mixed:
    parameter a = [1, 2]
    parameter (b, c) = [ (3, 5), (4, 6) ]

    assert eval (a + b + c in [9, 10, 11, 12])

(a, (b, c))의 다음 조합으로 총 네 번 실행합니다.

(1, (3, 5)), (1, (4, 6)), (2, (3, 5)), (2, (4, 6))

표현식에서 매개변수 사용

표현식을 받는 모든 테스트 속성에서 매개변수를 사용할 수 있습니다.

예를 들어 다음 테스트는 x의 각 값으로 한 번씩 세 번 실행합니다. x가 0 또는 1이면 통과하고, x가 2이면 실패할 것으로 예상합니다(xfail).

testcase choice_test:
    parameter x = [0, 1, 2]
    xfail x == 2

    assert eval (x < 2)

매개변수로 스크린이나 버튼을 이름으로 선택할 수도 있습니다. 다음 테스트는 choice_text 값에 따라 "first" 또는 "second" 선택지를 클릭합니다.

testcase show_menu:
    parameter screen_name = ["preferences", "load"]

    run ShowMenu(screen_name)
    pause until screen screen_name
    run Return()

매개변수 이름 앞에 expression을 붙여 해당 매개변수로 버튼을 선택할 수 있습니다.

testcase click_buttons:

parameter button_name = ["Load", "Save"]

click expression button_name

Python 코드 블록 안에서도 매개변수를 사용할 수 있습니다. 다음 테스트는 x와 y의 현재 값을 출력하고 해당 위치를 클릭합니다.

testcase param_test:
    parameter (x, y) = [(0.0, 0.0), (0.5, 0.5), (1.0, 1.0)]

    $ print(f"Clicking at position ({x}, {y})")
    click pos (x, y)

매개변수화 테스트 모음

테스트 모음 전체에도 매개변수를 지정할 수 있습니다. 이 경우 각 매개변수 집합마다 모음 안의 모든 훅과 테스트 케이스를 한 번씩 실행합니다.

매개변수를 바꾸어 실행할 때마다 setup, before/after testsuite, teardown 훅을 실행합니다.

예를 들면 다음과 같습니다.

testsuite math_tests:
    parameter (x, y, z) [ (1, 2, 3), (2, 3, 5), (3, 5, 8) ]

    setup:
        $ print(f"Running math tests with x={x}, y={y}, z={z}")

    testcase addition:
        assert eval (x + y == z)

    testcase multiplication:
        assert eval (x*y == z*y - y*y)

매개변수를 중첩할 수도 있으며 모든 조합을 테스트합니다. 예를 들면 다음과 같습니다.

testsuite parameter_field:
    parameter choice_text = ["첫 번째", "두 번째"]

    testcase param_test2:
        parameter (x, y) = [(0.0, 0.0), (0.5, 0.5)]

        advance until screen "choice"
        click choice_text
        click pos (x, y)

(choice_text, (x, y))의 각 조합으로 한 번씩 네 번 실행합니다.

("first", (0.0, 0.0)), ("first", (0.5, 0.5)), ("second", (0.0, 0.0)), ("second", (0.5, 0.5))

경고

매개변수는 참조로 전달됩니다. 목록이나 딕셔너리 같은 변경 가능한 매개변수를 수정하면 같은 객체를 공유하는 다른 모든 테스트에도 영향을 줍니다.

예외와 실패

테스트 케이스 도중 오류가 발생하면 다음과 같이 처리합니다.

  1. 테스트 케이스 실행을 즉시 중단합니다.

  2. 해당 테스트 케이스가 속한 모음의 after testcase 훅을 실행합니다.

  3. 테스트 케이스가 더 있으면 before testcase 훅을 포함하여 계속 실행합니다.

  4. 남은 테스트 케이스가 없으면 모음의 after 훅을 실행합니다.

훅(before testcase 등) 도중 오류가 발생하면 다음과 같이 처리합니다.

  1. 테스트 모음 실행을 즉시 중단합니다.

  2. 다른 모음에서 호출된 경우 상위 모음의 실행은 계속합니다.

  3. 상위 모음이 없으면 게임이 테스트 실행을 마칩니다.

테스트 보고

테스트 실행 후 콘솔에 모든 테스트 케이스와 결과를 나열하는 보고서를 출력합니다. --print_details 옵션을 지정하면 각 테스트의 추가 정보를 포함합니다.

다음은 "The Question" 테스트에 성공한 뒤의 보고서 예제입니다.

Test report example

테스트 결과

테스트 결과는 다음 중 하나입니다.

  • Passed: 오류 없이 정상적으로 실행되었습니다.

  • Failed: 실행은 되었지만 문 중 하나가 실패했습니다.

  • XFailed: xfail 속성이 True여서 실패를 예상했으며 실제로 실패했습니다.

  • XPassed: xfail 속성이 True여서 실패를 예상했지만 통과했습니다.

  • Skipped: enabled 속성이 False이거나 only True인 다른 테스트가 있어 건너뛰었습니다.

일반적으로 passed나 xfailed이면 성공으로, failed나 xpassed이면 실패로 간주합니다.

테스트 설정

다음 변수로 테스트 동작을 변경할 수 있습니다.

_test.maximum_framerate

테스트 중 최대 프레임 속도 모드를 사용할지 지정하는 불리언입니다. 가능하다면 화면 주사율보다 높게 프레임 속도 제한을 해제합니다. 기본값은 True입니다.

_test.timeout

테스트 문이 조건 충족을 기다릴 최대 시간을 초 단위로 지정하는 실수입니다. 기본값은 10.0입니다.

assert와 until처럼 timeout을 지원하는 문에서는 이 속성을 지정하여 문별로 재정의할 수 있습니다.

_test.force

renpy.config.suppress_underlay가 True여도 테스트를 강제로 진행할지 지정하는 불리언입니다. 기본값은 False입니다.

_test.transition_timeout

화면 전환을 건너뛰고 테스트를 계속하기 전에 완료를 기다릴 최대 시간을 초 단위로 지정하는 실수입니다. 기본값은 5.0입니다.

_test.focus_trials

위치가 없는 선택자를 사용할 때 테스트 시스템이 마우스를 이동할 유효한 지점을 찾는 최대 시도 횟수입니다. 정수이며 기본값은 100입니다.

_test.screenshot_directory

스크린샷을 저장할 디렉터리를 지정하는 문자열입니다. 기본값은 tests/screenshots입니다.

_test.vc_revision

확인할 수 있는 경우 현재 소스 트리의 버전 관리(대개 git) 리비전입니다. 기본값은 RENPY_TEST_VC_REVISION 환경 변수이며, 설정되지 않았으면 빈 문자열입니다.

테스트 문

테스트 문은 테스트 케이스를 구성하는 기본 요소입니다. 크게 명령 문, 조건/선택자 문, 제어 문으로 나눌 수 있습니다.

기본 명령

Advance

종류: 명령

advance

게임을 대사 한 줄만큼 진행합니다.

advance
advance until screen "choice"

Exit

종류: 명령

exit

확인 스크린을 호출하지 않고 게임을 종료합니다. 종료 시 게임을 저장하지 않습니다.

if eval need_to_confirm:
    # Asks for confirmation, and autosaves if config.autosave_on_quit is True
    run Quit(confirm=True)

if eval persistent.quit_test_using_action:
    # Does not ask, but still autosaves if config.autosave_on_quit is True
    run Quit(confirm=False)

exit # neither asks nor autosaves

Pass

종류: 명령

pass

아무것도 하지 않습니다. 빈 테스트 케이스를 만들 수 있게 하는 무동작 문입니다.

testcase not_yet_implemented:
    pass

Pause

종류: 명령

pause [time (float)]

지정한 초만큼 테스트 실행을 일시 정지합니다. pause 문과 비슷하지만 값이 필요합니다. 다만 뒤에 until 절이 있으면 시간을 생략할 수 있습니다.

pause 5.0
pause until screen "inventory"

Run

종류: 명령

run <action>

지정한 스크린 언어 액션 또는 액션 목록을 실행합니다.

해당 액션 또는 목록을 포함하는 버튼이 활성화될 수 있는 시점에 실행 준비 상태가 됩니다.

testcase chapter_3:
    run Jump("chapter_3")

Skip

종류: 명령

skip [fast]

게임의 건너뛰기를 시작합니다. 메뉴 컨텍스트라면 게임으로 돌아가고, 아니면 건너뛰기만 활성화합니다.

fast를 지정하면 다음 메뉴 선택지까지 즉시 건너뜁니다.

skip
skip fast
skip until screen "choice"

마우스 명령

Click

종류: 명령

click [button (int)] [selector] [pos (x, y)]

화면 클릭을 시뮬레이션합니다. 다음 선택적 속성을 받습니다.

  • button은 시뮬레이션할 마우스의 어느 버튼으로 클릭할지

    지정합니다. 정수이며 기본값은 1입니다. 1은 왼쪽 클릭, 2는 오른쪽 클릭, 3은 가운데 클릭이고 4와 5는 일부 마우스의 추가 버튼입니다. 보통 Ren'Py는 1과 2에만 반응합니다.

selector 또는 pos를 지정하면 클릭을 보내기 전에 move 문 규칙에 따라 가상 테스트 마우스를 이동합니다.

Click은 패턴이 지정되지 않은 패턴 절처럼 동작합니다. pos를 지정하지 않으면 포커스 가능한 요소를 클릭하지 않는 중립적인 위치를 찾습니다.

참고

게임 대사를 진행하려면 advance나 skip 문을 사용하세요. 클릭은 마우스 위치와 현재 화면 내용에 따라 예측하기 어려운 결과를 낳을 수 있습니다.

Drag

종류: 명령

drag <[selector] [pos (x, y)]> to <[selector] [pos (x, y)]> [button (int)] [steps (int)]

화면의 드래그 동작을 시뮬레이션합니다. 다음 속성을 받습니다.

  • 첫 부분(to 앞)은 드래그 시작점을 지정합니다. 선택적으로 selector 또는 pos 속성을 받으며, move 문 규칙에 따라 해석합니다.

  • 두 번째 부분(to 뒤)은 드래그 끝점을 지정합니다. 마찬가지로 선택적 selector 또는 pos 속성을 받으며, move 문 규칙에 따라 해석합니다.

  • button은 드래그에 사용할 가상 마우스 버튼을 지정합니다. 정수이며 기본값은 1입니다. 1은 왼쪽 클릭, 2는 오른쪽 클릭, 3은 가운데 클릭이고 4와 5는 일부 마우스의 추가 버튼입니다. 보통 Ren'Py는 1과 2에만 반응합니다.

  • steps는 드래그의 중간 단계 수를 지정합니다. 정수이며 기본값은 10입니다. 단계가 많으면 더 부드럽지만 시간도 더 걸립니다.

drag id "item_icon" to id "inventory_slot_3" button 1 steps 20
drag pos (100, 200) to pos (400, 500) button 1
drag id "item_icon" pos (0.5, 0.5) to pos (300, 400) steps 5
drag pos (50, 50) to id "inventory_slot_1"
drag pos (50, 50) to pos (150, 150)

Move

종류: 명령

move [selector] [pos (x, y)]

가상 테스트 마우스를 화면의 지정한 위치로 이동합니다.

selector를 지정한 경우:

  • pos가 있으면 선택자를 기준으로 해당 위치에 마우스를 이동합니다.

  • pos가 없으면 클릭 시 해당 선택자에 포커스를 주는 픽셀을 찾으려고 시도합니다. 이때 focus_mask 같은 요소도 고려합니다.

selector를 지정하지 않은 경우:

  • pos가 있으면 화면을 기준으로 해당 위치에 마우스를 이동합니다.

  • pos가 없으면 오류를 발생시킵니다.

# Move to a random clickable point within `back_btn`
move id "back_btn"

# Move to the center of `back_btn`
move id "back_btn" pos (0.5, 0.5)

# Move to a point 20 pixels right and 10 pixels down from the top-left corner of `back_btn`
move id "back_btn" pos (20, 10)

# Move to the top right corner of the screen
move pos (1.0, 0.0)

# Move to a point 20 pixels right and 10 pixels down from the top-left corner of the screen
move pos (20, 10)

Scroll

종류: 명령

scroll [amount (int)] [selector] [pos (x, y)]

스크롤 이벤트를 시뮬레이션합니다. 다음 선택적 속성을 받습니다.

  • amount는 스크롤할 “칸” 수를 지정합니다. 정수이며 기본값은 1입니다. 양수는 아래로, 음수는 위로 스크롤합니다.

  • selector 또는 pos를 지정하면 스크롤을 보내기 전에 move 문 규칙에 따라 가상 테스트 마우스를 이동합니다.

scroll "bar"
scroll id "inventory_scroll"
scroll amount 10 id "inventory_scroll" pos (0.5, 0.5)
scroll # scrolls down at the current mouse position

참고

마우스 휠 이벤트만 시뮬레이션합니다. 스크린 액션, 값, 함수의 Scroll 액션을 사용하는 방법도 고려하세요.

run Scroll("inventory_scroll", "increase", amount="step", delay=1.0)

키보드 명령

Keysym

종류: 명령

keysym <keysym> [selector] [pos (x, y)]

keysym 이벤트를 시뮬레이션합니다. config.keymap의 키도 포함됩니다.

selector 또는 pos를 지정하면 keysym을 보내기 전에 move 문 규칙에 따라 가상 테스트 마우스를 이동합니다.

keysym "skip"
keysym "help"
keysym "ctrl_K_a"
keysym "K_BACKSPACE" repeat 30
keysym "pad_a_press"

Type

종류: 명령

type <string> [selector] [pos (x, y)]

지정한 문자열을 키보드로 입력한 것처럼 입력합니다.

selector 또는 pos를 지정하면 텍스트를 보내기 전에 move 문 규칙에 따라 가상 테스트 마우스를 이동합니다.

type "안녕, 세상아!"

조건 문

조건은 특정 조건의 참과 거짓을 확인하는 데 사용합니다. if, assert, until처럼 조건을 받는 테스트 문에서 사용합니다.

불리언 값

테스트에서는 불리언 리터럴 True와 False를 사용할 수 있습니다. 이 값들은 항상 평가 준비 상태입니다.

if True:
    click "시작"

if False:
    click "설정" # does not execute, since the condition is always false

불리언 연산

조건은 not, and, or 연산자를 지원합니다. 표현식은 괄호로 감싸도 되고 감싸지 않아도 됩니다.

assert eval (renpy.is_in_test() and screen "main_menu")
advance until "ask her right" or label "chapter_five"
click "다음" until not screen "choice"

Eval

종류: 조건

eval <expression>

지정한 Python 표현식을 평가합니다. assert, if, until처럼 조건을 받는 테스트 문 안에서만 사용합니다.

assert eval (renpy.is_in_test() and ("Ren'Py" in renpy.version_string))

참고

달러 기호로 시작하는 줄과 eval 절의 차이는 다음과 같습니다.

  • Eval은 독립된 줄로 사용할 수 없고 if나 until 같은 문 안에 있어야 합니다. 반면 달러 줄은 반드시 독립된 줄이어야 합니다.

  • 달러 줄은 $ import math처럼 값이 없는 Python 문도 실행할 수 있지만 eval 절에는 반환값이 필요합니다.

Label

종류: 조건

label <labelname>

마지막 테스트 문 실행 이후 지정한 Ren'Py 레이블에 도달했는지 확인합니다.

다음 예제를 살펴보세요.

run Jump("chapter_1")
assert label chapter_1 # works
assert label chapter_1 # fails

첫 assert 문은 run Jump("chapter_1") 문이 chapter_1 레이블에 도달했으므로 작동합니다. 두 번째 assert 문은 첫 assert 이후 chapter_1 레이블에 다시 도달하지 않았으므로 실패합니다.

따라서 다음 예제도 작동하지 않습니다.

run Jump("chapter_1")
advance repeat 3
assert label chapter_1 # fails

경고

이 테스트 문을 검사 대상인 Ren'Py 기본 label 문이나, 스크린에서 사용하는 별개의 label 요소와 혼동하지 마세요.

선택자 문

선택자 문은 화면에 특정 요소가 있는지 확인하고 후속 동작에 해당 요소를 사용하는 데 쓰입니다.

선택자는 특수한 종류의 조건입니다.

디스플레이어블 선택자

종류: 조건, 선택자

지정한 ID의 스크린이나 요소가 현재 표시되는지 확인합니다.

스크린 이름을 매개변수 하나로 받습니다. 다음 속성을 받습니다.

screen <name>

확인할 스크린 이름입니다.

id <name>

확인할 요소의 ID입니다.

layer <name>

스크린이 표시되는 레이어입니다. 지정하지 않으면 스크린 이름으로 자동 결정합니다.

if screen "main_menu":
    click "시작"

advance until id "inventory_viewport" layer "overlay"

click "닫기" until not id "close_button"

텍스트 선택자

종류: 조건, 선택자

"<text>" [raw]
expression <expression> [raw]

text 선택자는 화면에서 찾을 대상을 나타내는 문자열을 받습니다. 화면의 모든 포커스 가능한 요소(대개 버튼과 기본 텍스트 상자)를 순회하며 텍스트와 alt 텍스트를 검색합니다.

대소문자를 구분하지 않으며 일치하는 항목 중 가장 짧은 것을 찾습니다. 예를 들어 "log"를 지정하고 화면에 "CATALOG"와 "illogical"이 있으면 대상은 "CATALOG" 텍스트입니다.

raw를 지정하면 번역과 문자열 보간 전의 스크립트 원문에서 검색합니다. 지정하지 않으면 번역과 보간을 거쳐 화면에 표시된 텍스트에서 검색합니다.

expression을 지정하면 해당 표현식을 평가하여 검색할 문자열을 정합니다.

# This may be in a button
skip until "게임 시작"

# This may be in the main textbox
advance until "이봐요, 그건 불공평하잖아요!"

# Case-insensitive search
assert "AsK HeR RighT AwaY"

# Search unsubstituted text
assert "어서 오세요, 에일린!"
assert "어서 오세요, [player_name]!" raw

# Search untranslated text after changing the language
run Language("japanese")
assert "スタート"
assert "Start" raw

제어 문

다음 문은 테스트 실행 흐름을 제어합니다.

Assert

종류: 제어

assert <condition> [timeout (float)] [xfail (bool)]

조건을 받으며, assert 문 실행 시 조건이 충족되지 않으면 RenpyTestAssertionError를 발생시킵니다.

timeout을 지정하면 해당 초만큼 조건 충족을 기다립니다. 그 안에 충족되지 않으면 검증에 실패합니다.

xfail이 True이면 assert 문의 실패를 예상합니다. 문의 의미가 반전되어 조건이 충족되면 검증에 실패하고 충족되지 않으면 통과합니다.

assert screen "main_menu"
assert eval some_function(args)
assert id "start_button" timeout 5.0

If

종류: 제어

if <condition>

지정한 조건이 충족되면 테스트 문 블록을 실행합니다.

예제:

if label "chapter_five":
    exit

if eval (persistent.should_advance and i_should_advance["now"]):
    advance

elif와 else 문으로 if 문에 추가 조건 분기를 넣을 수 있습니다.

if eval persistent.should_advance:
    advance
elif eval i_should_advance["now"]:
    advance
else:
    click "시작"

Repeat

종류: 제어

<command> repeat <number> [timeout (float)]

문을 지정한 횟수만큼 반복합니다. 왼쪽의 명령 문과 오른쪽의 반복 횟수를 repeat로 구분합니다.

click "+" repeat 3
keysym "K_BACKSPACE" repeat 10
advance repeat 3

Screenshot

종류: 명령

screenshot <path> [max_pixel_difference (int or float)] [crop (x, y, width, height)]

현재 화면의 스크린샷을 촬영하여 지정한 경로에 저장합니다.

  • path는 _test.screenshot_directory 기준의 저장 경로입니다. 확장자를 포함할 수 있으며 .png만 지원합니다.

  • max_pixel_difference는 촬영한 스크린샷과 기존 스크린샷 사이에 허용할 픽셀 차이를 지정합니다. 정수는 픽셀 수를, 실수는 전체 픽셀 수에 대한 비율을 지정합니다. 기본값은 0입니다.

  • crop은 스크린샷을 자를 사각형을 (x, y, width, height)로 지정합니다. 좌표는 정수여야 합니다.

프로젝트가 git 저장소에 있으면 현재 커밋 해시를 파일명 뒤에 @{hash}.png 형태로 자동 추가합니다. 시간에 따른 스크린샷 변경을 추적할 수 있습니다.

파일이 이미 있으면 현재 스크린샷과 비교합니다. 차이가 max_pixel_difference 픽셀보다 크면 RenpyTestScreenshotError를 발생시킵니다.

기존 스크린샷을 덮어쓰려면 파일을 삭제하거나 --overwrite_screenshots 명령줄 옵션으로 테스트를 실행하세요.

screenshot "screens/main_menu.png"
screenshot "screens/inventory" max_pixel_difference 0.01
screenshot "button.png" crop (10, 10, 100, 50)

매개변수화 테스트에서 여러 스크린샷을 촬영할 때 사용할 수 있습니다.

testcase screen_tester:
    parameter screen_name = ["inventory", "stats", "map"]

    run Show(screen_name)
    screenshot f"screens/{screen_name}.png"

Until

종류: 제어

<command> until <condition> [timeout (float)]

조건이 충족될 때까지 문을 반복합니다. 왼쪽의 명령 문과 오른쪽의 조건을 until로 구분합니다.

오른쪽 조건이 충족되면 다음 문으로 제어를 넘깁니다. 그렇지 않으면 조건이 평가 준비 상태가 될 때까지 왼쪽 문을 반복 실행합니다.

timeout을 지정하면 해당 초만큼 조건 충족을 기다립니다. 그 안에 충족되지 않으면 RenpyTestTimeoutError를 발생시킵니다.

이 제한 시간은 전역 _test.timeout 설정을 일시적으로 재정의합니다.

advance until screen "choice"
click "다음"
advance until label "chapter_5"

skip until screen "inventory" timeout 20.0

Python 블록과 달러 줄

테스트 케이스 안에 python 블록이나 한 줄 Python 문을 넣을 수 있습니다. 일반 Ren'Py 코드와 달리 python 블록은 in substore 매개변수를 받지 않지만 hide 키워드는 받습니다. 두 방식 모두 임의의 Python 코드를 실행할 수 있습니다.

초기화 코드는 테스트 전에 실행되므로 init python 블록에서 정의한 함수와 클래스를 테스트의 python 블록과 달러 줄에서 호출할 수 있습니다. 예를 들면 다음과 같습니다.

init python in test:
    def afunction():
        if renpy.is_in_test():
            return "test"
        return "not test"

testcase default:
    $ print(test.afunction()) # ends up in the console