테스트는 현대 소프트웨어 개발에서 필수적인 부분이다. Swift Package Manager에 테스트를 긴밀하게 통합하면 안정적이고 신뢰할 수 있는 패키징 생태계를 구축하는 데 큰 도움이 된다.
기존 패키지 디렉토리 구조를 확장해 테스트 모듈을 수용할 것을 제안한다. 패키지 루트 디렉토리 아래에 “Tests”라는 이름의 하위 디렉토리나 기존 모듈 디렉토리 아래에 “Tests”라는 이름의 하위 디렉토리가 있다면, 이를 테스트 모듈로 간주한다. 예를 들면 다음과 같다:
Package
├── Sources
│ └── Foo
│ └──Foo.swift
└── Tests
└── Foo
└── Test.swift
또는:
Package
└── Sources
├── Foo.swift
└── Tests
└── Test.swift
단일 모듈 프로젝트의 경우:
Package
├── Sources
│ └── Foo.swift
└── Tests
└── TestFoo.swift
TestFoo.swift
파일명은 임의로 지정할 수 있다.
위 예제에서 Foo
모듈에 대한 테스트 케이스는 관련 하위 디렉토리의 소스 코드를 기반으로 생성된다.
Tests 디렉토리의 각 하위 디렉토리마다 테스트 모듈이 생성된다. 따라서:
Package
├── Sources
│ └── Foo
│ └──Foo.swift
└── Tests
└── Foo
└── Test.swift
└── Bar
└── Test.swift
이 경우 두 개의 테스트 모듈이 생성된다. 이 예제에서 각 모듈은 Foo
모듈의 다른 측면을 테스트할 수 있으며, 이는 패키지 작성자의 결정에 달려 있다.
추가로, 모듈을 빌드할 때 해당 모듈의 테스트도 함께 빌드할 것을 제안한다. 이로 인해 빌드 시간이 약간 증가할 수 있지만, 테스트의 중요성을 고려할 때 이는 타당하다고 본다(빌드가 느린 테스트는 코드의 문제점을 나타낼 수도 있다). 더 나아가 모듈을 빌드할 때마다 테스트를 실행하는 것도 고려했지만, 디버그 주기에 방해가 될 수 있음을 이해한다.
예외적으로, 패키지를 릴리스 모드로 빌드할 때는 테스트를 빌드하지 않는다. 릴리스 빌드에서는 테스트 기능을 활성화하지 않아야 하기 때문이다. 하지만 릴리스 모드에서의 테스트 필요성을 고려해 이는 향후 개선 방향이 될 것이다.
이 기능은 논란의 여지가 있다. 많은 사람들이 테스트를 항상 빌드하면 디버그 주기가 느려질 것을 우려한다. 이러한 우려를 이해하지만, 코드 변경으로 인해 테스트가 깨졌는지 빠르게 파악하는 것이 중요하다고 생각한다. 그러나 테스트를 빌드하지 않도록 하는 커맨드라인 옵션을 제공할 것이며, 이 결정이 잘못되었다는 것이 증명되면 향후 변경할 수도 있다.
모든 테스트를 실행하기 위해 다음과 같은 문법을 제안한다. 단, 의존성 패키지의 테스트는 포함되지 않는다.
$ swift test
특정 테스트 케이스를 실행하기 위해 커맨드라인에서 테스트 이름을 지정할 수 있다.
swift test TestModule.FooTestCase
또는 특정 테스트를 실행할 수도 있다.
swift test TestModule.FooTestCase.test1
향후에는 특정 종류의 테스트를 실행하는 기능도 지원할 계획이다.
swift test --kind=performance
swift test
는 다른 인자를 기본 테스트 프레임워크로 전달하며, 해당 프레임워크가 이를 해석한다.
때로는 테스트 소스가 컴파일되지 않을 수 있고, 이를 수정하는 것이 가장 시급한 우선순위가 아닐 수도 있다. 따라서 테스트 빌드를 건너뛸 수 있는 추가 플래그를 제공한다.
swift build --without-tests
특정 테스트만 빌드하도록 지정할 수 있는 기능도 필요하다. 이 기능은 향후 작업에서 swift build
가 특정 타겟을 독립적으로 빌드할 수 있도록 하는 기능과 함께 제공될 예정이다.
사용자는 모든 의존성의 테스트를 실행할 수 있어야 한다. 이는 기본 동작은 아니지만, 플래그를 통해 제공될 것이다.
터미널에서 테스트를 실행하면 사용자가 읽을 수 있는 출력이 생성된다. 이 출력은 다른 테스트 도구와 유사하게 색상 표시와 기타 포맷팅을 포함해 다양한 테스트의 성공과 실패를 나타낸다. 예를 들어:
$ swift test --output module
PackageX 테스트 실행 중 (x/100)
.........x.....x...................
완료
소요 시간: 0.2초
98 성공
2 실패
1 경고
실패: Tests/TestsA.swift:24 testFoo()
XCTAssertTrue에서 true를 기대했지만 false를 받음
실패: Tests/TestsB.swift:10 testBar()
XCTAssertEqual
경고: Tests/TestsC.swift:1
"Some Warning"
테스트 커맨드에 추가 옵션을 전달해 JUnit 스타일의 XML이나 다른 포맷으로 출력할 수 있다. 이 출력은 지속적 통합(CI) 및 기타 시스템과 통합할 수 있다.
swift test
를 실행하면 먼저 빌드가 트리거된다. 테스트를 실행하기 전에 반드시 빌드해야 하며, 거의 모든 도구가 테스트 실행 전에 빌드를 수행하기 때문에 이는 가장 기대되는 결과라고 본다. 그러나 빌드를 건너뛰기 위한 플래그도 제공할 예정이다.
테스트 전용 의존성을 지정할 수 있는 메커니즘은 이미 존재한다. 현재는 매우 기본적인 수준이지만, 더 발전된 Package.swift
기능을 위해 새로운 제안이 필요하다.
이 제안은 또한 유틸리티 코드에 대한 요구사항을 다루지 않는다. 예를 들어, 테스트를 위해 사용되지만 외부 패키지로 제공되기를 원하지 않는 모듈이 있다. 이런 유형의 모듈은 향후 제안의 일부로 추가하고자 한다.
이 제안은 테스트 모듈이 자신의 패키지 내 모듈에 의존하는 것을 허용하지 않는다. 따라서 Package.swift
파일에서 테스트를 지정하거나 설정할 수 있는 메커니즘을 제공하지 않는다.
이 기능은 향후 Package.swift
파일의 발전을 위한 더 광범위한 제안의 일부로 추가될 예정이다.
테스트는 중요하며, 테스트를 진행하는 데 걸림돌이 최소화되어야 한다. 따라서 테스트 대상의 이름을 분석해 해당 테스트와 가장 관련이 높은 의존성을 자동으로 결정하고 적절히 처리한다. 예를 들어, “Foo”에 대한 테스트는 Foo
라이브러리 타겟의 컴파일에 의존하게 된다. 추가적인 의존성이나 자동으로 결정할 수 없는 의존성은 패키지 매니페스트에서 별도로 지정해야 한다.
일반적으로 디버그 구성으로 빌드된 테스트는 디버그 구성으로 빌드된 모듈을 대상으로 실행된다. 하지만 때로는 테스트를 위해 빌드 구성을 별도로 지정해야 할 필요가 있다. 또한 성능 테스트를 실행하기 위해 릴리스 구성으로 빌드할 때처럼, 모든 빌드에 대해 이 정보를 명시적으로 지정해야 하는 경우도 있다. 이러한 사용 사례를 지원할 계획이지만, 이 기능의 초기 구현에서는 포함되지 않을 것이다.
Swift는 “테스트 가능성(testability)“을 통해 모듈을 빌드할 수 있다. 이 기능은 테스트에서 internal
접근 제어 수준의 엔티티에 접근할 수 있게 한다. 사용자가 테스트를 위해 이 요구 사항을 일일이 지정하는 것은 번거롭기 때문에, 우리는 디버그 빌드에서 기본적으로 테스트 가능성을 활성화할 계획이다.
테스트를 위해 빌드된 모듈이 이 사실을 소스 코드에서 식별할 수 있도록 하는 것이 바람직하다. 따라서 추후에 이를 위한 정의(define)를 제공할 예정이다.
초기 단계에서, Swift 패키지 매니저는 기본 테스트 프레임워크로 XCTest
를 사용한다. 하지만 테스트는 점점 발전하는 분야이기 때문에, 패키지 매니저가 XCTest 외의 다른 프레임워크도 지원할 수 있는 방식을 고려하고 있다. 이러한 구현은 패키지 매니저가 정의한 Swift 프로토콜 형태로 제공될 것이며, 다른 테스트 프레임워크가 이를 채택할 수 있도록 설계할 예정이다.
현재 패키지 매니저의 최신 릴리스에서는 “Tests”라는 이름의 디렉터리를 대상 결정에서 제외한다. FooTests
와 같은 이름의 디렉터리는 제외하지 않지만, 현재 이러한 디렉터리는 컴파일 실패를 유발한다. 따라서 이번 변경 사항은 기존 코드에 긍정적인 영향을 미칠 것이다.
다음과 같은 레이아웃을 지원하는 방안을 검토했다:
Package
└── Sources
│ └── Foo.swift
└── FooTests
└── Test.swift
이 방안을 검토한 이유는 기존 Xcode 프로젝트 중 이와 같은 구조로 구성된 사례가 많기 때문이다. 하지만 이러한 레이아웃의 규칙이 현재 사용 중인 간단한 규칙 집합과 일치하지 않는다는 결론을 내렸다. 사용자가 우리의 규칙 방식을 사용할 때 예상치 못한 결과를 경험하면 문제를 진단하기 어렵고 혼란스러울 수 있다. 따라서 향후 Package.swift
에서 타겟을 쉽게 구성할 수 있는 새로운 제안을 제출하는 방향으로 결정했다.
테스트를 SwiftPM에서 완전히 분리하는 방안도 고려했다.
하지만 테스트를 빌드하고 테스트의 의존성을 관리해야 하기 때문에 완전한 분리는 불가능하다. 테스트를 위한 별도의 라이브러리와 실행 파일을 사용해 결합을 최소화하는 것이 현실적인 선이다.
XCTest 지원을 기본으로 포함하지 않고 테스트를 위한 프로토콜만 제공하는 방안도 검토했다.
테스트 기능을 빠르게 확보하는 것이 중요하다. XCTest를 사용하면 이를 신속하게 달성할 수 있다. 또한 Swift 시스템에서 제공하는 테스트 프레임워크에 의존할 수 있는 패키지의 가치도 있다.
하지만 XCTest 지원을 나중에 프로토콜 시스템을 통해 구현하는 것도 가능하다.