프로그래밍/스프링

단위테스트와 Mockito

스루나루 2024. 2. 18. 16:32
728x90
728x90

 

 

 

 

서론

 

 회사에서 드디어 테스트코드를 작성하는 방침으로 프로젝트를 진행하게 되었다.

 전에는 MR하고 프론트랑 합쳐서 테스트를 화면 단에서 버튼 눌러가면서 잘 동작하나 확인하는 느낌으로 결합 테스트를 했지 백엔드 전용으로 코드를 테스트 하지 못해서 통합 테스트랑 단위 테스트랑 별 차이가 없었다. 그저 로컬 환경에서 테스트하냐 데브 환경에서 테스트하냐 라는 차이정도? 그러다 테스트 코드를 도입하게 되었는데 영 감을 못 잡아서 정리해보고자 한다.

 

 

테스트 종류 

 

단위 테스트 

 - 하나의 모듈을 기준 독립적으로 진행되는 가장 작은 단위 테스트 

 - 하나의 메서드에는 하나의 기능이 들어가듯 하나의 테스트에는 하나의 동작만 테스트하도록 작성 = 가장 작은 단위 테스트니까! 

 

통합 테스트

 - 모듈 통합과정에 모듈 간의 호환성 확인을 위한 테스트 

 - 각각 모듈들이 유기적으로 잘 연결이 되었는지 확인하는 전체적인 통합 테스트 

 - 요거 통과해야 릴리즈가 가능하다. 

 

 

단위 테스트의 목적

 

단위 테스트를 왜 해야하는가? 

 - 내가 만든 로직 부분만 테스트하기 때문에 빠르게 문제점을 파악할 수 있다. 

 - 캐시나 데이터베이스 연결없이 빠르게 테스트 가능 

 - 해당 로직을 수정할 때 기존에 테스트 코드를 작성했다면 테스트하기가 편함

 - 가장 좋은 거는 화면 단에서 테스트 하는게 아니라 코드로 테스트하니까 휴면에러도 없고 정직하고 여러번 테스트 할 수 있다.

 

단위 테스트를 위해 신경써야할 것들

 - 1개의 테스트 함수에는 하나의 개념만 테스트 하도록 하여 독립성을 지킨다

 - 테스트에서는 다수의 assert를 사용하지 않는 편이 좋다.

 - 어디서든 동일하고 빠른 테스트를 추구하자 

 - 다른 테스트 코드에 영향이 없도록 설계하자.

 

문제점 

 - 보통 서비스 로직을 보면 혼자 작동하는 게 아니라 다른 객체에서 데이터 받아서 동작하는 경우가 많음 .. 근데 단위 테스트는 @Test 애노테이션 붙은 놈을 단위로 테스트를 진행하고 해당 객체가 빈 컨테이너에 존재하지 않다 보니까 제대로 테스트를 진행할 수가 없음 ....

 

 

 

Stub

 

 ※  Stub 

  - 빈 컨테이너에 객체를 넣는 것이 아니라 해당 테스트가 동작할 때 동안 가짜 객체 ( Mock )을 만들어서 주입해주고 원하는 결과가 나오도록 설정하는 것을 뜻함 

 

  Stub 적용 예시 

   1) 외부 리소스나 서비스 의존하는 경우 : 디비나 api호출 , 파일 시스템 등 외부 리소스에 접근하는 코드를 테스트할 때 

   2) 복잡한 객체 또는 동작을 대체하는 경우 : 테스트하려는 코드가 복잡한 객체를 사용하거나 특정 동작 필요로 할 때 

 

public class MyServiceTest {

    @Mock
    private MyDependency myDependency;  // 테스트 대상 코드에서 의존하는 객체

    @Test
    public void testMyService() {
        // Mockito를 사용하여 myDependency 객체의 동작을 stub으로 설정합니다. 
        // 원하는 기댓값을 설정해서 사용한다.
        Mockito.when(myDependency.getData()).thenReturn("Test Data");

        // 테스트 대상인 MyService 객체를 생성하고 myDependency를 주입합니다.
        MyService myService = new MyService(myDependency);

        // MyService의 특정 메서드를 호출하고 반환값을 확인합니다.
        String result = myService.processData();
        assertEquals("Processed: Test Data", result);
    }
}

  

 


 

Mockito

 

 - 아까 적었던 stub을 만들어주는 테스트 프레임워크 : 가짜 객체를 만들어줌 

 - 복잡한 연관성을 가진 코드를 테스트할 때 독립적인 테스트를 진행하기 위해 가짜 객체를 만들어서 테스트를 진행할 수 있음 

 

 

애노테이션 종류 

 

1. @Mock 

 - 해당 테스트 코드와 상호 작용하는 가짜 객체를 만듦

 - 가짜 객체라서 무조건 Stub해서 사용해야함 Stub를 객체를 세팅하지 않으면 null이 나옴 

 

public class MyDependency {
    public int getValue() {
        return 1;
    }
}

public class MyService {

    private MyDependency myDependency;

    public MyService(MyDependency myDependency) {
        this.myDependency = myDependency;
    }

    public int calculate(int value) {
        int dependencyValue = myDependency.getValue();
        return value * dependencyValue;
    }
}

 

해당 객체에 대한 테스트코드는 아래와 같다

보면 stub안 한 경우는 어짜피 값이 설정 안되어 있어서 결과물로 데이터면 0, 참조형이면 null이 나온다

가짜 객체라 수행할 수가 없음 무조건 stub을 해서 원하는 세팅을 해야 테스트가 가능하다 

 

public class MyServiceTest {

    @Mock
    private MyDependency myDependency;

    private MyService myService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        myService = new MyService(myDependency);
    }

    @Test
    public void testWithoutStubbing() {
        // 테스트 실행 (stub 없이)
        int result = myService.calculate(5);

        // 결과 검증
        Assert.assertEquals(0, result);
    }

    @Test
    public void testWithStubbing() {
        // stub 설정
        Mockito.when(myDependency.getValue()).thenReturn(10);

        // 테스트 실행 (stub 후에)
        int result = myService.calculate(5);

        // 결과 검증
        Assert.assertEquals(50, result);
    }
}

 

 

2. @Spy

 - 일부 메서드 호출 혹은 일부 상태를 유지하면서 테스트 수행

 - 진짜 객체를 생성해서 쓰는 거다 보니까 stub을 세팅 안 해도 동작이 가능하다. stub을 쓰면 원하는 결과값이 나오도록 세팅할 수가 있고 안 쓰면 원래 객체의 메서드나 필드로 테스트를 진행할 수 있음 

public class MyServiceTest {

    @Spy
    private MyDependency myDependency;

    private MyService myService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        myService = new MyService(myDependency);
    }

    @Test
    public void testWithStubbedValue() {
        // getValue() 메서드의 반환값을 2로 스텁(stub) 설정
        Mockito.doReturn(2).when(myDependency).getValue();

        // 테스트 실행
        int result = myService.calculate(5);

        // 기대값: 5 * 2 = 10
        Assert.assertEquals(10, result);
    }

    @Test
    public void testWithoutStubbedValue() {
        // getValue() 메서드의 반환값을 스텁하지 않음

        // 테스트 실행
        int result = myService.calculate(5);

        // 기대값: 5 * 1 = 5
        Assert.assertEquals(5, result);
    }
}

 

3. InjectMocks

 - 테스트 대상 객체의 가짜 객체를 생성 

 - @Mock이나 @Spy 애노테이션이 붙은 객체를 자신의 멤버 클래스와 일치하면 주입시킴 

 

public class MyServiceTest {

    @Mock
    private MyDependency myDependency;

    @InjectMocks
    private MyService myService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testWithStubbedValue() {
        // getValue() 메서드의 반환값을 2로 스텁(stub) 설정
        Mockito.when(myDependency.getValue()).thenReturn(2);

        // 테스트 실행
        int result = myService.calculate(5);

        // 기대값: 5 * 2 = 10
        Assert.assertEquals(10, result);
    }

    @Test
    public void testWithoutStubbedValue() {
        // getValue() 메서드의 반환값을 스텁하지 않음

        // 테스트 실행
        int result = myService.calculate(5);

        // 기대값: 5 * 1 = 5
        Assert.assertEquals(5, result);
    }
}

 

@RunWith(MockitoJunitRunner.class)

mockito에서 제공하는 기능을 사용하기 위해서 테스트 클래스에 달아주는 애노테이션 
없는 경우 테스트 코드에 아래의 코드를 삽입해야한다.

@Before public void setUp() {
    MockitoAnnotations.initMocks(this);
    myService = new MyService(myDependency);
}

 

 

 

참고

 

https://cornswrold.tistory.com/369

 

Mockito 어노테이션(@Mock, @InjectMocks)

Mockito 관련 어노테이션 @RunWith(MockitoJunitRunner.class) Mockito에서 제공하는 목객체를 사용하기 하기위해 위와같은 어노테이션을 테스트클래스에 달아준다. @RunWith(MockitoJunitRunner.class) public class Test(){

cornswrold.tistory.com

 

https://lemontia.tistory.com/951

 

[junit5] Mock을 이용한 단위 테스트 (@InjectMocks 과 @Mock 차이)

Mockito를 이용하면 좀더 작은 단위까지 테스트가 가능하다. 무엇보다 데이터를 컨트롤해야하는 상황에서 DB연결없이 임의로 주고받을 수 있기 때문에 유용하다. 이번에는 그 테스트에 관한 내용

lemontia.tistory.com

 

https://mangkyu.tistory.com/145

 

[Spring] JUnit과 Mockito 기반의 Spring 단위 테스트 코드 작성법 (3/3)

이번에는 Spring 기반의 웹 애플리케이션에서 테스트를 작성하는 방법에 대해 알아보도록 하겠습니다. 1. Mockito 소개 및 사용법 [ Mockito란? ] Mockito는 개발자가 동작을 직접 제어할 수 있는 가짜 객

mangkyu.tistory.com

 

 

728x90
728x90