들어가며
오늘은 Kotlin 테스트 라이브러리인 MockK에 BDD(Behavior-Driven Development) 스타일 API를 추가하는 오픈소스 기여 과정을 공유하려고 합니다. 이 과정에서 오픈소스 프로젝트에 기여하는 방법과 함께 Kotlin 멀티플랫폼 프로젝트 구조에 대해서도 살펴볼 수 있었습니다.
배경: MockK와 BDD
MockK는 Kotlin을 위한 강력한 모킹(mocking) 라이브러리로, JUnit과 함께 사용하여 단위 테스트를 작성할 때 많이 사용됩니다. 하지만 기존 MockK의 API는 BDD 스타일과는 약간 다른 네이밍 컨벤션을 사용하고 있었습니다.
// 기존 MockK 스타일
every { service.getValue() } returns "test"
verify { service.getValue() }
// BDD 스타일 (구현 목표)
given { service.getValue() } returns "test"
then { service.getValue() }
이 차이로 인해 이미 Issue #439와 PR #662에서 BDD 스타일 API 추가에 대한 논의가 있었습니다. 하지만 완전히 통합되지 않았고, 새로운 모듈로 분리하자는 제안이 있었습니다.
구현 방법 계획하기
먼저 기존 PR 토론 내용을 검토하여 다음과 같은 요구사항을 확인했습니다
- 기존 API와 혼동을 피하기 위해 별도의 모듈로 구현
- 다음과 같은 BDD 스타일 함수 제공
given(=every)coGiven(=coEvery)then(=verify)coThen(=coVerify)
- Android 지원을 위한 별도 모듈 제공
이 요구사항을 바탕으로 다음과 같은 구현 계획을 세웠습니다
mockk-bdd모듈 생성mockk-bdd-android모듈 생성- 각 모듈에 필요한 BDD 스타일 함수 구현
- 테스트 코드 작성
구현 과정
1. 프로젝트 구조 분석
먼저 MockK 프로젝트의 구조를 분석하여 새로운 모듈을 추가하는 방법을 이해했습니다. 다른 모듈들(mockk-dsl, mockk-android 등)의 구조를 참고하여 비슷한 방식으로 구현하기로 했습니다.
2. 모듈 추가
settings.gradle.kts 파일을 수정하여 새 모듈을 추가했습니다
include(
// 기존 모듈들...
":modules:mockk-bdd",
// ...
)
if (androidSdkDetected == true) {
include(
// 기존 Android 모듈들...
":modules:mockk-bdd-android",
)
}
3. BDD 스타일 API 구현
mockk-bdd 모듈에 BDD 스타일 함수를 구현했습니다
package io.mockk.bdd
import io.mockk.*
/**
* Starts a block of stubbing in BDD style. Part of DSL.
*/
fun <T> given(stubBlock: MockKMatcherScope.() -> T): MockKStubScope<T, T> =
every(stubBlock)
/**
* Verifies calls happened in the past in BDD style. Part of DSL
*/
fun then(
ordering: Ordering = Ordering.UNORDERED,
inverse: Boolean = false,
atLeast: Int = 1,
atMost: Int = Int.MAX_VALUE,
exactly: Int = -1,
timeout: Long = 0,
verifyBlock: MockKVerificationScope.() -> Unit
) = verify(ordering, inverse, atLeast, atMost, exactly, timeout, verifyBlock)
// coGiven, coThen 함수도 비슷하게 구현
여기서 기존 MockK API를 직접 호출하는 방식으로 구현하여 코드 중복을 최소화했습니다.
4. 테스트 작성
새로 추가한 BDD 스타일 API가 기존 MockK 함수들과 동일하게 작동하는지 확인하기 위한 테스트를 작성했습니다:
@Test
fun `given should work like every`() {
// Stub using original MockK API
every { service.getValue() } returns "MockK"
// Reset and stub using BDD API
clearAllMocks()
given { service.getValue() } returns "BDD Style"
// Verify both APIs work the same
assertEquals("BDD Style", service.getValue())
}
5. 문서화
각 함수에 자세한 KDoc 주석을 추가하여 사용 방법과 예시를 제공했습니다
/**
* Starts a block of stubbing in BDD style. Part of DSL.
*
* Used to define what behaviour is going to be mocked.
*
* @sample
* ```
* val navigator = mockk<Navigator>()
* given { navigator.currentLocation } returns "Home"
*
* println(navigator.currentLocation) // prints "Home"
* ```
* @see [coGiven] Coroutine version.
* @see [io.mockk.every] MockK original function.
*/
기술적 도전과 해결 방법
1. 의존성 관리
새로운 모듈이 필요한 의존성만 포함하도록 설정했습니다. 과도한 의존성은 불필요한 크기 증가와 버전 충돌 문제를 일으킬 수 있기 때문입니다.
3. API 일관성 유지
BDD 스타일 함수들이 기존 MockK 함수들과 동일한 파라미터와 동작을 제공하도록 했습니다. 이를 통해 사용자가 쉽게 전환할 수 있도록 했습니다.
오픈소스 기여 과정
1. 이슈 분석
먼저 이슈 #439와 PR #662를 자세히 읽고 요구사항과 제안된 해결책을 이해했습니다.
2. 구현 및 테스트
요구사항에 맞게 코드를 구현하고 테스트를 작성했습니다. MockK의 기존 테스트 스타일을 따라 동일한 품질의 테스트를 제공했습니다.
3. PR 준비
CONTRIBUTING.md 가이드라인을 준수하여 PR 설명을 작성했습니다. 구현 내용, 설계 선택 이유, 테스트 방법 등을 상세히 설명했습니다.
결론
이번 오픈소스 기여를 통해 다음과 같은 점을 배울 수 있었습니다
- 오픈소스 프로젝트 이해하기: 큰 오픈소스 프로젝트의 구조와 코드 스타일을 이해하는 방법
- 효과적인 PR 작성: 프로젝트 메인테이너들이 이해하기 쉽고 리뷰하기 쉬운 PR을 작성하는 방법
이제 MockK 사용자들은 다음과 같이 BDD 스타일로 테스트를 작성할 수 있게 되었습니다
// build.gradle.kts
testImplementation("io.mockk:mockk-bdd:x.y.z")
// 테스트 코드
class UserServiceTest {
@Test
fun `should return user profile when requested`() {
// Given
val service = mockk<UserService>()
given { service.getUserProfile(1) } returns UserProfile("John", "Doe")
// When
val profile = service.getUserProfile(1)
// Then
assertEquals("John", profile.firstName)
then { service.getUserProfile(1) }
}
}
오픈소스 기여는 단순히 코드를 제공하는 것뿐만 아니라, 프로젝트의 방향성과 기존 사용자들의 요구를 이해하고 조화시키는 과정입니다. 이 경험이 다른 오픈소스 기여자들에게도 도움이 되길 바랍니다.
PR
https://github.com/mockk/mockk/pull/1399
[Feature] Implement BDD-style aliases as separate module (mockk-bdd) by Minseok-2001 · Pull Request #1399 · mockk/mockk
Implement BDD style aliases as separate modules This PR implements BDD (Behavior-Driven Development) style API for MockK as proposed in issue #439. The implementation provides aliases for existing ...
github.com