본문 바로가기
✔️ Kotlin

🐛 Bug: 로컬에서 잘 실행되던 Unit Test가 parallel test 실행에서 오류가 났다 : 잠깐🤚 아까 설정했던 Mock을 어떻게 했나요...?

by iirin 2024. 3. 23.

발생 상황

  • spring boot gradle test
  • kotest, spoke 테스트

 

SpringBoot 프로젝트의 테스트를 로컬에서 실행했을 때 모든 단위 테스트와 API 테스트가 통과했으나, Github workflow 실행시 갑자기 단위 테스트 중 몇 개가 깨져버렸습니다.

로컬과 origin 코드가 동일한 상황에서 로컬에서는 계속 잘 통과하는 데 무엇이 문제일까 헤매었습니다.

근데 예외코드가 약간 이상합니다. 분명 실패한 테스트는 수정 로직인데, 예외 스택에 삭제 테스트가 함께 포함되어 있습니다.

// 예외 로그 중 시그니처가 살짝 수정되어있습니다

com.backend.domain.test.Modify > 수정 > 작성자가 수정할 경우 성공한다 FAILED
    java.lang.AssertionError at ModifySpec.kt:78
        Caused by: java.lang.AssertionError at ModifySpec.kt:78
            Caused by: java.lang.AssertionError at ModifySpec.kt:78
                Caused by: com.backend.domain.AccessException at DeleteSpec.kt:181 //뜬금없이 다른 파일 코드

 

해당 코드 라인으로 찾아가보니 `mockkObject` 를 설정했던 코드가 있었습니다.

 

발생 원인

로컬과 workflow의 가장 큰 차이점은 `gradlew test` 로 하였나 `gradlew test -parallel` 로 실행하였느냐였습니다.

github workflow는 생산성과 비용의 문제로 병렬 실행하고 있었어요.

 

관련해서 찾아보니 `mockkObject`, `mockkStatic`과 같이 static이나 object를 모킹하는 경우 스레드 세이프하지 안다는 이슈가 있더라고요. 2019년(제가 찾아본 제일 오래된 것)부터 지속적으로 제기되어 왔었던 이슈였습니다. 특히 모킹된 동작을 지워주는 `clearAllMocks`, 모킹된 요소를 지워주는 `unmockkAll` 가 스레드 세이프하지 않아 병렬 실행시 테스트가 깨지는 경우가 왕왕 있는 것 같습니다.

 

그러니까 mockk을 해제 안해줘도 다음 테스트에 영향이 가서 문제이고, 해제해도 스레드세이프하지 못하면 예상과 다른 동작을 할 수 있으므로 주의해야 합니다.

 

🙋‍♀️ 잠깐, `clearAllMocks` 와 `unmockkAll` 의 차이점 더 자세히

`clearAllMocks`
- mock객체와 관련된 객체 내부 상태를 삭제하여 빈 객체로 되돌려 둡니다. (access count, mock된 동작 등)
- `every { sth } returns sth` `coEvery { sth } returns sth` 와 같은 상태를 취소합니다.

`unmockkAll`
- 객체를 mockking 하기 이전처럼 MockK's internal collection에서 mock객체를 삭제합니다.
- `mockkStatic(sth::class)` 등을 이전으로 되돌립니다.

[관련 이슈 : Doc change: clarify difference between clear and unmock]

 

 

대처

  • 테스트 메서드 실행 이후에 `unmockkObject` 를 통해 명시적으로 mockk을 제거해주었습니다.
    override suspend fun afterEach(testCase: TestCase, result: TestResult) {
        unmockkObject(Access.Companion)
    }

 


Refs.