사람들이 TDD를 많이 언급하는 것이 보였다.
실제 입사할 때, 포트폴리오를 제출할 때에도 테스트 코드가 있는지 살펴보는 것을 알 수 있다.
입사과정에서 테스트 코드를 본다는 사실은 즉슨, 테스트 주도 개발이 매우 중요하고 핵심적인 것이라는 뜻이다.
개발에 관심이 많은 사람이라면 누구나 "TDD"라는 말을 들어보았을 것이다.
TDD가 대체 뭐길래 그렇게 언급이 되는 걸까..?
테스트 주도 개발(Test-driven development TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다.
위키 백과에서는 위와 같이 말한다.
TDD에는 Red, Green, Refactoring이라는 3가지의 개념이 있다.
다음 그림을 보자.
빨간줄, 초록줄, 파란줄이 보인다.
Red : 하려는 행위를 변수, 객체, 메소드가 있다는 전제하에 코드를 작성한다. 테스트를 실행하고 실패한다.
Green : 테스트를 통과시키기 위한 코드를 작성한다.
Refactoring : 통과된 코드를 리팩토링한다.
위 Red, Green, Refactoring 과정을 살펴보면 이해가 안될 수도 있다. 나 또한 그랬다. "굳이 왜 저렇게 하나?"라고 생각을 했다.
관련 자료를 많이 찾아보니, 조금은 이해가 간다.
나의 뇌피셜로는 다음과 같은 생각이 든다.
TDD를 하기 때문에, 메소드에는 정말 필요한 기능만 정의하게 된다. 그리고 불확실한 변수, 메소드, 로직들이 들어가지 않는다.
각 진영마다 TDD를 담당하는 기술이 있다.
Java는 대표적으로 JUnit을 사용한다.
Python은 pytest를 대표적으로 사용한다.
그래서 오늘 글에서는 JUnit을 알아보도록 하자.
JUnit이란 자바 프로그램의 단위 테스트를 위한 대표적인 프레임 워크이다.
"@Test"메소드가 호출될 때마다 새로운 인스턴스를 생성하여 독립적 테스트를 수행한다.
assertXXXX 메소드로 테스트 케이스의 수행 결과를 판단한다.
어노테이션을 제공하여 매우 쉽고 간결하게 테스트 코드를 작성하여 실행 가능하다.
현재 가장 최신버전은 JUnit 5 이다.
나는 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);
}
}
코드가 이해되면서도 안될 것이다.
테스트는 준비 -> 동작 -> 확인 과정을 거친다.
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("테스팅 끝");
}
}
위의 코드를 실행하면
테스팅 끝
테스팅 끝
이렇게 나올 것이다.
동일한 기능을 다른 값으로 여러번 테스트를 실행해야 하는 경우가 있을 것이다.
그때에는 어떻게 해야하는지 살펴보자.
말로만 보았을 때, 이해가 안간다.
코드를 통해 확실히 보자!
@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번 실행할 수 있게 된다.
되도록 많은 것을 테스트 해야한다고 생각한다. 커버리지 100%는 매우매우 어렵다고 하지만 100%를 지향해야 하지 않을까?
나는 앞으로 모든 행위를 테스트할 것이다.
이유는 코드의 품질을 높일 수 있기 때문.
단, 외부 모듈을 사용해야하는 경우에는 moking에 대해서 고민해야겠지..
테스트에 대해서 "포프"님이 영상을 올려주신 것이 있다.
혹시나 참고하자
어디까지 테스트를 해야할까? - 포프TV(Youtube)
다음 순서로 하자!
테스트를 시작할 때,
1. 현재 코드를 통해서 구현하려는 행위를 생각한다.
모든 변수와 객체, 메소드가 있다는 전제 하에 행위를 하는 코드를 작성한다.
테스트를 수행한다.
에러가 생긴 변수, 객체, 메소드를 생성한다.
테스트를 통과한다.
리팩토링을 수행한다.
나는 객체를 생성하여, 두 정수를 더하는 작업을 할 것이다. 코드를 짜고, 테스트를 실행하자.
"Somthing" 객체가 없으니까 만들고 테스트를 실행하자.
"sum" 함수가 없어서 에러가 생겼다.
"sum" 함수를 생성하자.
다시 Test를 수행하자.
테스트에 통과했다.
Refactoring을 수행하자.
다시 테스트를 수행하자.
하나의 유닛 테스트를 수행하는 과정을 살펴보았다.
아직까지, 완벽한 테스팅같은 기분은 들지 않는다.
DB 조회, Web 요청과정 등등 테스팅을 해야 한다.
계속해서 각 유닛테스트는 어떤 식으로 이루어지는지 글을 올리자!!