[Java] TDD 어떻게 하는가?

YoungHo-Cha·2021년 9월 22일
4

Java

목록 보기
3/7
post-thumbnail

사람들이 TDD를 많이 언급하는 것이 보였다.

실제 입사할 때, 포트폴리오를 제출할 때에도 테스트 코드가 있는지 살펴보는 것을 알 수 있다.

입사과정에서 테스트 코드를 본다는 사실은 즉슨, 테스트 주도 개발이 매우 중요하고 핵심적인 것이라는 뜻이다.

🚗오늘은 TDD가 무엇이고, 어떻게 하는 것인지 살펴보자.


🐱‍👓목차

  • TDD란?
  • JUnit이란?
  • 어떤 것을 Test 해야 하나?
  • 어떻게 하나?
  • 실제 코드

📌TDD란?

개발에 관심이 많은 사람이라면 누구나 "TDD"라는 말을 들어보았을 것이다.
TDD가 대체 뭐길래 그렇게 언급이 되는 걸까..?

테스트 주도 개발(Test-driven development TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다.

위키 백과에서는 위와 같이 말한다.

🔎Red, Green, Refactoring

TDD에는 Red, Green, Refactoring이라는 3가지의 개념이 있다.

다음 그림을 보자.

빨간줄, 초록줄, 파란줄이 보인다.

Red : 하려는 행위를 변수, 객체, 메소드가 있다는 전제하에 코드를 작성한다. 테스트를 실행하고 실패한다.

Green : 테스트를 통과시키기 위한 코드를 작성한다.

Refactoring : 통과된 코드를 리팩토링한다.

위 Red, Green, Refactoring 과정을 살펴보면 이해가 안될 수도 있다. 나 또한 그랬다. "굳이 왜 저렇게 하나?"라고 생각을 했다.

관련 자료를 많이 찾아보니, 조금은 이해가 간다.

나의 뇌피셜로는 다음과 같은 생각이 든다.

TDD를 하기 때문에, 메소드에는 정말 필요한 기능만 정의하게 된다. 그리고 불확실한 변수, 메소드, 로직들이 들어가지 않는다.

🔎어떤 것을 이용하나?

각 진영마다 TDD를 담당하는 기술이 있다.
Java는 대표적으로 JUnit을 사용한다.
Python은 pytest를 대표적으로 사용한다.

그래서 오늘 글에서는 JUnit을 알아보도록 하자.


📌JUnit이란?

JUnit이란 자바 프로그램의 단위 테스트를 위한 대표적인 프레임 워크이다.

🔎JUnit 특징

  • "@Test"메소드가 호출될 때마다 새로운 인스턴스를 생성하여 독립적 테스트를 수행한다.

  • assertXXXX 메소드로 테스트 케이스의 수행 결과를 판단한다.

  • 어노테이션을 제공하여 매우 쉽고 간결하게 테스트 코드를 작성하여 실행 가능하다.

  • 현재 가장 최신버전은 JUnit 5 이다.

🔎Junit 테스트 메소드 살펴보기

나는 intelliJ에 Spring과 함께 사용할 예정이다.

🔎🔎테스트 메소드의 구성

코드부터 살펴보자


public class SomeTest{
	
    //합계테스트 / 유닛테스트1
	@Test
    public void testSum(){
    	int result = 0;
        
        Something some = new Somthing();
        result = som.sum(10);
        assertEquals(55, result);
        
    //정렬테스트 / 유닛테스트2
    @Test
    public void testSort(){
    	int[] a = {44, 33, 66, 22, 55, 11};
        int[] b = {11, 22, 33, 44, 55, 66};
        
        Somthing some = new Somthing();
        some.bubble_sort(a);
        assertArrayEquals(b, a);
    }
    
}

코드가 이해되면서도 안될 것이다.

테스트는 준비 -> 동작 -> 확인 과정을 거친다.

  • @Test : 단위 테스트 메소드임을 나타낸다.
    * 준비(Arrange) : 테스트를 수행하기위한 테스트 환경 조성 부분
    • 동작(Act) : 실제 테스트 대상 기능 실행
    • 확인(Assert) : 동작의 실제 실행 결과와 기대 결과를 비교 확인

🔎🔎테스트 메소드 실행

  • 테스트 클래스를 우클릭 -> Run
  • 성공하면 green bar / 실패하면 red bar
  • 실패시, Failures에 실패한 테스트 메소드 개수를 표시한다.

🔎🔎Assert 구문

Assert는 "확인"을 수행하는 동작이다.

Assert가 제공하는 함수를 보자.

Assert 함수용도
Assert.fail(message)메소드 실행 결과를 실패로 만듦, 테스트 코드를 구현하기 전에 placeholder로 활용 가능
Assert.assertEquals("출력글", a, b)a와 b가 같은지 테스트한다. 같으면 성공 & 같지 않으면 실패, 실패하면 "출력글"을 출력한다.
Assert.assertEquals("출력글", a, b, delta)a와 b가 delta 범위 내에서 같은지 테스트한다.
Assert.assertArrayEquals(array1, array2)array1과 array2가 일치하는지 테스트한다.

🔎🔎테스트 어노테이션

  • @Test : 해당 메소드가 테스트 대상 메소드임을 의미

  • @Test(timeout=10) : 테스트 메소드 수행 시간이 10 millisecond를 넘기면 실패

  • @Test(expected=RuntimeException.class) : RuntimeException이 발생하면 테스트 성공, 아니면 실패

  • @Before 메소드 : 각 테스트 메소드에서 공통적으로 실행해야 할 작업 포함, 테스트 메소드가 실행되기 전에 실행됨

  • @After 메소드 : 각 테스트 메소드의 실행이 완료된 후에 동일한 마무리 작업을 수행할 경우 사용, 테스트 메소드가 실행된 후에 실행됨

  • @BeforeClass 메소드 : static 메소드이며, 모든 테스트 메소드가 동작하기 전 공통적으로 실행해야 하는 작업 수행

  • @AfterClass 메소드 : static 메소드이며, 모든 테스트 메소드가 생행되고 난 후에 한번 실행하는 메소드

위의 그림처럼 행동한다.
코드로 다시한번 보자.


public class SomeTest{
	private Something some;
    
    @Before
    public void setUp(){
    	some = new Somthing();
    }
    
    @Test
    public void testSum1(){
    	int result = sume.sum(10);
        assertEquals(55, result);

	@Test
    public void testSum2(){
    	int result = some.sum(0);
        assertEquals(0, result);
    }
    
    @After
    public void testEnd(){
    	System.out.println("테스팅 끝");
    }
    
    
}

위의 코드를 실행하면

테스팅 끝
테스팅 끝

이렇게 나올 것이다.

🔎🔎반복해서 테스트 실행

동일한 기능을 다른 값으로 여러번 테스트를 실행해야 하는 경우가 있을 것이다.

그때에는 어떻게 해야하는지 살펴보자.

  1. @Runwith(Parameterized.class) 사용
  2. @Parametes를 통하여 데이터 등록
  3. 생성자 생성
  4. 테스트 코드 작성

말로만 보았을 때, 이해가 안간다.
코드를 통해 확실히 보자!


@RunWith(Parameterized.class)
public class AnotherTest{
	private int expected;
    private int value;
    
    @Parameters
    public static Collection getParameters(){
    	return Arrays.asList(new Object[][]{
        	{4, 10},
            {-200, -10}
            });
        }
        
        public AnotherTest(int expected, int value){
        	this.expected = expected;
            this.value = value;
        }
        
        @Test
        public void testSum(){
        	Something some = new Something();
            int result = some.sum(value);
            assertEquals(expected, result);
        }
}

위와 같이 코드를 작성하면 파라미터가 2개이므로, 테스트를 2번 실행할 수 있게 된다.


📌어떤 것을 Test 해야 하나?

되도록 많은 것을 테스트 해야한다고 생각한다. 커버리지 100%는 매우매우 어렵다고 하지만 100%를 지향해야 하지 않을까?

나는 앞으로 모든 행위를 테스트할 것이다.
이유는 코드의 품질을 높일 수 있기 때문.
단, 외부 모듈을 사용해야하는 경우에는 moking에 대해서 고민해야겠지..

테스트에 대해서 "포프"님이 영상을 올려주신 것이 있다.
혹시나 참고하자

어디까지 테스트를 해야할까? - 포프TV(Youtube)


📌어떻게 하나?

다음 순서로 하자!
테스트를 시작할 때,
1. 현재 코드를 통해서 구현하려는 행위를 생각한다.

  1. 모든 변수와 객체, 메소드가 있다는 전제 하에 행위를 하는 코드를 작성한다.

  2. 테스트를 수행한다.

  3. 에러가 생긴 변수, 객체, 메소드를 생성한다.

  4. 테스트를 통과한다.

  5. 리팩토링을 수행한다.


📌실제 흐름

  1. 나는 객체를 생성하여, 두 정수를 더하는 작업을 할 것이다. 코드를 짜고, 테스트를 실행하자.

  2. "Somthing" 객체가 없으니까 만들고 테스트를 실행하자.

  3. "sum" 함수가 없어서 에러가 생겼다.

  4. "sum" 함수를 생성하자.

  5. 다시 Test를 수행하자.

테스트에 통과했다.

  1. Refactoring을 수행하자.

  2. 다시 테스트를 수행하자.

하나의 유닛 테스트를 수행하는 과정을 살펴보았다.


📒마치며

아직까지, 완벽한 테스팅같은 기분은 들지 않는다.
DB 조회, Web 요청과정 등등 테스팅을 해야 한다.
계속해서 각 유닛테스트는 어떤 식으로 이루어지는지 글을 올리자!!


🧷Reference

profile
관심많은 영호입니다. 궁금한 거 있으시면 다음 익명 카톡으로 말씀해주시면 가능한 도와드리겠습니다! https://open.kakao.com/o/sE6T84kf

0개의 댓글