-
[Tuist] 모듈 생성 및 테스트 환경 구축개발/Swift 2025. 1. 14. 22:07
Tuist를 활용하여 프로젝트를 모듈화하고, 각 모듈 내에 테스트 환경을 구축하는 구체적인 방법을 알아보겠습니다. 이는 개발 효율성을 극대화하고, 앱의 유지보수성을 향상시키는 데 큰 도움이 될 것입니다.
프로젝트를 모듈화하는 것은 코드 재사용성을 높이고, 특정 기능에 대한 의존성을 명확히 하며, 빌드 시간을 단축하는 데 필수적입니다. Tuist는 이러한 모듈화를 매우 효율적으로 지원합니다.tuist edit 명령어를 실행한 후, 메인 앱 타겟의 의존성(dependencies) 설정을 살펴보면 다음과 같이 다양한 외부 라이브러리들이 설정되어 있는 것을 확인할 수 있습니다.dependencies: [TargetDependency] { return [ .external(name: "AdFitSDK"), .external(name: "AMPopTip"), .external(name: "ChannelIOSDK"), .external(name: "CocoaLumberjack"), .external(name: "CocoaLumberjackSwift"), .... ]
여기에 우리가 새로 만들 모듈 프로젝트를 추가할 수 있습니다.예를 들어, MyProfile이라는 기능 모듈과 Entity라는 데이터 모듈을 추가한다면 다음과 같이 설정할 수 있습니다..project(target: "MyProfile", path: .relativeToRoot("Projects/MyProfile")), .project(target: "Entity", path: .relativeToRoot("Projects/Entity")),
이 설정은 메인 앱이 MyProfile과 Entity 모듈에 의존한다는 것을 의미합니다.모듈별 Project.swift 파일 생성모듈을 추가했다면, Project 폴더 내부에 위와 같이 각 모듈에 해당하는 폴더를 만들고 그 안에 Project.swift 파일을 생성해야 합니다. 이 파일은 해당 모듈의 빌드 방식을 정의합니다. 설정은 비교적 간단합니다.Entity 모듈의 Project.swift 파일 예시:Entity 모듈은 특정 외부 의존성 없이 순수한 데이터 구조나 공통 정의를 포함할 때 유용합니다.//Entity Project 파일 import TemplatePlugin import ProjectDescription import TargetPlugin let project = Project( name: "Entity", settings: .librarySettings, targets: [ .target( name: "Entity", destinations: .iOS, product: .staticFramework, bundleId: "com.Entity.Entity", sources: [ .glob(.path("Sources/**")) ], dependencies: [], settings: .settings( base: [ "DEFINES_MODULE": "NO", ] ) ) ] )
MyProfile 모듈의 Project.swift 파일 예시:MyProfile 모듈은 Entity 모듈과 같은 내부 모듈, 그리고 ComposableArchitecture, RxSwift와 같은 외부 라이브러리에 의존할 수 있습니다.Tuist의 Registry 기능을 활용하면 이처럼 복잡한 외부 패키지 의존성도 빠르고 효율적으로 관리할 수 있어 패키지 해석 시간을 분 단위에서 초 단위로 단축시킬 수 있습니다.//MyProfile Project 파일 let project = Project( name: "MyProfile", settings: .librarySettings, targets: [ .target( name: "MyProfile", destinations: .iOS, product: .staticFramework, bundleId: "com.MyProfile.MyProfile", sources: [ .glob(.path("Sources/**")) ], dependencies: [ .project(target: "Entity", path: .relativeToRoot("Projects/Entity")), .external(name: "ComposableArchitecture"), .external(name: "RxSwift"), ], settings: .settings( base: [ "DEFINES_MODULE": "NO", ] ) ) ] )
중요한 설정 및 고려사항:•- product 타입: 위 예시에서는 staticFramework로 지정했습니다. framework로 지정했을 때 런타임에 EXC_BAD_ACCESS 에러가 발생했던 경험이 있습니다. 이 원인에 대해서는 추가적인 조사가 필요할 수 있습니다.
- Sources 폴더 및 경로: 각 모듈 내에 Sources 폴더를 만들고, sources 설정에서 해당 경로를 정확히 지정하는 것이 중요합니다. 이 경로에 모듈의 실제 소스 코드가 위치하게 됩니다.
- dependencies: 해당 모듈이 의존하는 다른 모듈(내부 프로젝트 모듈)이나 외부 라이브러리를 여기에 추가합니다.
- settings: 이 부분은 Xcode 프로젝트의 Configuration이나 Info 탭에 들어가는 내용과 유사합니다. librarySettings와 같이 미리 정의된 설정을 활용하여 프로젝트 설정을 간소화할 수 있습니다. 예를 들어, librarySettings는 다음과 같이 정의될 수 있습니다.
static let librarySettings: Settings = .settings( base: [ "CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER": "NO", "DEVELOPMENT_TEAM": "39MSJ7FY3F", "EXCLUDED_ARCHS[sdk=iphonesimulator*]": ["x86_64"], "IPHONEOS_DEPLOYMENT_TARGET": "15.0", "ONLY_ACTIVE_ARCH": "YES", "TARGETED_DEVICE_FAMILY": "1", ], configurations: [ .debug(name: "AppStore Debug"), .release(name: "AppStore Release"), .debug(name: "Development Debug"), .release(name: "Development Release"), ] )
이 모든 설정을 마친 후 tuist generate 명령어를 실행하면, 새로 정의한 모듈들이 Xcode 프로젝트 내에 정확히 반영되는 것을 확인할 수 있습니다.Tuist의 Cache 기능 덕분에 한번 컴파일된 바이너리는 캐시되어 다음 빌드 시 컴파일 과정을 건너뛸 수 있어 전체 컴파일 시간을 크게 단축할 수 있습니다.모듈에서 테스트 환경 만들기
모듈화된 프로젝트에서는 각 모듈의 기능이 독립적으로 잘 작동하는지 확인하기 위해 유닛 테스트 환경을 구축하는 것이 중요합니다.개발 중에 테스트 코드를 작성하려는데 유닛 테스트를 지정할 타겟이 없는 경우, tuist edit를 통해 해당 모듈의 Project.swift 파일을 수정하여 테스트 타겟을 추가할 수 있습니다.예를 들어, MyProfile 모듈에 대한 테스트 타겟을 추가하려면 Project.swift 파일 내의 targets 배열에 다음과 같은 unitTests 타입의 타겟을 추가합니다.let project = Project( name: "MyProfile", settings: .librarySettings, targets: [ .target( name: "MyProfile", destinations: .iOS, product: .staticFramework, bundleId: "com.MyProfile.MyProfile", sources: [ .glob(.path("Sources/**")) ], dependencies: [ .project(target: "Entity", path: .relativeToRoot("Projects/Entity")), .external(name: "ComposableArchitecture"), .external(name: "RxSwift"), ], settings: .settings( base: [ "DEFINES_MODULE": "NO", ] ) ), // MyProfileTests 타겟 추가 .target( name: "MyProfileTests", destinations: .iOS, product: .unitTests, // 유닛 테스트 타입으로 지정 bundleId: "com.MyProfile.MyProfileTests", sources: [ .glob(.path("Tests/**")) // 테스트 코드 소스 경로 ], dependencies: [ .target(name: "MyProfile") // 테스트할 대상 모듈 지정 ], settings: .settings( base: [ "DEFINES_MODULE": "NO", ] ) ) ] )
이 코드에서 target을 하나 더 추가하고 product를 .unitTests 타입으로 설정합니다.또한, sources를 통해 테스트 코드의 경로(일반적으로 Tests/**)를 지정하고, dependencies에는 테스트하고자 하는 대상 모듈(MyProfile 모듈)을 추가합니다.이 설정을 마친 후 프로젝트 내부에 Tests 폴더를 생성하고 다시 tuist generate 명령어를 실행하면,새로운 테스트 타겟이 성공적으로 생성된 것을 확인할 수 있습니다.이제 Tests 폴더 안에 테스트 코드를 작성하고, 해당 코드의 타겟 멤버십을 새로 생성한 테스트 타겟으로 설정하면 됩니다.같은 모듈 내에서도 타겟이 다르면 기본적으로 internal 접근 수준의 함수는 접근할 수 없습니다. 따라서 테스트하려는 함수는 public으로 지정되어야 테스트 코드에서 사용 가능합니다.
SwiftUI + TCA: 실전 프로젝트로 완성하는 차세대 iOS 아키텍처 강의 | 덤벨로퍼 - 인프런
덤벨로퍼 | 복잡한 SwiftUI 상태 관리, TCA (The Composable Architecture)로 깔끔하고 견고한 앱을 만드세요. 실전 프로젝트 예제로 핵심만 빠르게 배웁니다. , SwiftUI + TCA: 실전 프로젝트로 완성하는 차세대
www.inflearn.com
'개발 > Swift' 카테고리의 다른 글
[SwiftUI + TCA] 에러 Perceptible state was accessed but is not being tracked (0) 2025.02.07 TCA에서 AsyncStream 사용해서 타이머 구현하기 (0) 2025.01.15 [Swift] XIB 만 가지고 이벤트 핸들링 하는법, xib cell 내부에 또 cell 이있는경우 (0) 2024.12.21 [Swift] ReactorKit 사용시 고민할 부분 (비즈니스 로직, Pulse ) (0) 2024.12.21 [Error] linker command failed with exit code 1 (0) 2024.11.24