Spring에서 개발자에게 제공하는 가장 중요한 가치는 객체지향과 테스트이다. 스프링의 핵심인 IoC와 DI는 오브젝트의 설계와 생성, 관계, 사용에 관환 기술이다. Spring은 IoC/DI를 이용해 객체지향 프로그래밍 언어의 근본과 가치를 개발자가 손쉽게 적용하고 사용할 수 있게 도와주는 기술이다.
개발을 진행하다보면 애플리케이션은 계속 변하고 복잡해져 간다. 그 변화에 대응하는 첫 번째 전략이 확장과 변화를 고려한 객체지향적 설계와 그것을 효과적으로 담아낼 수 있는 IoC/DI 같은 기술이라면, 두 번째 전략은 만들어진 코드를 확실할 수 있게 해주고, 변화에 유연하게 대처할 수 있는 테스트 기술이다.
▶ 웹을 통한 테스트 방법의 문제점
테스트 코드를 사용하지 않고 웹을 통해 테스트하는 것은 매우 불편하다. 나도 저번 프로젝트를 진행하면서 테스트 코드를 적용하지 않아 웹을 통해 API를 호출하고 CRUD를 눈과 DB로 확인하며 테스트를 진행했었다. 하지만 이렇게 진행하는 방법은 시간도 오래 걸릴 뿐만 아니라 가장 큰 문제점이 존재한다. 그 문제점이 무엇이냐면 나는 작은 기능 하나만 테스트를 하고자 하는데 웹 동작을 위한 서비스 계층, MVC 프레젠테이션 계층, DB SQL 문법, JDBC API 호출 등 모든 것이 필요하다는 것이다. 작은 기능 테스트를 위해 이와 같은 것들을 모두 고려한 후 문제가 없을 때 단위 테스트가 가능하다. 해당 파트에서는 어떻게 이런 문제를 피할 수 있고, 효율적으로 테스트를 활용할 수 있는지 학습할 수 있는 파트이다.
▶ 작은 단위의 테스트
테스트하고자 하는 대상이 명확하다면 그 대상에만 집중해서 테스트하는 것이 바람직하다. 한꺼번에 너무 많은 것을 몰아서 테스트하면 테스트 수행 과정도 복잡해지고, 오류가 발생했을 때 정확한 원인을 찾기가 힘들어진다. 따라서 테스트는 가능하면 작은 단위로 쪼개서 집중해서 할 수 있어야 한다. 관심사의 분리라는 원리가 여기에도 적용된다.
이렇게 작은 단위의 코드에 대해 테스트를 수행하는 것을 단위 테스트(unit Test)라고 한다. 여기서 말하는 단위란 무엇인지, 그 크기와 범위가 어느 정도인지 딱 정해진 건 아니다. 충분히 하나의 관심에 집중해서 효율적으로 테스트할 만한 범위의 단위라고 보면 된다.
단위 테스트를 하는 이유는 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빨리 확인받기 위해서다.
▶ JUnit 테스트
JUnit은 프레임워크다. 프레임워크의 기본 동작 원리가 바로 제어의 역전(IoC)이다. 프레임워크는 개발자가 만든 클래스에 대한 제어 권한을 넘겨 받아서 주도적으로 애플리케이션의 흐름을 제어한다. 개발자가 만든 클래스의 오브젝트를 생성하고 실행하는 일은 프레임워크에 의 해 진행된다.
JUnit 프레임워크를 적용하여 테스트를 개발할 때에는 그에 맞는 요구 두 가지를 충족해야한다.
- 첫 째는 메소드가 public으로 선언되어야 한다.
- 두 번째는 메소드에 @Test라는 어노테이션을 적용해준다.
그러면 검증 코드 또한 JUnit을 적용시켜 변화시킬 수 있다.
if(!user.getName().equals(user2.getName())) {...}
위와 같은 if/else 문장을 JUnit이 제공해주는 assertThat이라는 스태틱 메소드를 이용해 다음과 같이 변경할 수 있다.
aasertThat(user2.getName(), is(user.getName()));
assertThat() 메소드는 첫 번째 파라미터의 값을 뒤에 나오는 매처(matcher)라고 불리는 조건으로 비교해서 일치하면 다음으로 넘어가고, 아니면 테스트가 실패하도록 만들어준다. is()는 매처의 일종으로 equals()로 비교해주는 기능을 가졌다.
▶ 테스트 주도 개발
만들고자 하는 기능의 내용을 담고 있으면서 만들어진 코드를 검증도 해줄 수 있도록 테스트 코드를 먼저 만들고, 테스트를 성공하게 해주는 코드를 작성하는 방식의 개발 방법이 있다. 이를 테스트 주도 개발(TDD : Test Driven Development)이라고 한다. 또는 테스트를 코드보다 먼저 작성한다고 해서 테스트 우선 개발이라고 한다.
TDD는 개발자가 테스트를 만들어가며 개발하는 방법이 주는 장점을 극대화한 방법이라고 볼 수 있다.
"실패한 테스트를 성공시키기 위한 목적이 아닌 코드는 만들지 않는다."는 것이 TDD의 기본 원칙이다. 이 원칙을 따랐다면 만들어진 모든 코드는 빠짐없이 테스트로 검증된 것이라고 볼 수 있다.
개발자듶이 정신없이 개발을 하다 보면 사이사이 테스트를 만들어서 코드를 점검할 타이밍을 놓치는 경우가 많다. 빨리 기능을 완성하고 싶은 욕구 때문일 수도 있고, 한번 집중하면 정신없이 빠져드는 습성 때문이기도 하다. 문제는 코드를 만들고 나서 시간이 많이 지나면 테스트를 만들기가 귀찮아진다는 점이다. 또, 작성한 코드가 많기 때문에 무엇을 테스트해야 할지 막막할 수도 있다. 결국 테스트 작성은 자꾸 뒷전으로 밀려나거나 점점 더 성의 없는 테스트를 만들게 될지도 모른다.
TDD는 아예 테스트를 먼저 만들고 그 테스트가 성공하도록 하는 코드만 만드는 식으로 진행하기 때문에 테스트를 빼먹지 않고 꼼꼼하게 만들어낼 수 있다.
▶ JUnit 프레임워크가 테스트 메소드를 실행하는 과정
프레임워크는 스스로 제어권을 가지고 주도적으로 동작하고, 개발자가 만든 코드는 프레임워크에 의해 수동적으로 실행된다. 그래서 프레임워크에 사용되는 코드만으로는 실행 흐름이 잘 보이지 않기 때문에 프레임워크가 어떻게 사용할지를 잘 이해하고 있어야 한다.
JUnit이 하나의 테스트 클래스를 가져와 테스트를 수행하는 방식은 다음과 같다.
- 테스트 클래스에서 @Test가 붙은 public이고 void형이며 파라미터가 없는 테스트 메소드를 모두 찾는다.
- 테스트 클래스의 오브젝트를 하나 만든다.
- @Before가 붙은 메소드가 있으면 실행한다.
- @Test가 붙은 메소드를 하나 호출하고 테스트 결과를 저장해둔다.
- @After가 붙은 메소드가 있으면 실행한다.
- 나머지 테스트 메소드에 대해 2~5번을 반복한다.
- 모든 테스트의 결과를 종합해서 돌려준다.
JUnit은 @Test가 붙은 메소드를 실행하기 전과 후에 각각 @Before와 @After가 붙은 메소드를 자동으로 실행한다.
@Before나 @After 메소드를 테스트 메소드에서 직접 호출하지 않기 때문에 서로 주고 받을 정보나 오브젝트가 있다면 인스턴스 변수를 사용해야한다.
또 한 가지 꼭 기억해야 할 사항은 각 테스트 메소드를 실행할 때마다 테스트 클래스의 오브젝트를 새로 만든다는 점이다. 한 번 만들어진 테스트 클래스의 오브젝트는 하나의 테스트 메소드를 사용하고 나면 버려진다. 테스트 클래스가 @Test 테스트 메소드를 두 개 갖고 있다면, 테스트가 실행되는 중에 JUnit은 이 클래스의 오브젝트를 두 번 만들 것이다.
▶ 그런데, 왜 테스트 메소드를 실행할 때마다 새로운 오브젝트를 만드는 것일까?
그냥 테스트 클래스마다 하나의 오브젝트만 만들어놓고 사용하는 편이 성능도 낫고 더 효율적이지 않을까? JUnit 개발자는 각 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 확실히 보장해주기 위해 매번 새로운 오브젝트를 만들게 했다. 덕분에 인스턴스 변수도 부담 없이 사용할 수 있다. 어차피 다음 테스트 메소드가 실행될 때는 새로운 오브젝트가 만들어져서 다 초기화될 것이다.
테스트 메솓의 일부에서만 공통적으로 사용되는 코드가 있다면 어떻게 해야 할까? 이 때는 @Before를 사용하기보다는, 일반적인 메소드 추출 방법을 써서 메소드를 분리하고 테스트 메소드에서 직접 호출해 사용하도록 만드는 편이 낫다. 아니면 아예 공통적인 특징을 지닌 테스트 메소드를 모아서 별도의 테스트 클래스로 만드는 방법도 생각해볼 수 있다.
▶ 픽스처
테스트를 수행하는 데 필요한 정보나 오브젝트를 픽스터(fixture)라고 한다.
'Spring > 스프링의 이해와 원리' 카테고리의 다른 글
[스프링의 이해와 원리] 2.2 테스트 (0) | 2023.02.04 |
---|---|
스프링의 디자인 패턴 (0) | 2022.12.16 |
오브젝트와 의존관계 - 싱글톤 패턴과 스프링에서의 싱글톤 (0) | 2022.07.28 |
오브젝트와 의존관계 - 3. DB 커넥션 독립 / 디자인 패턴 (0) | 2022.07.13 |
오브젝트와 의존관계 - etc. 스프링 IoC의 용어 정리 (0) | 2022.03.14 |