NullNull

ObjectMother 패턴 적용하기 본문

프로그래밍 언어/JAVA

ObjectMother 패턴 적용하기

KYBee 2024. 4. 4. 23:52

ObjectMother 패턴이란?

우선 해당 패턴이 무엇인지 알아보기 위해 GTP에게 질문을 해보았다.

 

Q: ObjectMother 패턴이 뭐야?

A:

"Object Mother"는 소프트웨어 개발에서 사용되는 디자인 패턴 중 하나입니다. 이 패턴은 테스트를 위해 객체를 생성하고 설정하는 방법을 정의합니다. 주로 유닛 테스트나 통합 테스트에서 많이 사용됩니다.

 

일반적으로, 테스트를 작성할 때 객체를 생성하고 초기화하는 것은 반복적이고 번거로운 작업일 수 있습니다. 특히 테스트 케이스마다 다양한 상태의 객체가 필요할 때 이런 문제가 더 커집니다. Object Mother 패턴은 이러한 상황에서 객체 생성 및 초기화를 추상화하여 객체를 쉽게 생성하고 조작할 수 있도록 도와줍니다.

 

 

객체를 조금 더 쉽게 생성하기 위해 사용한다고 보여진다.

 

실제 사례에서 이 패턴을 왜 사용하는지 보자.

 

만약 한 개의 인스턴스를 생성하여 Unit Test를 작성한다면 아래와 같을 것이다.

package soccer.team.player;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import soccer.team.player.Player;
import soccer.team.player.PlayerRepositoryImpl;
import soccer.team.player.PlayerServiceImpl;

import static org.mockito.Mockito.doReturn;

@ExtendWith(MockitoExtension.class)
class PlayerServiceImplTest {

    @InjectMocks
    private PlayerServiceImpl playerService;
    @Mock
    private PlayerRepositoryImpl playerRepository;

    @Test
    @DisplayName("선수 create 테스트")
    public void tc1() {
        Player actual = new Player("HALLAND", "1999.10.18", 9, "Norway", true, 197, 77);
        doReturn(actual).when(playerRepository).createPlayer(actual);

        Player result = playerService.createPlayer(actual);

        Assertions.assertEquals(actual, result);
    }
}

 

 

그럼 인스턴스를 한 개 더 추가하면 어떻게 코드가 바뀔까?

 

아래를 보자

package soccer.team.player;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import soccer.team.player.Player;
import soccer.team.player.PlayerRepositoryImpl;
import soccer.team.player.PlayerServiceImpl;

import static org.mockito.Mockito.doReturn;

@ExtendWith(MockitoExtension.class)
class PlayerServiceImplTest {

    @InjectMocks
    private PlayerServiceImpl playerService;
    @Mock
    private PlayerRepositoryImpl playerRepository;

    @Test
    @DisplayName("선수 2명 create 테스트")
    public void tc2() {
        Player actual1 = new Player("HALLAND", "1999.10.18", 9, "Norway", true, 197, 77);
        Player actual2 = new Player("DE BRYUNE", "1993.10.18", 17, "Belgium", true, 183, 70);

        doReturn(actual1).when(playerRepository).createPlayer(actual1);
        doReturn(actual2).when(playerRepository).createPlayer(actual2);

        Player result1 = playerService.createPlayer(actual1);
        Player result2 = playerService.createPlayer(actual2);

        Assertions.assertEquals(actual1, result1);
        Assertions.assertEquals(actual2, result2);
    }
}

 

 

이처럼 인스턴스를 만들기 위해서 추가해줘야 하는 내용이 많다.

 

이를 간단하게 할 수 있는 패턴이 MotherPattern 이다.

사실 설명이 어렵지 구현은 간단하다.

 

아래 처럼 Mother 클래스를 구현하고 테스트에서 이를 사용하면 된다. static 메서드로 등록해두자.

package soccer.team.player;

import org.jeasy.random.EasyRandom;

public class PlayerMother {

    static EasyRandom easyRandom;

    static {
        easyRandom = new EasyRandom();
    }
    
    static Player generate() {
        return Player.builder()
                .name(easyRandom.nextObject(String.class))
                .birthday(easyRandom.nextObject(String.class))
                .nationality(easyRandom.nextObject(String.class))
                .nationalTeam(easyRandom.nextBoolean())
                .number(easyRandom.nextInt())
                .weight(easyRandom.nextInt())
                .height(easyRandom.nextInt())
                .build();
    }
}

  1. 테스트 패키지에서 생성하고자 하는 객체의 Mother 클래스를 생성하고 그 안에 어트리뷰트에 랜덤한 값을 채워둔다.
  2. 테스트 클래스에서 해당 함수를 호출하여 임의의 값으로 세팅된 인스턴스를 만든다.
  3. 해당 인스턴스를 테스트에서 활용한다.

 

Random 라이브러리를 활용하여 임의의 값을 주어도 좋지만, EasyRandom을 사용하여 더 간단하게 MotherPattern을 구현했다.

아래는 실제 적용한 예시이다. 실제로 더 간단하게 인스턴스를 구현했다.

package soccer.team.player;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import soccer.team.player.Player;
import soccer.team.player.PlayerRepositoryImpl;
import soccer.team.player.PlayerServiceImpl;

import static org.mockito.Mockito.doReturn;

@ExtendWith(MockitoExtension.class)
class PlayerServiceImplTest {

    @InjectMocks
    private PlayerServiceImpl playerService;
    @Mock
    private PlayerRepositoryImpl playerRepository;

    @Test
    @DisplayName("MotherPattern 테스트")
    public void tc3() {
        Player actual1 = PlayerMother.generate();
        Player actual2 = PlayerMother.generate();

        doReturn(actual1).when(playerRepository).createPlayer(actual1);
        doReturn(actual2).when(playerRepository).createPlayer(actual2);

        Player result1 = playerService.createPlayer(actual1);
        Player result2 = playerService.createPlayer(actual2);

        Assertions.assertEquals(actual1, result1);
        Assertions.assertEquals(actual2, result2);
    }
}

 

 

 

안에 들어간 랜덤 값은 다음과 같다. 이처럼 쉽게 Unit Test를 위한 인스턴스를 생성할 수 있다. 테스트니까.. 쉽게 인스턴스 만들자..

'프로그래밍 언어 > JAVA' 카테고리의 다른 글

Mock? Spy?  (0) 2025.02.04
간단한 Unit Test 작성하기  (0) 2024.04.04
객체지향 언어의 특징과 설계 원칙  (0) 2022.09.29
예외 처리  (0) 2022.08.14
Abstract class vs Interface  (0) 2022.08.14
Comments