테스트 프레임워크

wippy/test 모듈은 BDD 스위트, 어서션, 생명주기 훅, 모킹 및 테스트 엔트리 러너를 제공합니다.

이 페이지는 API 입문서입니다. Lua, YAML, 출력 및 프로젝트 레이아웃 블록은 기존 Wippy 프로젝트에서 조합해 사용할 수 있는 참고용 코드 조각이며, 하나의 복사 후 실행 가능한 프로젝트가 아닙니다. validate, format_name, db, connect, notify_user 같은 이름은 테스트 대상이 제공하는 애플리케이션 함수 또는 모듈을 나타냅니다. 완전히 실행 가능한 예제는 Wippy 애플리케이션 테스트를 따라 하세요.

설정

의존성을 추가합니다:

wippy add wippy/test
wippy install

모듈은 테스트 엔트리포인트(use_case: test를 가진 명령)를 자동으로 등록합니다. 설치 후 wippy test로 프로젝트의 모든 테스트 엔트리를 검색하고 실행합니다.

테스트 정의하기

테스트는 meta.type: test를 가진 function.lua 엔트리입니다:

version: "1.0"
namespace: app.test

entries:
  - name: math
    kind: function.lua
    meta:
      type: test
      suite: math
      name: Math operations
    source: file://math_test.lua
    method: main
    imports:
      test: wippy.test:test

테스트 메타데이터

필드 필수 설명
type 예 러너가 검색하려면 "test"여야 합니다
suite 아니요 러너 출력에서 테스트를 그룹화합니다
name 아니요 러너 출력에 표시되는 이름
order 아니요 스위트 내 정렬 순서 (낮을수록 먼저 실행)

테스트 작성하기

BDD 스타일

describe와 it 블록을 사용하여 테스트를 구조화합니다:

local test = require("test")

local function define_tests()
    test.describe("calculator", function()
        test.it("adds numbers", function()
            test.eq(1 + 1, 2)
        end)

        test.it("multiplies numbers", function()
            test.eq(3 * 4, 12)
        end)
    end)
end

local run_cases = test.run_cases(define_tests)

local function run(options)
    local result = run_cases(options)
    if result.failed_tests > 0 then
        error("tests failed: " .. result.failed_tests)
    end
    return result
end

return { run = run }

중첩 스위트

관련 동작을 그룹화하도록 스위트를 중첩할 수 있습니다:

test.describe("user", function()
    test.describe("validation", function()
        test.it("requires name", function()
            test.ok(validate({}).error)
        end)

        test.it("accepts valid input", function()
            test.is_nil(validate({name = "Alice"}).error)
        end)
    end)

    test.describe("formatting", function()
        test.it("formats display name", function()
            test.eq(format_name("alice"), "Alice")
        end)
    end)
end)

테스트 건너뛰기

test.it_skip("not implemented yet", function()
    test.fail("TODO")
end)

건너뛴 테스트는 출력에 표시되지만 실패로 집계되지 않습니다.

스위트 별칭

test.spec과 test.context는 test.describe의 별칭입니다:

test.spec("feature", function()
    test.context("when valid input", function()
        test.it("succeeds", function()
            test.ok(true)
        end)
    end)
end)

어서션

동등성

test.eq(actual, expected, msg?)       -- actual == expected
test.neq(actual, expected, msg?)      -- actual ~= expected

참/거짓 판별

test.ok(val, msg?)                    -- val is truthy
test.fail(msg?)                       -- unconditional failure

Nil 검사

test.is_nil(val, msg?)                -- val == nil
test.not_nil(val, msg?)               -- val ~= nil

타입 검사

test.is_true(val, msg?)               -- val == true
test.is_false(val, msg?)              -- val == false
test.is_string(val, msg?)
test.is_number(val, msg?)
test.is_table(val, msg?)
test.is_function(val, msg?)
test.is_boolean(val, msg?)

문자열 및 컬렉션

test.contains(str, substr, msg?)      -- substring match
test.matches(str, pattern, msg?)      -- Lua pattern match
test.has_key(tbl, key, msg?)          -- table key exists
test.len(val, expected, msg?)         -- #val == expected

수치 비교

test.gt(a, b, msg?)                   -- a > b
test.gte(a, b, msg?)                  -- a >= b
test.lt(a, b, msg?)                   -- a < b
test.lte(a, b, msg?)                  -- a <= b

오류 처리

test.throws(fn, msg?)                 -- fn() raises error, returns it
test.has_error(val, err, msg?)        -- val is nil, err is not nil
test.no_error(val, err, msg?)         -- err is nil

모든 어서션은 마지막 인자로 선택적 메시지를 받습니다. 실패 시 해당 메시지가 오류 출력에 포함됩니다.

생명주기 훅

test.describe("database", function()
    test.before_all(function()
        -- runs once before the suite
        db = connect()
    end)

    test.after_all(function()
        -- runs once after the suite
        db:close()
    end)

    test.before_each(function()
        -- runs before each test
        db:begin_transaction()
    end)

    test.after_each(function()
        -- runs after each test
        db:rollback()
    end)

    test.it("inserts a record", function()
        db:exec("INSERT INTO users (name) VALUES ('Alice')")
        local count = db:query_row("SELECT COUNT(*) FROM users")
        test.eq(count, 1)
    end)
end)

중첩 스위트의 훅은 순서대로 실행됩니다: 부모 before_each가 자식 before_each 전에 실행되고, 자식 after_each가 부모 after_each 전에 실행됩니다.

모킹

모킹 시스템은 전역 객체 필드를 교체하고 각 테스트 후 자동으로 복원합니다.

기본 모킹

test.describe("notifications", function()
    test.it("sends message", function()
        local sent = false
        test.mock("process.send", function(pid, topic, payload)
            sent = true
        end)

        notify_user("hello")
        test.is_true(sent)
        -- mock is auto-restored after this test
    end)
end)

모킹 API

test.mock("object.field", replacement)    -- replace a global field
test.mock_process("field", replacement)   -- shorthand for process fields
test.restore_mock("object.field")         -- restore one mock
test.restore_all_mocks()                  -- restore all mocks

모킹 경로는 점 표기법을 사용합니다: "process.send"는 _G.process.send를 교체합니다.

process.send에 대한 모킹은 원래 함수를 통해 테스트 프레임워크 메시지를 자동으로 프록시하므로, process.send가 모킹된 상태에서도 테스트 이벤트 리포팅이 계속 동작합니다.

모든 모킹은 after_each 훅을 통해 각 테스트 후 자동으로 복원됩니다.

테스트 실행하기

전체 테스트 실행

wippy test

패턴으로 필터링

wippy test math
wippy test user validation

필터는 엔트리 ID의 리터럴 부분 문자열과 일치합니다. 여러 패턴을 지정하면 ID가 그중 하나와 일치하는 엔트리가 실행됩니다.

출력 예시

3 tests in 1 suites

  calculator
    + adds numbers                           0ms
    + multiplies numbers                     0ms
    - divides by zero                        1ms
      Error: expected error, got nil

  1 suite | 2 passed | 1 failed | 0 skipped | 3ms

간단한 테스트

BDD 프레임워크가 필요 없는 테스트의 경우, true를 반환하거나 오류를 발생시키는 간단한 함수를 정의합니다:

local funcs = require("funcs")

local function main()
    local result, err = funcs.call("app:my_function", "input")
    if err then
        error("call failed: " .. tostring(err))
    end
    if result ~= "expected" then
        error("expected 'expected', got: " .. tostring(result))
    end
    return true
end

return { main = main }
  - name: integration
    kind: function.lua
    meta:
      type: test
      suite: integration
    source: file://integration_test.lua
    method: main
    modules:
      - funcs

러너는 테스트가 BDD 케이스 이벤트를 사용하는지 단순 값을 반환하는지 감지합니다. 두 패턴 모두 wippy test에서 동작합니다.

프로젝트 구조

일반적인 테스트 레이아웃:

src/
  _index.yaml
  app.lua
  test/
    _index.yaml          # test entries
    math_test.lua
    user_test.lua
    integration_test.lua

테스트 _index.yaml은 테스트 네임스페이스와 엔트리를 정의합니다:

version: "1.0"
namespace: app.test

entries:
  - name: math
    kind: function.lua
    meta:
      type: test
      suite: math
    source: file://math_test.lua
    method: main
    imports:
      test: wippy.test:test

  - name: user
    kind: function.lua
    meta:
      type: test
      suite: user
    source: file://user_test.lua
    method: main
    imports:
      test: wippy.test:test

터미널 호스트

wippy/test는 CLI 러너가 사용하는 자동 시작 wippy.terminal:host를 제공하는 wippy/terminal에 의존합니다. 애플리케이션은 wippy test 실행만을 위해 별도의 프로세스나 터미널 호스트를 선언할 필요가 없습니다.

참고 항목