https://dagger.dev/hilt/testing-philosophy


10.2 Design Decisions – Testing Philosophy

개요

이 페이지는 Hilt를 사용하는 테스트 사례를 설명하는 것을 목표로 한다. Hilt의 많은 API와 기능 ( 그리고 특정 기능의 부족함)은 좋은 테스트를 만드는 것에 대한 무언의 철학을 바탕으로 만들어졌다. 좋은 테스트의 개념은 보편적으로 합의 된 것은 아니기 때문에 이 문서는 Hilt 팀의 테스트 철학을 명확히 하는 것을 목표로 한다.

테스트해야 하는 것

Hilt는 외부 사용자의 관점에서 가능한 많은 테스트하는 것을 장려한다. 외부 사용자 관점에서라는 것은 많은 것을 의미 한다. 그것은 앱 또는 서비스의 실제 사용자가 될 수도 있고, API 또는 클래스를 사용하는 더 많은 범위의 사용자가 될 수도 있다.

핵심적인 부분은 테스트가 세부적인 구현에 대한 내용까지 작성하지 않는다는 점이다. 내부 메서드가 호출되었는지 확인하는 것 처럼 세부적인 구현에 대한 것은 불안정한 테스트를 유발한다. 리팩토링시 내부 메서드의 이름이 변경된다면 좋은 테스트의 경우에는 갱신이 필요 없을 것이다. 기존 테스트가 깨지는 유일한 변경사항은 사용자가 볼 수있는 동작을 변경하는 것이다.

실제 의존성 사용하기

Hilt의 테스트 철학은 모든 클래스가 자체 테스트를 가져야 하는 엄격한 규칙을 규정하지는 않는다. 실제로 이러한 규칙은 일반적으로 사용자의 관점에서 위의 테스트 원칙을 위반한다. 테스트는 작성 및 실행이 편리하도록 필요한 만큼만 작아야 한다(예. 빠르거나 또는 리소스 집약이지 않게 작아야 한다.). 다른 모든것이 동일하다면 테스트는 이 순서대로 진행되는 것이 좋다.

  • 의존성에 대한 실제코드를 사용하라
  • 라이브러리에 의해 제공된 표준 Fake를 사용하라
  • Mock은 최후의 수단으로 사용하라

그렇지만 트레이드 오프도 존재한다. 테스트에서 실제 의존성/실제 의존성 주입을 사용하는 것은 하나 또는 다음과 같은 이유들로 인해 제한적으로 어려울 수 있다:

  • 실제 의존성 주입을 설정하고 인스턴스화하는 것은 너무 많은 보일러플레이트 코드 또는 반복되는 코드를 만든다.
  • 실제 의존성을 사용하는 것은 퍼포먼스 문제에 직면하게 된다.(백엔드 서버를 시작하는 것을 필요로 하는 것 처럼)

Hilt는 첫번째와 같은 문제를 해결하기 위해 설계되었다. 성능은 문제일 수 있지만, 성능은 대부분의 의존성에 대해 문제가 되지 않는다. 이는 중대한 I/O를 가진 의존성을 사용할 때만 문제가 될 수 있다. 그래서 성능을 크게 떨어뜨리지 않으면서 더 많은 실제 의존성을 사용하여 테스트를 보다 편리하고 강력하게 작성할 수 있는 경우, 실제 의존성을 사용하여 작성해야 한다. 테스트에서 큰 부정적인 영향을 미치는 클래스의 경우 Hilt는 바인딩을 교체하는 수단을 제공한다.

더 많은 실제 의존성을 사용하면 다음과 같은 중요한 이점이 있다:

  • 실제 의존성은 실제 문제를 잡아낼 가능성이 더 높다. 실제 의존성은 Mocking 하는 것과 달리 항상 최신 상태를 유지 한다.
  • 사용자 관점에서 위의 테스트 원칙과 부합하여 동일한 적용 범위에 대해 더 적은 테스트를 작성하게 된다.
  • 테스트가 깨진다는 것은 잘못 구성된 Fake 또는 Mock 대신 실제 문제를 나타낸다.
  • 더 많은 실제 의존성을 사용하면 사용자의 관점에서 테스트 하는 위의 원칙과 종종 관련이 있다.

그래도 실제 의존성을 사용할 수 없으면 일반적으로 라이브러리에서 제공하는 표준 Fake가 다음으로 좋은 선택지다. 라이브러리 제작자가 유지보수하는 프로덕션 코드와 동기화 될 가능성이 높기 때문에 표준 Fake는 Mock보다 낫다. 이러한 이유로 Mock은 일반적으로 최후의 수단으로 선택된다.

Hilt, 의존성 주입 그리고 테스트

테스트에 대한 기초가 설명되었으므로 이제 Hilt, 의존성 주입 및 테스트의 특성에 대해 알아보자. 실제 객체를 사용한다는 철학에 따라 Hilt는 테스트에 의존성 주입 / Dagger를 사용한다. 이는 프로덕션 코드에서 객체가 생성되기 때문에 더욱 현실적이다. 즉, 테스트는 프로덕션 코드보다 깨지기 쉽지 않으며 실제 객체를 보다 쉽게 사용할 수 있다. 실제로 @Inject 생성자를 가진 타입의 경우, 실제로 이 조언을 따르고 실제 코드를 사용하는 것이 Mock를 구성하고 바인딩하는 것보다 쉽고 적은 코드를 갖는다.

불행히도, Hilt가 없는 이런 종류의 테스트는 전통적으로 보일러플레이트 코드와 테스트에서 Dagger를 설정하기 위한 추가 작업으로 인해 실제로 어려웠다. 그러나 Hilt는 보일러플레이트 코드를 생성하고 Fake 또는 Mock이 필요할 때 테스트를 위한 다양한 바인딩 구성을 설정하는 명확한 스토리를 가지고 있다. Hilt와 함께 이 문제는 더 이상 Dagger로 테스트를 작성하는 데 방해가 되지 않으므로 실제 의존성을 쉽게 사용할 수 있다.

Dagger를 사용하지 않아 생기는 단점

유닛 테스트에서 Dagger를 사용하지 않는 것은 실제로 매우 일반적이다. 이런 점은 상당히 단점으로 작용하지만, 테스트에서 Hilt 없이 Dagger를 사용하는 것이 더 어렵다는 점을 감안하면 이해할 만한 방법이다.

예를 들어 테스트하려는 Foo 클래스가 있다고 가정하자:

class Foo @Inject constructor(bar: Bar) {
}

이 경우 Dagger를 사용하지 않으면 테스트는 생성자를 호출하여 Foo를 직접 인스턴스화한다. 언뜻 보기에 이것은 매우 간단해 보이지만, Foo의 생성자에 Bar 인스턴스를 제공해야만 한다.

직접 인스턴스화 하는 대신 Mock으로 테스트를 하자.

앞에서 설명한 테스트 철학에 따르면, 실제 Bar 클래스를 사용하는 것이 좋다. 하지만, 어떻게 해야할까? 이는 사실 테스트 하기 위해 실제 Foo 클래스를 얻는 것을 반복하는 것이다: Foo를 직접 인스턴스화 해야 하며 Bar가 자체의 의존성을 가지고 있다면, 그것 또한 비슷하게 인스턴스화 해야하는 작업이 필요하다. 너무 복잡하지 않게 하기 위해 Fake 또는 Mock을 사용해야 한다. 꼭 테스트 속도나 성능에 때문은 아니지만, 유지보수 문제를 야기하는 불안정한 보일러 플레이트 코드의 사용을 피하기 위해서다. 이것이 Fake 또는 Mock을 사용하는 타당한 이유는 아니지만 어쨌든 그렇게 하는 것이 좋다.

위에서 언급한 대안은 일반적인 Fake를 사용하는 것이다. 이는 의존관계를 끊고 직접적인 인스턴스화의 유지 관리의 부담을 감소시킨다. 그러나 항상 모든게 그렇게 간단하지는 않다. 대부분의 경우에 Fake는 기존 클래스와 비슷한 의존성을 필요로 하게 된다. 예를 들면 실제 Bar가 Clock을 필요로 한다면, FakeBar도 결국은 FakeClock을 필요로 한다. FakeClock이 서로 다른 클래스간에 흔히 조정을 해주기 때문이다 (Foo에 Clock을 사용하는 다른 의존성 Baz가 있다고 가정하면, FakeBaz는 동일하게 FakeClock 인스턴스를 사용하여 시간이 지날 때 기능이 잘 동작하도록 할 것이다). 이런식으로 의존성을 관리하다보면 급속도로 감당할 수 없게 된다.

이런 부분 때문에 일반적으로 Mock으로 테스트를 작성해야 한다. Mock으로 테스트를 하는 것은 이러한 의존성 체이닝 문제를 해결해 준다. 하지만 자동으로 쉽게 최신 정보를 얻지 못하고, 실제 버그를 찾는 전체 목표에서 테스트를 쓸모 없게 만들 수 있다는 중대한 단점을 갖게 될 수도 있다. 테스트 작성자 외에 Mock 테스트의 동작을 확인하는 사람이 없기 때문에 일반적으로 충분한 시간이 지나면 테스트에서 더 이상 유용한 시나리오를 테스트하지 않을 가능성이 높다.

직접 인스턴스화 하는 것은 테스트에서 세세한 구현까지 해야 한다.

직접적으로 인스턴스화를 하면 생성자 호출이 의존성의 세부 정보를 표현하기 때문에 테스트에서 구현 세부 정보까지 작성하지 않는다는 철학을 깨뜨리게 된다. Bar를 @Inject 생성자의 매개변수로 갖는 경우 Foo를 사용 하는 입장에서는 Bar 클래스의 존재에 대해 알아야 할 이유가 없다. Foo의 리팩토링 로직이 다른 라이브러리의 private 클래스에 세부 구현사항이 될 수 있기 때문이다.

이를 실례로 보기 위해서는, Foo가 Foo(Bar,Baz) 생성자와 같이 Bar와 Baz의존성을 필요로 하는 경우를 생각해보자. Dagger에서는 @Inject 생성자 매개변수의 순서를 바꿔도 무관하다. 그러나 직접 인스턴스화하여 Foo를 테스트 해야 한다면, 테스트 또한 변경이 필요하다. 마찬가지로, 새로운 @Inject 클래스의 사용을 추가하거나 또는 옵셔널 바인딩의 사용은 최종 사용자에게는 보이지 않지만, 테스트를 지속적으로 갱신해야만 한다.

요약

Hilt는 실제 의존성과 함께 쉬운 테스트를 진행하기 위해 Dagger의 단점을 보완하여 설계되었다. Hilt를 사용하여 작성한 테스트는 이러한 원리 원칙을 잘 따른다면 전반적으로 더 나은 테스트 경험을 보여줄 것이다.

카테고리: Dagger2

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.