이동욱님의 '스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 책과 채수원님의 'TDD 실천법과 도구'를 참고했다.
코살람 유지보수를 다시 시작해볼까한다.
유지보수 때는 TDD 개발 방식을 도입해보려고 한다.
인턴 기간 중 테스트코드가 오히려 총 개발 시간을 줄여준다는 걸 깨달은 적이 있었다. 신뢰성은 덤~!
마침 코살람 유지보수를 시작하기로 마음먹었으니 여기에 적용해보려고 한다!
적용하고 나면 코드를 수정할 때마다 Tomcat 을 내렸다 켰다 하는 경험을 하지 않을 수 있게 될 것이다. 얏호
Test Driven Development의 약자. 테스트가 주도하는 개발
1. 항상 실패하는 테스트를 먼저 작성하고(RED)
2. 테스트를 통과하는 프로덕션 코드를 작성하고(Green)
3. 테스트가 통과하면 프로덕현 코드를 리팩토링한다.(Refactor)
조금 더 자세한 설명
1. Ask(질문): 테스트 작성으르 통해 시스템에 질문(결과는 실패로)
2. Respond(응답): 테스트를 통과하는 코드를 작성해서 질문에 대답(성공)
3. Refine(정제): 아이디어를 통합하고 불필요한 것은 제거하고, 모호한 것은 명확히 해서 대답을 정제(리팩토링)
4. Repeat(반복): 다음 질문을 통해 대화를 계속 진행
음 TDD는 테스트 작성을 먼저 한다는 게 가장 중요한 전제 조건인데
코살람은 이미 기능 구현이 돼있다.
TDD를 적용한다기보단 공부해서 테스트 코드를 추가했다...라고 봐야할듯하다.
물론 앞으로 추가되는 기능들은 전부 TDD를 적용할 예정이다.
TDD 사이클을 한번 돌아보자!
JUnit 과 같은 단위테스트 프레임워크를 사용하지 않은 예시다.
역시 맨땅부터 찬찬히 공부하는 게 가장 효과적인 방법인 것 같다.
테스트 작성으르 통해 시스템에 질문(결과는 실패로)
- 테스트 시나리오 작성 : 기도실을 생성한다 -> 정상적으로 생성됐는지 확인한다.
- 실패하는 테스트 메소드 생성
TDD에는 테스트의 최소 작성 단위를 최하위 모듈의 단위와 일치시킨다. Java 언어 기준으로 최하위 모듈은 method다.
첫 ask 단계에서는 메소드 수준의 단위 테스트를 작성한다.
작성하고자 하는 메소드나 기능이 무엇인지 선별하고
작성 완료 조건을 정해서 실패하는 테스트 케이스를 작성하는 것이다.
우선 구현해야하는 기능을 정리해보자
- 클래스 이름은 Prayerroom
- 기능
- CRUD
- 반경 Nkm 내의 기도실 리스트 조회
- 좋아요
- 리뷰
정리한 기능에 대한 테스트 케이스를 하나씩 추가해나가면서 구현 클래스를 점진적으로 만들어나가는 방식이 가장 좋다.
대부분의 경우 하나의 테스트 케이스는 하나의 메소드로 표현된다.
public class PrayerroomTest {
public void testPrayerroom() {
Prayerroom prayerroom = new Prayerroom();
if (prayerroom == null) {
throw new Exception("기도실 생성 실패");
}
}
테스트를 통과하는 코드를 작성해서 질문에 대답(성공)
- 기도실 생성 테스트 케이스를 통과하는 코드를 작성한다.
public class PrayerroomTest {
public void testAccount() throws Exception{
Account account = new Account();
if ( account == null){
throw new Exception("계좌생성 실패");
}
}
public static void main(String[] args) {
AccountTest test = new AccountTest();
try {
test.testAccount(); // 테스트 케이스 실행
} catch (Exception e) {
System.out.println("실패(X)"); // 예외가 발생하면 실패(X)
return;
}
System.out.println("성공(O)");
}
}
사실 예시로 작성한 객체가 잘 생성되는지에 대한 테스트는 일반적으로 필요없다.
Java JDK가 망가지지 않는 이상.. 문제될 일이 없기 때문이다.
다만, 생성 로직에서 특별한 처리가 필요할 때(ex. 1000이상의 값이 들어가면 안된다. not nullable이다.)는 있는 게 좋다.
테케는 메소드 사용 설명서
라는 측면에서 해당 클래스를 사용하게 될 다른 개발자들에게 도움이 되기 때문이다.
아이디어를 통합하고 불필요한 것은 제거하고, 모호한 것은 명확히 해서 대답을 정제(리팩토링)
아래와 같은 항목을 리팩토링 적용 대상으로 고민해본다.
에릭 감마와 켄트 벡이 탄생시킨 JUnit은 현재 전세계적으로 가장 널리 사용되는
Java 단위 테스트 프레임워크다.
테스트 결과가 예상과 같은지 판별하는 것
@Test // 테스트 메소드로 지정해줌
public void testAssertFalse() {
assertEquals([message], expected, result);
assertSame([message], expected, result); // 두 객체가 동일한 객체인지 주소값으로 비교
assertTrue([message], expected);
assertNull([message], expected);
}
assertSame
단정문은 주로 동일 객체임을 증명하는 데 쓰인다. 이를테면 캐시 기능을 만들었는데 해당 캐시가 제대로 동작하는지 판단해야한다고 가정해보자. 이때 특정 객체가 캐시에서 가져온 객체와 동일한지 여부를 판단할 수 있다.
cache.add(someObject, KEY);
assertSame("캐시처리 실패!", someObject, cache.lookup(KEY));
싱글톤(특정 클래스의 인스턴스가 오직 하나만 생성될 수 있도록 하는 디자인 패턴)으로 만들어진 객체를 비교할 때 쓰이기도 한다.
작성한 테스트를 실행하기 위해서는 내장된 JUnit 태스트 러너를 이용하는 방법과 테스트 클래스를 직접 실행하는 방법이 있다.
java 프로그램에서 실행
org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...);
콘솔에서 실행
$ java org.junit.runner.JUnitCore TestClass1 [...other test classes...]
@RunWith
annotationJUnit에 내장된 러너 대신 해당 클래스에서 테스트를 실행하기 위해 참조하는 클래스를 호출한다.
@RunWith(SpringJUnit4ClassRunner.class)
라고 하면
테스트를 진행할 때 JUnit 내장 러너 대신 스프링 부트를 테스트하는 실행자를 실행시킨다고 보면된다. 즉, 스프링 부트 테스트와 JUnit 사이의 연결자 역할을 해준다.
@RunWith(SpringJUnit4ClassRunner.class)
public PrayerroomTest {
@Test
public getPrayerroomTest() {
}
}
테스트를 반복적으로 수행할 수 있게 도와주고 매번 동일한 결과를 얻을 수 있게 도와주는
기반이 되는 상태나 환경를 의미한다. 일관된 테스트 실행환경이라고도 함.
For example
- Preparation of input data and setup/creation of fake or mock objects
- Loading a database with a specific, known set of data
- Copying a specific known set of files creating a test fixture will create a set of objects initialized to certain states.
이제 개념 공부는 대강 끝났으니 Kosalaam 프로젝트에 단위테스트부터 추가해보려고 한다. 그 과정은 다음 글에서!