지금 당장 (유사) BDD 시작하기

chanju Jeon·2020년 10월 5일
1

간단하게 BDD를 적용해보고 기존과 다르게 개발했던 경험

원티드 앱 4.6.11의 새로운 기능 중에서 이벤트 '관심 키워드'를 설정하면, 알림으로 빠르게 이벤트 정보를 확인할 수 있는 기능을 구현하면서 간단하게 BDD를 적용해보고 기존과 다르게 개발했던 경험에 대해서 공유해보려고 합니다.

(유사)라는 제목을 사용한 이유는 BDD를 적용했지만, Test Case를 먼저 작성하고 구현하고 리팩토링한 건 아니고 개발을 하기 전에 Use Case를 정리하고 Use Case 단위로 구현하고 테스트를 BDD로 했기 때문입니다.

개발 순서

기존에는 프로젝트 기획서를 보고 디자인 가이드가 나오면 UI를 우선 만들고 UI의 각 요소에 기능을 구현하고 API와 연결하는 순서로 개발을 했었는데요, 이번에는 기획서 내용을 토대로 사용자 관점에서 시나리오를 Use Case로 정리하고 Use Case 별로 Jira 태스크를 생성, 개발하고 Test Case를 작성하여 확인하면서 진행했습니다.

디자인 가이드를 보고 화면을 먼저 만드는 개발 순서에는 몇 가지 문제점이 있었습니다. 한 화면에 많은 기능이 있을 때는 개발 복잡도가 올라가고 일정을 예상하기도 어려운 경우가 많았습니다. 또한 실제 제품을 위해 필요한 기능 개발은 UI 구현 뒤로 밀리기 때문에 일정에 쫓기거나 테스트 케이스를 작성하지 못하는 경우도 있었습니다.

Use Case 단위로 개발하면서는 사용자 관점에서 기능에 대한 정의와 시나리오를 그대로 구현하는 느낌이었습니다. 실제 앱에서 View는 사용자의 입력을 받아서 적절한 출력을 보여주는 역할이기 때문에 중요한 건 비즈니스 로직을 다루는 부분입니다. 역할 분리가 잘 되어 있는 경우에는 View(화면)가 없어도 Use Case에 대해 로직을 구현할 수 있었고, Test Case를 통해 구현에 대한 검증을 할 수 있었습니다.

아키텍처

원티드 iOS 앱에서는 MVC, MVVM을 거쳐서 지금은 Clean Swift 아키텍처를 개선해서 사용하고 있습니다. Clean Swift는 한 화면(Scene)에 기본적으로 3개의 역할(Display Logic, Business Logic, Presentation Logic)으로 나뉘며 각각 View controller, Interactor, Presenter에서 역할을 구현하게 됩니다. 전체적인 이벤트(및 데이터)의 흐름은 VIP 사이클로 이루어집니다.

위에서 말한 역할은 프로토콜로 정의되고 화면(View controller)이 없어도 Interactor에 비즈니스 로직을 가지기 때문에 Use Case 단위로 BDD를 진행할 수 있었습니다. Clean Swift 아키텍처와 그것을 개선한 이야기는 나중에 조금 더 자세히 다루도록 하겠습니다.

BDD

BDD를 위한 테스트 케이스 작성은 Quick, Nimble을 사용해서 Given, When, Then 형식을 따라서 작성하고 있습니다.

실제로 구현한 Interactor에 대한 Test Case 일부를 보면,

describe("뷰가 로드되었을 때") {
    var interactor: EventKeywordNoticeInteractor!
    var presenter: EventKeywordNoticePresenterMock!
    let worker = EventKeywordNoticeWorkerMock()

    beforeEach {
        interactor = EventKeywordNoticeInteractor(worker: worker)
        presenter = EventKeywordNoticePresenterMock()
        interactor.presenter = presenter
    }

    context("선택한 관심 키워드를 가져오는데 성공하면") {
        beforeEach {
            let request = EventKeywordNotice.Request.OnLoad()
            interactor.process(request)
        }

        it("선택한 관심 키워드를 전달한다") {
            expect(presenter.displayInterestKeywordsCalled).to(beTrue())
        }
    }
}

describe("키워드를 선택했을 때") {
    var interactor: EventKeywordNoticeInteractor!
    var presenter: EventKeywordNoticePresenterMock!
    let worker = EventKeywordNoticeWorkerMock()

    let selectedKeywords = [
        EventKeyword.Keyword(id: 1, kind: "EVENT", keyword: "a.b.c", title: "키워드1"),
        EventKeyword.Keyword(id: 2, kind: "EVENT", keyword: "d.e.f", title: "키워드2")
    ]

    beforeEach {
        interactor = EventKeywordNoticeInteractor(keywords: selectedKeywords, worker: worker)
        presenter = EventKeywordNoticePresenterMock()
        interactor.presenter = presenter
    }

    context("키워드 선택이 완료되면") {
        it("선택한 키워드를 전달한다") {
            let request = EventKeywordNotice.Request.SelectKeywords(
                indexPaths: [IndexPath(row: 0, section: 0), IndexPath(row: 1, section: 0)]
            )
            interactor.process(request)

            expect(presenter.displayInterestKeywordsCalled).to(beTrue())
            expect(presenter.displayInterestKeywords.count).to(equal(selectedKeywords.count))
        }
    }
}

Interactor에 연결된 presenter와 worker는 프로토콜을 구현한 Mock 객체로 대체해서 테스트 케이스에서 VIP 사이클이 정상적으로 동작하는지, API 호출이 정상적으로 호출되는지 확인할 수 있습니다.

Interactor 이외에도 Presenter, View Controller에 대해서도 Test Case를 작성하면 Use Case에 대한 기본적인 흐름은 모두 테스트를 할 수 있고, 실제로 QA를 진행하기 전에 기획서에 정리된 요구사항에 대한 테스트는 진행할 수 있었습니다.

마치며

지금까지 BDD를 적용하기 위해서 개발을 시작하기 전에 Use Case를 정리하고, Clean Swift의 각 컴포넌트를 구현하면서 Test Case까지 작성해본 프로젝트 경험이었습니다.

다음에는 (유사) BDD가 아닌 시나리오 중심의 테스트 주도로 개발을 시도해봐야겠습니다. 테스트에 대해서도 성공과 실패(예외) 케이스에 대해 더 효과적으로 분리하는 방법을 고민해보고 BDD로 작성한 테스트 케이스가 자동화되어 지속해서 검증될 수 있도록 테스트 자동화 환경을 구축할 예정입니다.

테스트의 중요성에 대해서는 알고 있었지만 실제로 시간에 쫓겨 테스트 케이스를 제대로 작성하지 못하고 실제로 실행하면서 테스트를 하는 경우가 많았는데, BDD를 통해서 요구사항에 맞는 테스트 케이스를 작성해두니 나중에 요구사항이 변경되었을 때도 기존의 구현에 사이드 이팩트를 발생하지 않고 빠르게 변경할 수 있었습니다.

profile
write is code, think ux design.

0개의 댓글