의존 관계를 가진 객체를 테스트 하는 방법

최지환·2022년 2월 27일
1

의존 관계를 가진 객체의 단위 테스트

어떠한 객체 A가 B라는 객체에 의존적인 경우, 우리는 테스트 코드를 어떻게 작성해야할까?

보통 서로 의존적이지 않은 경우, A를 테스트 할 때, A에 대한 테스트코드를 작성하고, B를 테스트 할 때, B에 대한 테스트만 작성하면 된다.
하지만 그렇지 않은 경우가 있다.

한 객체가 다른 객체에 의존하는 경우가 그렇다.

A객체가 B객체에 의존적일 때, A를 테스트 하기 위해서는 B와 A 객체를 둘을 한번에 테스트 해야만 할것이다. 그것은 결국 A 와 B를 각각 테스트 해야하는, TDD에 위배가 된다 생각 할 수 있다. 그렇다면 어떻게 해야 할까?

→ 이때 나온 개념이 바로 단위 테스트임시 객체이다.

5-1. 단위 테스트와 임시 객체

기존에는 A 객체가 B에 의존적일 때, 테스트를 하기 위해서는 A와 B 전체를 테스트 해야 했다. 하지만 '단위' 즉, A객체만 테스트를 하면된다. 그렇다면 B는 어떻게 할까? B는 임시객체로 사용 하면 된다.

요약하자면 A객체가 B에 의존적 일 때, A를 테스트 하기 위해선 B를 임시객체로 만들고, A를 테스트하면 된다.

이해가 안된다면, 코드를 쳐보자.

//A.java

public class A {
    private int age;

    public A(int age) {
        this.age = age;
    }

    public boolean isOlder(int age){
        return this.age<age;
    }

   
}

A.java 설명

isOlder 메소드를 이용해, 객체가 갖고 있는
age와 비교하여 값이 크면 true을 반환하고 그렇지 않으면 false 반환.

//B.java
public class B {
    private A a;

    private int age = 17;

    public B(A a) {
        this.a = a;
    }

    public int isOlderThanA() {

        if (a.isOlder(age)) {
            return 1;
        }
        return 0;

    }

  
}

위의 두 자바 코드의 의존성은 단 방향적이다.
B는 A 클래스 타입의 필드를 갖고 있기 때문에 , B에서는 A에 대해 알 수 있는, '연관 관계'적이다.

→ 의존성이 있다는 것을 알 수 있다.

이제 테스트 코드를 살펴보자.

//ATest

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.assertj.core.api.Assertions.assertThat;

class ATest {
    private A a;

    @BeforeEach
    public void setUp() {
        a = new A(13);

    }

    @ParameterizedTest
    @DisplayName("isOlder을 실행했을 때, 객체의 age 보다 큰 나이가 들어가면 true 반환, 그렇지 않으면 false 반환 ")
    @CsvSource(value = {"15,true", "1,false", "10,false"})
    void isOrder(int age, boolean expect) {
        assertThat(a.isOlder(age)).isEqualTo(expect);
    }

}

위 테스트 코드에서

isOrder 테스트 코드는 value에 해당하는 값들이 제대로 전잘되어 값 비교가 되는지 확인 하는 코드이다. 코드 설명은 @DisplayName를 확인해 보면된다.

또한 @ParameterizedTest 와 @CsvSource에 대해서는 나중에 따로 다루겠다.

그냥 쉽게 생각해서 나이를 넣었을 때, 13(@SetUp에서 생성자에 13을 넣어줬기 때문에, age = 13) 을 기준으로 잘 작동하는지 테스트 하는 코드이다.

핵심 부분 - 단위 테스트와 임시 객체 -

//BTest

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.*;

class BTest {
    private A a;
    private B b;

    @BeforeEach
    void setUp() {
        a = mock(A.class);
        b = new B(a);

    }

    @Test
    @DisplayName("isOlderThanA a의 age를 비교하여 값이 true면 1반환")
    void isOlderThanA_1() {

        when(a.isOlder(anyInt())).thenReturn(true);
        assertThat(b.isOlderThanA()).isEqualTo(1);
    }

    @Test
    @DisplayName("isOlderThanA a의 age를 비교하여 값이 false면 0반환")
    void isOlderThanA_0() {
        when(a.isOlder(anyInt())).thenReturn(false);
        assertThat(b.isOlderThanA()).isEqualTo(0);
    }

    
    @ParameterizedTest
    @DisplayName("isOlderThanA를 실행했을 때, 객체의 age 보다 큰 나이가 들어가면 1 반환, 그렇지 않으면 0 반환")
    @CsvSource(value = {"true,1", "false,0"})
    void isOlderThanA_1_and_0(boolean result, int expect) {

        when(a.isOlder(anyInt())).thenReturn(result);
        assertThat(b.isOlderThanA()).isEqualTo(expect);

    }

    @Test
    @DisplayName("a.check 메서드가 3번 실행 되어야 한다.")
    void check() {
        b.check();
        verify(a, times(3)).check();

    }
}

위의 테스트 코드가 사실상 우리가 다뤄야하는 핵심 이다.


임시객체 mock

코드가 많이 나와 살짝 방황 했을 수 있지만, 다시 처음으로 돌아와서 우리가 알아봐야할 것은, 의존 관계가 있는 경우 그 객체를 어떻게 테스트 하느냐 이다.

지금 B 는 A를 의존 하고 있는 상태이다. 이런 경우 단위테스트를 어떻게 하는지 살펴보자. 우선 가독성을 높히기 위해 위 코드의 일부분을 가져와 보겠다.

import static org.mockito.Mockito.*;

...

class BTest {
   
...

    @BeforeEach
    void setUp() {
        a = mock(A.class);
        b = new B(a);

    }

    @Test
    @DisplayName("isOlderThanA a의 age를 비교하여 값이 true면 1반환")
    void isOlderThanA_1() {

        when(a.isOlder(anyInt())).thenReturn(true);
        assertThat(b.isOlderThanA()).isEqualTo(1);
    }

...

}

위 코드에서 주의 깊게 봐야할 것은 setUp 부분 이다

지금 setUP() 을 보면 ' mock ' 을 사용한 것을 알 수 있다. B객체를 생성하기 위해서는 A 객체가 필요하다. 하지만 제대로 된 단위 테스트를 하기위해, 여기서는 A객체를 '임시' 로 생성해줬다.

a = mock(A.class) // a에 A클래스의 임시객체 생성

mock을 사용하기 위해서는 Mockito라는 라이브러리를 임포트 해줘야 한다.


이제 위에서 생략했던 @ParameterizedTest 와 @CsvSource 에 대해 알아보자

@ParameterizedTest

하나의 테스트 메소드로 여러 개의 파라미터에 대해서 테스트할 수 있다.

쉽게 말해 여러번 @Test를 짜는 것을 한번에 할 수 있음.

// A.java
public class A {
    private int age;

    public A(int age) {
        this.age = age;
    }

    public boolean isOlder(int age){
        return this.age<age;
    }
}
//ATest.java

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.assertj.core.api.Assertions.assertThat;

class ATest {
    private A a;

    @BeforeEach
    public void setUp() {
        a = new A(13);

    }

   @Test
    @DisplayName("isOlder을 실행했을 때, 객체의 age 보다 크면 true 반환")
    void isOlder_23() {
      assertThat(a.isOlder(23)).isTrue();
    }

    @Test
    @DisplayName("isOlder을 실행했을 때, 객체의 age 보다 작으면 false 반환")
    void isOlder_10() {
        assertThat(a.isOlder(10)).isFalse();
    }

    @Test
    @DisplayName("isOlder을 실행했을 때, 객체의 age 보다 작으면 false 반환")
    void isOlder_3() {
        assertThat(a.isOlder(3)).isFalse();
    }

}

위 코드를 아래와 같이 줄 일 수 있다.

    @ParameterizedTest
    @DisplayName("isOlder을 실행했을 때, 객체의 age 보다 큰 나이가 들어가면 true 반환, 그렇지 않으면 false 반환 ")
    @CsvSource(value = {"23,true", "10,false", "3,false"})
    void isOrder(int age, boolean expect) {
        assertThat(a.isOlder(age)).isEqualTo(expect);
    }

이제 CsvSoure에 대해 알아보자.


@CsvSoure

//BTest코드에서 발췌

@CsvSource(value = {"23,true", "10,false", "3,false"})
void isOrder(int age, boolean expect) {
        assertThat(a.isOlder(age)).isEqualTo(expect);
    }

CsvSource의 value 속성으로 다음과 같이 파라미터를 던질 수 있다.

즉 "23,true", "10,false", "3,false"라는 값들을 순서대로 age, expect로 isOrder 함수의 파라 미터로 보낼 수가 있다.


한번의 여러 테스트를 하는 @ParameterizedTest와 한번의 여러 value를 파라미터로 보낼수 있는
@CsvSoure는 같이 자주 쓰이니 잘 알아두자 ^_^

0개의 댓글

관련 채용 정보