Mockito에 대한 생각

서노·2022년 11월 27일
0

프로그램을 개발 할 때 만든 코드에 대한 검증이 필요할 때가 있습니다. 프로그래머가 사람은 누구나 실수를 할 수 있고 아무리 완벽하게 코드를 짰다고 생각 할지라도 예상치 못한 사용으로 로직이 틀릴 수도 있기에 이를 검증할 수 있는 도구가 필요로 했습니다. 그래서 테스트 코드라는 코드를 검증하기 위한 코드를 개발하는 방법론(TDD)이 제시되었고, 자바에서는 Junit이나 AssertJ와 같은 라이브러리를 통해 쉽게 코드를 검증할 수 있는 환경 또한 제공하고 있습니다.

하지만 코드의 검증 범위에 따라 어디까지 테스트를 하고 어떠한 가능을 제공하는 라이브러리를 적절하게 쓰고 어떻게 가독성있는 테스트 코드를 짤 지 고민을 해야 합니다. 그래서 Mockito를 사용하기에 앞서 먼저 프로그래밍 테스트에 대한 고찰이 필요하면서 Mockito를 어떻게 하면 잘 사용하여 클린한 코드와 검증 가능한 테스트에 대한 고민을 해야 됩니다. 이런 고민까지 하게 되면 설계부터 테스트 코드까지 적절한 Mockito를 사용하여 코드의 결함을 줄이고 가독성있게 하여 코드를 좀 더 클린하게 만들 수 있다고 생각합니다.

(1) Integration Test와 Unit Test

보통 정의되는 통합 테스트는 독립적으로 개발된 소프트웨어 단위가 서로 연결될 때 올바르게 작동하는지 확인하는 것을 의미하고, 단위 테스트는 개별적인 소프트웨어 단위를 테스트하는 것을 의미합니다. 하지만 단위 테스트에서 이는 사람마다 다르게 정의 될 수 있습니다. 단위를 클래스의 메서드 단위로 보는 사람도 있을 뿐더러, 클래스 단위 혹은 행위 자체를 단위로 보는 의견 또한 있습니다.

단위 테스트는 순수하게 로직만 검증하는 코드로 만들 수 있습니다. Junit을 통해서 데이터를 넣어 값이 제대로 확인만 하면 되니까요. 하지만 단위테스트를 작성하려면, 비즈니스 로직을 파악하여 단위가 무엇인가, 내가 정말 테스트하고자 하는게 무엇인가 알아야 하고 어디까지 테스트를 해야 할 지 염두해야 합니다.

(2) Solitary or Sociable

그렇다면 단위테스트의 단위는 어디까지 측정할 수 있을까요? Wikipedia에 따르면 단위는 "전체 모듈", "개별 함수", "전체 인터페이스/클래스"일 수도 있고 "개별 메서드"일 수도 있습니다. 또한 어떤 사람의 경우 프로덕션 코드가 변경되지 않는 한 결과가 일관된다고도 말을 합니다. 또 어떤 사람은 행위 자체를 "unit"을 정의하기도 하죠.

마틴 파울러의 단위테스트 정의에 따르면 아래와 같습니다.

  1. 단위 테스트가 소프트웨어 시스템의 작은 부분에 초점을 맞춘 저수준이라는 개념
  2. 단위 테스트는 거의 항상 단위 테스트 프레임워크를 사용하여 작성
  3. 단위 테스트는 다른 종류의 테스트보다 훨씬 빠를 것으로 예상된다는 점

하지만 복잡한 대규모 어플리케이션을 만든다고 하면, 단위 테스트는 정의하기 어렵습니다.

예를들어 수학의 작은 연산 단위인 더하기, 빼기, 나누기, 곱하기를 단위 테스트로 만들 수 있지요. 
하지만 이 작은 단위를 가지고... 원의 넓이를 정의할 수 있고.. 더 나아가...

따라서 아래와 같이 마틴파울러는 단위 테스트를 고립되거나 혹은 연결된 테스트를 의미한다고 말을 합니다.

서로 연결된 서비스들이 있다고 가정하면, 테스트할 서비스 로직 이외의 모든 것이 올바르게 작동한다고 가정하고 테스트를 작성합니다. 테스트할 메서드는 제외한 연결되어있는 메서드는 올바르게 작동한다고 가정하고 테스트 코드를 작성합니다.

그래서 Unit Test에서 각각의 Test Suite은 무엇을 테스트할 것인지 그 대상을 명확히 해야 합니다. 이때 하나의 테스트에서 테스트하고자 하는 주요 대상이 되는 Unit을 SUT(System Under Test)라고 부르는 데, SUT는 용어 그대로 시스템의 일부이기 때문에 다른 Unit과 상호 작용을 할 수 있습니다. 그 중 SUT는 자신이 의존하고 있는 객체가 있을 수 있는데, 이를 DOC(Depended On Component)라고 부릅니다.

따라서 단위 테스트는 내가 테스트하고자 하는 대상인 sut(System Under Test)를 고립시켜 테스트 대상이 행위할 수 있는것 자체에 집중할 수 있어야 하고 통합 테스트와 다르게 doc(Depended On Component)에 대해선 테스트를 위한 대역 배우(Test Double)가 필요합니다.

(3) Test Double

xUnit Test Patterns의 저자인 제라드 메스자로스(Gerard Meszaros)가 만든 용어로 테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어주는 객체를 말합니다.

Test Dobule은 영화 촬영 시 위험한 역할을 대신하는 스턴트 더블(= stunt man)에서 파생되었다.

몇몇 코드의 경우 복잡한 계층레이어를 띄고 있을때, 데이터베이스나 파일 시스템과 같은 외부 리소스와의 모든 협업에서 test double을 사용해야 한다고 주장합니다.

Dummy

  • Dummy객체는 전달되지만 실제로는 사용되지 않는 객체를 의미합니다.
  • 일반적으로 매개변수 목록을 채우는 데만 사용합니다.

Fakes

  • 가짜 개체에는 실제로 작동하는 구현이 있지만 일반적으로 프로덕션에 적합하지 않게 만드는 몇 가지 지름길을 사용합니다.
  • 동작은 하지만 실제 사용되는 객체처럼 정교하게 동작하지는 않는 객체를 말합니다.

stub

  • stub은 테스트 중에 만들어진 호출에 대한 미리 준비된 답변을 제공하여 일반적으로 테스트를 위해 프로그래밍된 것 이외의 항목에는 전혀 응답하지 않습니다.
  • 가짜 객체(Mock Object)에 어떤 결과를 반환하라고 정해진 답변을 만드는 것을 의미합니다.
  • dummy객체가 마치 실제로 동작하는 것 처럼 보이도록 만들어놓은 것입니다.
  • doReturn(): Mock 객체가 특정한 값을 반환해야 하는 경우
  • doNothing(): Mock 객체가 아무 것도 반환하지 않는 경우(void)
  • doThrow(): Mock 객체가 예외를 발생시키는 경우

spies

  • 호출 방법에 따라 일부 정보를 기록하는 스텁입니다.
  • 예를들어 전송된 메시지 수를 기록하는 이메일 서비스일 수 있습니다.
  • Mockito 프레임워크의 verify() 메서드의 times와 같은 역할을 합니다.

mock

  • mock는 받을 것으로 예상되는 호출의 사양을 형성하는 기대치로 사전 프로그래밍 됩니다.
  • 예상하지 못한 전화를 받으면 예외를 던질 수 있으며 확인 중에 예상했던 모든 전화를 받았는지 확인할 수 있습니다.
  • 객체의 동작을 확인하는 데 사용합니다.
  • Mockito 프레임워크의 verify() 메서드와 동일합니다.

따라서 Mockito는 Mock을 생성하고 관리하기 위한 프레임 워크입니다.

(4) Mockito를 사용할 때

그래서 Mockito를 사용 시기는 제어할 수 없는 영역을 대체하기 위해 사용합니다. 예를들어 DB에 의존적인 dao나 repository를 사용할 때 그리고 외부 API와 통신할 때 Mocking을 사용합니다. 또한 복잡한 레이어 구성을 가질 때 사용하는 데 예를 들어 spring service안에 무수히 많은 service가 존재할 수 있습니다. 이런 복잡한 레이어 계층을 가지고 있으면 Mockito를 사용하면 집중할 수 있는 service에만 테스트를 하고 그 외에는 mock을 두어 테스트 대상에서 제외할 수 있다는 이점이 있습니다.

(6) 결론

Mockito가 많은 기능을 제공하지만 무작정 도입하는 건 아니라고 생각됩니다. Mockito를 적용하기 전에, 테스트 할 대상이 무엇인지 정확하게 파악하고 충분히 고민한다면 더 좋은 테스트 코드를 작성할 수 있다고 생각합니다.

만약 Mockito를 사용하게 된다면, 이처럼 테스트하려는 대상의 내부 구현도 일부 알고 있어야 합니다. Mock 대상 객체가 많아질수록, 호출하는 메소드가 많아질수록 테스트는 길어질 것이기에 테스트의 가독성을 저하시키고 되려 테스트 대상에 집중하는 것을 방해할 수 있기에 내부 설계에 따라 적용을 해야합니다.

그리고 고립된(Solitary)서비스의 경우 POJO로 로직을 분리하여, 연결된 모듈들을 모두 POJO의 로직을 사용하면 테스트코드를 가독성있게 만들기 쉽다고 생각합니다.

물론 가독성이 떨어뜨리는 테스트 코드를 작성한다면 애초에 설계부터 잘못된 것이니 설계부터 다시 뜯어고칠 수 있지만 기존 리거시의 대한 코드도 염두해야 합니다. 그래서 Mockito를 사용한다면 적절한 상황에서 써야됨을 알 수 있습니다.

링크

profile
무엇을 할라까

0개의 댓글