[TDD] Test code 접근 방식, Mock, Mockito, BDD

2021. 4. 16. 19:13 테스트 코드/JUnit

Test Code

 

 

Test Method 명명 방식

 

1. Test 대상 method와 이름을 1:1로 매치

- getName()-> test_getName()

 

2. 1:1로 매치 후 메소드명에 예상 동작 표현

- getName() -> test_getName_isSuccess()

 

3. 테스트 시나리오에 맞게 정하기 

 

 

접근 방식

 

1. 시나리오식 접근 방법

1)Happy Day 시나리오

- 정상적인 흐름일 때, 동작해야 하는 결과값 선정

2)Blue Day 시나리오

- 예외나 에러 상황에 대한 결과값 선정

 

2. 삼각측량법

- 곱하기 메소드의 경우 (a*b)와 같은지, a를 b번 더한 것과 같은지 확인하는 방법

 

3. Edge Case

- 경계 조건의 값들로 테스트하는 방법

 

 

Mock

 

 

Mock 객체

 

- 모듈의 겉모양이 실제 모듈과 비슷하도록 만든 가짜 객체

- ‘의존성’ -> 테스트 작성의 어려움 -> 의존성을 단절시키기 위해 Mock 객체 사용

 

1. 사용 이유

1) 테스트 작성을 위한 환경 구축의 어려움

- 환경 구축을 위한 작업 시간이 많이 필요한 경우

- 특정 모듈을 갖고 있지 않아 테스트 환경 구축이 어려운 경우 

- 타 부서의 협의나 정책이 필요한 경우

2) 테스트가 특정 경우나 순간에 의존적

- 특정 상태를 가상으로 만들어 놓을 수 있음

3) 테스트 시간이 오래 걸릴 때

 

Test Double

 

- 원래의 객체를 사용해서 테스트 진행이 어려운 경우, 이를 대신할 수 있게 만들어 주는 객체

 

1. Dummy Object

- 인스턴스화 될 수 있을 수준으로만 인터페이스를 구현한 객체

- 해당 객체의 기능이 필요없을 때 사용

 

2. Test Stub

- Dummy object가 실제로 동작하는 것처럼 보이게 만든 객체

- 특정 상태를 가정해서 만듬

 

3. Fake Object

- 여러 개의 인스턴스를 대표할 수 있는 객체

- 좀 더 복잡한 구현이 들어가있는 객체

- DB에서 가져온 정보처럼 data를 가지고 있음

 

4. Test Spy

- 특정 메소드 정상 호출 여부 확인

- 보통 Mock Framework 이용

- EasyMock, jMock, Mockito에 기본적으로 포함되어 있음

 

5. Mock Object

- 행위를 검증하기 위해 사용되는 객체(행위 기반 테스트)

- 보통 test double과 mock object를 같은 의미로 사용함

 

Mock Framework

 

- Mock Object를 직접 생성할 필요없게 도와주는 framework

- Mock 객체에 대한 행위도 테스트 케이스에 포함시킬 수 있음

 

1. Mockito

- Mock Framework 중 하나

1) 장점

- 테스트의 행위와 반응에만 집중해서 테스트 메소드 작성할 수 있게 해줌

- 테스트 스텁을 만드는 것을 쉽게 하고 만드는 것과 검증을 분리

- Mock을 만드는 방법을 단일화

- API가 간단

- 프레임워크가 지원하지 않으면 안되는 코드를 최대한 배제

- 에러추적이 깔끔    

 

2) 과정

- Mock 생성 -> Stub -> 수행 -> 검증

- Mock 생성

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
        Test testMock = Mockito.mock(Test.class);
        System.out.println(testMock.getId());       // -> 0
        System.out.println(testMock.getMsg());      // -> null         
    }

 

- Stub : Mockito.when으로 특정 메소드가 호출 됐을 때, .thenReturn(return값)을 지정함

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
        Test testMock = Mockito.mock(Test.class);
        System.out.println(testMock.getId());       // -> 0
        System.out.println(testMock.getMsg());      // -> null        
       Mockito.when(testMock.getId()).thenReturn(1);
        Mockito.when(testMock.getMsg()).thenReturn("MockTest");
        System.out.println(testMock.getId());       // -> 1
        System.out.println(testMock.getMsg());      // -> "MockTest" 
    }

 

-> 결과

 

- 검증 : Mockto.verify를 사용하여 특정 mock에 대해 어떤 조건을 만족하며 method를 호출했는지 검증

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
        Test testMock = Mockito.mock(Test.class);
        System.out.println(testMock.getId());       // -> 0
        System.out.println(testMock.getMsg());      // -> null        
       Mockito.when(testMock.getId()).thenReturn(1);
        Mockito.when(testMock.getMsg()).thenReturn("MockTest");
        System.out.println(testMock.getId());       // -> 1
        System.out.println(testMock.getMsg());      // -> "MockTest" 
 
        Mockito.verify(testMock,Mockito.never()).getId();        // 검증
    }

 

-> 결과

 

- spy 사용 : 일반 객체를 그대로 가져옴

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
         Test test = new Test(10,"test");
        Test testMock1 = Mockito.spy(test);
        System.out.println(testMock1.getId());      // -> 10
        System.out.println(testMock1.getMsg());     // -> "test"
    }

 

-> 결과

 

- SMART_NULL 사용 : RETURNS_SMART_NULLS 사용 시, int는 0, string은 "" 값이 들어감

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
        Test testMock2 = Mockito.mock(Test.class,Mockito.RETURNS_SMART_NULLS);
        System.out.println(testMock2.getId());      // -> 0
        System.out.println(testMock2.getMsg());     // -> ""
    }

 

-> 결과

 

 

BDD(Behavior-Driven Development)

 

- TDD에 대한 시작점, 범위, 테스트 케이스 등에 대한 문제를 해결해 줄 수 있는 방식

- 어플리켕션의 행위 중 가치 있는 기능부터 개발하여 테스트하는 방식

- TDD(Test-Driven Development) : inside-out 방식(안쪽 모듈 -> 사용자에 가까운 부분)

-> unit test 지향

- BDD : outside-in 방식(사용자에 가까운 부분 -> 안쪽 모듈)

-> 사용자 테스트, 회귀 테스트 지향

 

 

TDD와의 차이점

 

1. BDD는 사용자에 좀 더 가까운 고수준의 기능영역을 우선적으로 다룸

 

2. 무엇을 테스트할 것인가에 초점이 맞추고 있음

 

3. Test method 작성에 용이한 문장적인 템플릿 제공

- Given(조건), When(기능 수행), Then(예상 결과)

@Test
 public void test_getName_isOk() throws Exception{
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        class Test {
            private int id;
            private String msg;
        }
        Test test = BDDMockito.mock(Test.class);
        System.out.println(test.getId());
        BDDMockito.when(test.getId()).thenReturn(10);              
        System.out.println(test.getId());
    }

 

- Spring boot에 있는 BDDMockito 사용

- when으로 특정 method 지정 시, 해당 method를 호출 할 때, 해당 값을 return하도록 설정

 

-> 결과

 

 

 

 

Reference) https://repo.yona.io/doortts/blog/issues

 

출처 : dongdd.tistory.com/165