본문 바로가기

Spring Framework

[Spring] TDD란?

반응형

이번 글에서는 TDD에 대해 정리해보려고 합니다. 그리고 TDD의 매력에 눈을 뜬 저처럼 이 글을 읽는 모든 분들이 TDD 매력에 대해 느낄 수 있도록 노력해 보겠습니다!

 

제가 TDD에 매력을 느낀 이유는 실수의 빈도를 줄이고 빠르게 버그를 수정할 수 있다는 점이었습니다. 또한 테스트의 편의성을 높일 수 있다는 점도 TDD의 매력이었습니다. 

 

저는 TDD 방식을 알기 전에 여러 웹 사이트를 개발하면서 사용자의 정보를 DB에 저장하는 간단한 테스트를 하기 위해서는 다음의 과정을 거쳐야 했습니다. 

 

 1. DAO를 만든 뒤 바로 테스트하지 않고 서비스 계층, MVC, 프레젠테이션 계층까지 포함한 모든 입출력 기능을 대충이라도 코드로 다 만든다. 
2. 테스트용 웹 애플리케이션을 서버에 배치한다. 
3. 웹 화면을 띄워 폼을 열고 값을 입력한 뒤 버튼을 눌러 등록해본다. 

 

대부분의 TDD를 모른는 개발자들은 다음과 같은 테스트 방식이 익숙하다고 느끼실 것 같습니다. (저는 어제 까지 저렇게 했어요... 사용자 정보 하나 테스 하려면 UI를 만들지 않고서는 사용자의 정보가 제대로 저장되는지 확신이 없었거든요...ㅜㅜ)

 

이런 방식의 문제점은 DAO에 대한 테스트로서는 단점이 너무 많습니다. DAO 뿐만 아니라 서비스 클래스, 컨트롤러, JSP뷰 등 모든 레이어의 기능을 다 만들고 나서야 테스트가 가능하다는 게 가장 큰 문제이죠... (작은 문제의 버그도 코드의 크기가 커지게 되면 쉽게 찾기 힘들더라고요...)

 

저는 실제로 웹개발을 하면서 저런 방식으로 테스트를 했고 상당히 많은 양의 버그를 자주 맞닥뜨렸기 때문에 사수한테 굉장히 많이 혼나는 경험을 했습니다. (제 스스로를 믿지 못하는 경지에 이르렀어요...ㅋㅋㅋㅋㅋ) 

 

이 문제를 해결하기 위해서 TDD를 공부 하기 시작했습니다. 이제부터는 TDD가 무엇이고 앞의 상황의 문제를 어떻게 해결할 수 있는지 천천히 알아보려고 합니다. 

 


TDD 란?

TDD는 테스트 주도 개발(Test Driven Development)을 의미하며, 테스트를 코드보다 먼저 작성한다고 해서 테스트 우선 개발(TFD : Test First Development)라고도 합니다.

 

여기서 주의해야 할점은 테스트하고자 하는 대상을 명확히 해야 한다는 것입니다. 한꺼번에 너무 많은 것을 몰아서 테스 하면 테스트 수행 과정도 복잡해지고 오류가 발생했을 때 정확한 원인을 찾기 힘듭니다. 따라서 테스트는 가능하면 작은 단위로 쪼개서 집중할 수 있게 해야 하는데 이렇게 작은 단위의 코드에 대해 테스트를 수행하는 것을 단위 테스트(Unit Test)라고 합니다. 

 

자바에서는 단순하면서도 실용적인 테스트를 위한 여러 도구를 제공합니다. 그중에서도 자바 테스팅 프레임워크라고 불리는 JUnit은 자바로 단위 테스트를 만들 때 유용하게 사용할 수 있습니다.

 


JUnit 사용하기

그럼 JUnit을 사용해 팩토리얼을 구하는 코드를 테스트하는 간단한 예시를 설계해 보겠습니다. 

 

폴더구조

Algorithms라는 자바 파일에 팩토리얼을 구하는 코드를, AlgorithmsTest 라는 파일은 테스트 코드를 작성하며 각각 src/main, src/test 폴더 하위 폴더에 위치하고 있음을 알고 있습니다. (예전에는 test폴더가 없었지만 TDD의 중요성이 대두되는 요즘에는 test용 패키지를 만드는 것을 볼 수 있습니다.)

 

Alogrithms.java
AlgorithmsTest.java

assertAll()이라는 메서드는 모든 결과값이 True 값일 때 테스트를 성공하며 assertEquals() 메서드는 왼쪽 값과 오른쪽의 값이 일치할 때 True 값을 리턴한다. 모든 값이 일치하는 True값을 리턴하기 때문에 checkFactorialForSmallNumbers()라는 메서드의 테스트를 통과할 수 있었다. 

 


JUnit의 테스트 수행 과정

JUnit 테스트 수행 과정에 대해 조금더 깊게 들어가 보겠습니다. JUnit 의 테스트 수행과정은 다음과 같은 수행과정을 거칩니다. 

 

1. 테스트 클래스에서 @Test가 붙은 public이고  void형이며 파라미터가 없는 테스트 메서드를 모두 찾는다. 
2. 테스트 클래스의 오브젝트를 하나 만든다. 
3. @Before가 붙은 메서드가 있다면 실행한다.
4. @Test가 붙은 메서드를 하나 호출하고 테스트 결과를 저장해 둔다.
5. @After 가 붙은 메서드가 있다면 실행한다.
6. 나머지 테스트 메서드에 대해 2~5를 반복한다. 
7. 모든 테스트의 결과를 종합해서 돌려준다.

 

@Before, @After 어노테이션은 테스트를 하면서 공통적으로 작성되는 코드가 있다면 활용할 수 있다. 여기서 한가지 주목할 점은 각 테스트 메서드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다는 것입니다. 즉, 한번 만들어진 테스트 클래스의 오브젝트는 하나의 테스트 메서드를 사용하고 나면 버려집니다. 만약 테스트 클래스내에 @Test가 붙은 메서드가 두개라면 JUnit은 이 클래스의 오브젝트를 두 번 만들것입니다. 

 

왜 테스트 메서드를 실행 할 때마다 새로운 오브젝트를 만드는 것일까요?  JUnit은 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위해서 매번 새로운 오브젝트를 만들게 되었습니다. 

 


TDD의 특징

TDD를 위한 코드를 설계하면서 명심해야 할점은 코드에 변경사항이 없다면 테스트는 항상 동일한 결과를 내야 한다는 점입니다. 즉, 단위 테스트는 항상 일관성 있는 결과가 보장돼야 한다는 점입니다. 예를 들어 테스트하려는 메서드가 여러 개 였을때 메서드의 실행 순서가 변경된다고 해서 테스트 결과가 변경되어서는 안 됩니다. @Test 어노테이션 붙은 메서드는 항상 동일한 테스트 결과가 보장되어야 합니다. 

 


JUnit의 테스트 효율성을 위한 노력

마지막으로 고려해볼 한 가지 찜찜한 상황을 해결해보려고 합니다. 위에서 JUnit은 각 테스트의 독립성을 위해 클래스 내의 테스트 메서드가 여러개 있다면 모든 메서드의 테스트를 위해 같은 클래스더라도 여러개의 오브젝트를 생성한다고 했습니다. 

 

만약 @Before 어노테이션이 붙은 메서드에 다음과 같은 내용의 코드가 작성되어 있다고 가정해보려고 합니다. 

 

ApplicationContext 생성 코드

ApplicationContext는 프로젝트의 설정 정보를 담을 오브젝트이며 application-context.xml 의 파일에서 그 내용이 작성되었습니다. @Before 메서드가 테스트 메서드 개수만큼 반복 되기 때문에 테스트 코드가 10개 있다면 ApplicationContext로 10번 만들어 질것입니다. 

 

ApplicationContext는 빈을 생성하고 빈 간의 연결을 관리 하기 때문에 빈의 개수가 많아지고 복잡해진다면ApplicationContext 생성에 적지 않은 시간이 걸릴 수 있습니다. (ApplicationContext 처럼 모든 테스트에서 공유되는 오브젝트를 여러번 만드는 것은 자연스럽게 비효율적이라는 생각이 들것 같습니다.)

 

ApplicationContext가 만들어질때는 모든 싱글톤 빈 오브젝트를 초기화 합니다. 빈을 생성하는 것정도는 상관없지만, 어떤 빈은 오브젝트가 생성될 때 자체적인 초기화 작업을 진행해서 제법 많은 시간을 필요로 하고, 또다른 문제는 ApplicationContext가 초기화 될 때 어떤 빈은 독자적으로 많은 리소스를 할당하거나 독립적인 스레드를 띄우기도 합니다.  

 

이런 경우 테스트를 마칠 때마다 애플리케이션 컨텍스트 내의 빈이 할당한 리소스 등을 깔끔하게 정리해주지 않으면 다음 테스트에서 새로운 ApplicationContext가 만들어지면서 문제를 일으킬 수 있습니다. 

 

때문에 ApplicationContext처럼 생성에 많은 시간과 자원이 소모되는 경우에는 테스트 전체가 공유하는 오브젝트를 만들기도 합니다. 다만 이 때도 테스트는 일관성 있는 실행 결과를 보장해야 하고, 테스트의 실행 순서가 결과에 영향을 미치지 않아야합니다. 

 

JUnit은 테스트 클래스 전체에 걸쳐 딱 한번만 실행되는 @BeforeClass 스태틱(static) 메서드를 지원합니다. 

 

이번 글은 TDD에 대해 알아 보았으며 JUnit을 사용하여 TDD를 구현하는 방법에 대해 간단히 알아 보았습니다. JUnit의 라이브러리에 더 자세한 내용은 추후에 다른 글에서 정리하겠습니다. 내용에 문제가 있다면 댓글을 통해 남겨 주세요! 

 

반응형

'Spring Framework' 카테고리의 다른 글

[Spring] PSA(Portable Service Abstraction)  (1) 2022.11.05
[Spring] 예외를 처리하는 방법  (0) 2022.11.01
[Spring] JdbcTemplate  (0) 2022.10.22
[Spring] IoC/DI 컨테이너  (0) 2022.10.12
[Spring] Gradle vs Maven  (2) 2022.09.25