[Spring] 테스트코드

Sun choi·2024년 9월 14일

NEW 지식

목록 보기
23/34

단위 테스트와 통합 테스트

  • 단위 테스트 : 가장 작은 단위의 테스트 방식. 메서드 단위로 테스트를 수행하고, 의도한 결과값이 나오는지 확인하는 수준 (ex: controller, service, repository… 격리해서 따로따로)
  • 통합 테스트 : 어플리케이션이 정상적으로 동작하는지 확인. 여러모듈을 함께 테스트 (ex: controller 메소드를 호출하면 controller → service→ repository 모두 수행)

F.I.R.S.T 원칙

좋은 테스트를 위한 F.I.R.S.T 원칙

  • Fast : 단위 테스트는 빨라야 한다. (가장 중요)
  • Independent : 단위 테스트는 각각의 테스트가 연관되지 않고, 고립되어야 한다. 즉 독립적
    이어야 한다.
  • Repeatable : 단위테스트는 반복 가능해야한다. 반복적으로 시행해도 결과는 같아야 한다.
  • Self-validating : 자체적으로 테스트 결과가 도출되야한다. (개발자가 print 찍어서 비교하는게 아니고 자동으로 수행되며 성공/실패 의 결과가 나오는 것)
  • Timely : 단위 테스트는 실제 코드보다 먼저 구현해야한다. (TDD일 경우에만 해당)

Given-When-Then 패턴

테스트코드를 작성할 때 10의 9은 사용하는 패턴이다.
given : 시스템의 설정
when : 시스템이 수행할 작업
then : 결과를 검증

JUnit + Mockito

  • JUnit : Java 에서 사용되는 대표적인 테스트 프레임워크. 자바 개발자 93% 가 사용한다는 통계가 있습니다. Assertion을 이용하여 예상하는 값과 실제 도출된 값을 검증할 수 있는 기능을 지원한다.

  • Mockito : Mock 객체를 쉽게 만들고, 관리하고, 검증할 수 있는 방법을 제공하는 프레임워크 입니다. Mock 객체는 진짜 객체와 비슷하게 동작하지만 프로그래머가 직접 행동을 관리하는 객체.

Mockito에서 Mock(가짜) 객체의 의존성 주입을 위한 어노테이션

  • @Mock: Mock 객체를 만들어 반환해주는 어노테이션

  • @InjectMocks: @Mock 또는 @Spy로 생성된 가짜 객체를 자동으로 주입시켜주는 어노테이션, Mock을 가져와서 사용하는 레파지토리. 이건 그럼 실체가 있음 Mock객체가 아님

  • @MockBean: 기존에 사용되던 Bean의 껍데기만 가져오고 내부의 구현 부분은 모두 사용자에게 위임 (Spring 에 자동으로 Bean 으로 주입됨)
    given에서 선언한 코드 외에는 전부 사용할 수 없다

  • @Spy: Stub하지 않은 메소드들은 원본 메소드 그대로 사용하는 어노테이션

  • @SpyBean: given에서 선언한 코드 외에는 전부 실제 객체의 것을 사용합니다.

예를 들어 UserController에 대한 단위 테스트를 작성하고자 할 때, UserService를 사용하고 있다면 @Mock 어노테이션을 통해 가짜 UserService를 만들고, @InjectMocks를 통해 UserController에 이를 주입시킬 수 있는 것.

Mock기반으로 작성된 테스트 코드는 인터넷이 안되는 비행기 안에서도 동작 및 검증 가능 (제약사항이 없기 때문에 매우 빠르게 동작)

어노테이션 비교

Mock - 가짜 깡통. 의존성 주입으로 독립성 유지할때 사용. 동장을 전부다 직접 지정해줘야한다.
Spy - 진짜 객체처럼 쓸수도 있고 원할때 동작을 지정해서 쓸 수도 있다.
InjectMock - 우리가 아는 진짜 객체

TDD (Test Driven Development)

  • 개발 할 때 테스트 코드를 먼저 작성하고 해당 테스트를 성공 시키게 만들면서 리팩토링 하는 개발 기법

좋은 테스트코드!

좋은 테스트코드란 미래의 나를 위한 것이다.

  • 순간순간 테스트가 아니라 결과 상태가 같은 테스트를 작성해야 한다

  • 간결한 코드! 테스트에서 가장 중요한 건 가독성!

  • 유지보수하기 쉬운 단위 테스트가 생산성을 높여주는 테스트이다.
    버그를 조기에 발견할 수 있고, 리팩토링을 안전하게 할 수 있다. 배포 전 검증을 빠르게 마무리 할 수 있다.

  • 깨지기 쉬운 테스트를 예방하기

    • 과정이 아닌 결과(행위)에 신경쓴다
    • 상호작용이 아니라 상태를 테스트하기
    • 테스트하고 싶은 것만 테스트하기, 많은 로직 넣지 않기
  • 테스트 이름은 검사하는 행위에 어울리게 짓기

    • 테스트의 이름은 주로 테스트 보고서에 담겨 사람에게 전달되기 때문에 이름이 상세해도 괜찮다.
  • 테스트에 논리를 넣지 말자

    • 테스트 코드에서는 스마트한 로직보다 직설적인 코드를!
  • 클래스 내부에서만 사용하는 private 메서드에 관한 건 테스트할 필요 없다.
    public 메서드의 if문은 에러 메세지 잘 나오나 테스트하면 좋음. 하지만 변수명이나 메서드에 연연하면 안됨

➕ Given과 When은 둘 다 동작은 동일한데 스타일이 다름.
given은 BDD(행위 주도 개발) 스타일에 더 맞게 나온 거라서, BDD의 포맷이 given-when-then이다 보니 거기에 맞게 given을 사용한다.

공유 데이터 셋팅

가독성을 올리기 위한 전략이다.
모든 테스트에서 기능을 확인 하기 위해서 셋팅해줘야 하는 파라미터 값, 변수를 매번 호출 할 필요가 없다. 또한 이후 엔티티나 어떤 값이 변경 되더라도 쉽게 대응 할 수 있다.

공유데이터를 사용했더니 길었던 given이 간결하게 표현됐다.


내가 제일 헷갈렸던 것!!

given(managerRepository.existsManagerByUser_IdAndTodo_id(authUser.getId(),todo.getId())).willReturn(false);

❓ 이 테스트 코드에서 파라미터에 아무 값이나 넣어줘도 false가 나오게 하는건데
이렇게 하면 왜 테스트를 하는거지? 왜 given으로 객체들을 생성해 주는거지? 아무거나 넣어도 false가 되게 만들었는데?!

➡️ given() 메서드는 메서드 호출 시 실제 데이터베이스를 호출하지 않고, 미리 정의된 값(true 또는 false)을 반환하도록 설정한다.
이 테스트코드는 값이 맞지 않을 경우에 예외처리가 되는지 확인하는 것이다.
그래서 우리가 궁금한건 맞지 않을 경우에 예외처리가 되는지!!! 이다.
어떤 값을 넣든 그건 안궁금하고, 맞지 않을 때 예외처리가 잘 되는지!!!! 이다.
그래서 안에 넣어주는 값은 신경쓰지 않고 false일 때 예외처리가 잘 되는지 신경쓰는 것에 집중하는 것이다.


❓ 오케이 그러면 객체를 왜 넣어줘야돼 그냥 바로
given(managerRepository.existsManagerByUser_IdAndTodo_id(anyLong(),anyLong())).willReturn(false);
만 주고 테스트하면 되는거 아니야?!

➡️

given(todoRepository.findById(anyLong())).willReturn(Optional.of(todo));

commentService.saveComment(authUser, todo.getId(), request));

이 두 코드에서 객체를 사용하기 위한 것도 있고, 테스트 환경에서 현실적인 상황을 시뮬레이션하기 위해 생성되고 설정된다. 테스트에서 중요한 상호작용을 담당하며, Todo가 어떤 할일인지, User가 어떤 사용자인지 정의하는 것!
현실적인 시나리오를 만들고, 그 시나리오에서 올바른 예외가 발생하는지를 검증하기 위해 필요하다!

profile
풀스택 개발자의 공부기록 📖

0개의 댓글