Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Home 테스트 코드 작성 #202

Merged
merged 7 commits into from
Dec 5, 2023
Merged

test: Home 테스트 코드 작성 #202

merged 7 commits into from
Dec 5, 2023

Conversation

loinsir
Copy link
Collaborator

@loinsir loinsir commented Dec 4, 2023

🧑‍🚀 PR 요약

  • Home의 View, Interactor, Worker, presenter 각 객체에 대한 테스트 코드를 작성했습니다.

📌 변경 사항

Note

전체적인 테스트 타겟 구조

  • CleanStore가슴 속 삼천원 프로젝트의 테스트 코드를 참고해서 작성했습니다.
  • 전체적인 테스트 형태는 CleanStore를, Mock 처리나 Dummy Data Json 가져다 쓰는 방식 같은 자잘한 테크닉은 가슴 속 삼천원 프로젝트를 참고했습니다.
  • 디렉토리는 아래와 같이 구성했고, 메인앱 타겟에 있는 MockWorker, Json 데이터를 우선은 그대로 두고 복사해서 가져와서 사용하면 될 것 같습니다.
  • 왜냐하면 현재 앱에서 Mock을 사용하여 동작하고 있는 부분이 있기 때문에 추후 테스트 코드가 작성되고, 서버와 모든 연결을 마치면 메인 앱 타겟에서 삭제하고 테스트 용도로 사용하면 될 것 같습니다.
    image

Note

테스트 코드 형태

  • 테스트 코드는 위에서 말한 것 처럼 CleanStore의 형태를 참고했는데, 제가 파악한 방식은 다음과 같습니다.
    1. VIP 단방향 구조이므로, 현재 테스트하고자 하는 sut의 뒤 객체를 Spy로 선언 ex) Interactor test이면 spy presenter를 선언
    2. sut에 spy를 주입시키고, spy에 선언한 메서드 호출 기록용 프로퍼티를 통해 적절한 메서드가 호출되는지 확인
    3. 필요하면 입출력 테스트 케이스를 더 선언하여 테스트 케이스 작성

Note

Interactor가 테스트 가능하도록 수정 필요

  • 지금 현재 interactor 메서드 대부분이 worker의 async 메서드 실행 때문에 Task 블록 하나로 감싸진 Void 리턴 타입 메서드인 경우가 대부분입니다.
  • 기존 completionHandler 방식과는 달리 Task 가 포함된 메서드는 expectation, fulfill 방식이 불가능합니다.(해당 메서드에 completion을 추가 인자로 넣어 Task가 끝나는 시점을 알 게 할 수는 있으나 이 방법은 테스트를 위해 프로덕션 코드를 바꾸는 안티패턴이기에 제외)
  • 따라서 Task 블록 자체를 Task<Bool, Never> 타입으로 리턴시키고, 해당 Task 블록의 value 값을 await하여 실행 순서를 보장하는 방식으로 해결했습니다.
    • 이 방법은 기존 코드를 크게 해치지 않으면서, 사용하는 곳에서 Task 블록의 성공, 실패 여부를 알 수 있게 하므로 더 좋은 구조라고 생각되었습니다.
  • 따라서 interactor를 testable하도록 하기 위해서는 해당 Task를 리턴하도록 변경해야 합니다.

+) 지난번에 학습 스프린트 기간때 리뷰어셨던 광현님께서도 비동기 컨텍스트를 실행하는 메서드의 경우 성공/실패 여부를 리턴하는게 좋다고 하셔서 괜찮다고 생각합니다!
image

그치만 더 좋은 방법이 있다면 알려주세요...ㅎㅎ....

Linked Issue

close #200

@loinsir loinsir self-assigned this Dec 4, 2023
@loinsir loinsir linked an issue Dec 4, 2023 that may be closed by this pull request
3 tasks
Copy link
Collaborator

@chopmozzi chopmozzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 VIP Cycle의 호출여부를 관리하는 테스트 코드만 있는데, 따로 올바른 받아오거나 비즈니스 로직을 테스트 하는 코드는 없는 건가요? 다음 VIP 싸이클 호출여부로 파악할 수 있다고 판단하셔서 이렇게 처리하셨는지 궁금합니다.
Task타입은 좋은 것 같습니다. 저도 한 번 써보겠습니다. 고생하셨습니다.

@@ -45,27 +47,33 @@ final class HomeInteractor: HomeDataStore {
// MARK: - Use Case

extension HomeInteractor: HomeBusinessLogic {
func fetchPosts(with request: Models.FetchPosts.Request) {
@discardableResult
func fetchPosts(with request: Models.FetchPosts.Request) -> Task<Bool, Never> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@loinsir
Copy link
Collaborator Author

loinsir commented Dec 5, 2023

현재 VIP Cycle의 호출여부를 관리하는 테스트 코드만 있는데, 따로 올바른 받아오거나 비즈니스 로직을 테스트 하는 코드는 없는 건가요? 다음 VIP 싸이클 호출여부로 파악할 수 있다고 판단하셔서 이렇게 처리하셨는지 궁금합니다. Task타입은 좋은 것 같습니다. 저도 한 번 써보겠습니다. 고생하셨습니다.

제가 presenter 입출력 테스트 하는 부분은 까먹고 작성을 못했네요. 작성해서 더 올리겠습니다

Comment on lines +38 to +63
final class HomePresentationLogicSpy: HomePresentationLogic { // 호출 테스트를 위한 Spy
var presentPostsCalled = false
var presentPostsReceivedResponse: Models.FetchPosts.Response!
var presentThumbnailImageCalled = false
var presentThumbnailImageReceivedResponse: Models.FetchThumbnailImageData.Response!
var presentPlaybackSceneCalled = false
var presentTagPlayListCalled = false

func presentPosts(with response: Models.FetchPosts.Response) {
presentPostsCalled = true
presentPostsReceivedResponse = response
}

func presentThumbnailImage(with response: Models.FetchThumbnailImageData.Response) {
presentThumbnailImageCalled = true
presentThumbnailImageReceivedResponse = response
}

func presentPlaybackScene(with response: Models.PlayPosts.Response) {
presentPlaybackSceneCalled = true
}

func presentTagPlayList(with response: Models.ShowTagPlayList.Response) {
presentTagPlayListCalled = true
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 이런식으로 뒷단 객체를 프로토콜 채택하는 spy를 만들어서 테스트하는 방법이군욤

Copy link
Member

@anyukyung anyukyung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하지않는 Task 타입을 리턴하는게 살짜쿵 마음에 걸리긴하는데,
말씀하신 것처럼 안티패턴까지는 아닌 것 같아서 충분히 괜찮은 방법 같아보여요 ㅇ.ㅇ
나중에 같이 더 좋은 방법 찾아봐도 괜찮을 것 같구용
수고하셨습니다 !! 나중에 테스트 코드 짤 때 참고할게요 🫰

@loinsir
Copy link
Collaborator Author

loinsir commented Dec 5, 2023

사용하지않는 Task 타입을 리턴하는게 살짜쿵 마음에 걸리긴하는데, 말씀하신 것처럼 안티패턴까지는 아닌 것 같아서 충분히 괜찮은 방법 같아보여요 ㅇ.ㅇ 나중에 같이 더 좋은 방법 찾아봐도 괜찮을 것 같구용 수고하셨습니다 !! 나중에 테스트 코드 짤 때 참고할게요 🫰

저도 해당 부분이 고민 되기는 하는데 스택 오버플로우에도 저랑 동일한 고민하시는 분이 계셨었어요.
그중 여러 답변들 중 딜레이 시간을 주어서 expectation, fulfillment 으로 완료 시점을 아는 방법도 있었습니다.
그런데 이 방법은 테스트 코드 원칙 중, 테스트 코드는 신속하게 수행되어야 한다는 원칙에 어긋나는 것 같아서 이 방법을 택하게 되었습니다.
그치만 제 인사이트가 좁아서 더 좋은 방법을 못찾은 것 같기도 합니다. 좋은 방법 찾으시면 꼭 공유 부탁드려요 ㅎㅎ...

@loinsir loinsir merged commit 605e301 into iOS/dev Dec 5, 2023
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

test: Home Scene 테스트 코드 작성
3 participants