1. 상황 찾기
어떤 상황이 실행 결과에 영향을 줄 수 있는지 찾기 위한 노력 필요
다양한 예외 상황을 찾아내고 이를 코드에 반영해야 기능이 비정상적으로 동작하는 것을 막을 수 있음
2. 테스트 코드의 구성 요소
기능은 상황에 따라 달라 지기 때문에 상황(given), 실행(when) 결과 확인(then) 세 가지 요소로 테스트를 구성할 수 있음
JUnit에서 상황을 설정하는 방법은 테스트할 대상에 따라 달라짐
1) 각 테스트 메서드마다 객체를 생성해서 상황 설정
public class BaseballGameTest {
@Test
void exactMatch() {
// given
BaseballGame game = new BaseballGame("456");
// when
Score score = game.guess("456");
// then
assertEquals(3, score.strikes());
assertEquals(0,score.balls());
}
@Test
void noMatch() {
// given
BaseballGame game = new BaseballGame("123");
// when
Score score = game.guess("456");
// then
assertEquals(0, score.strikes());
assertEquals(0,score.balls());
}
}
2) @BeforeEach를 적용한 메소드에서 상황 설정(주로 상황 설정과 관련된 대상을 필드로 보관)
public class BaseballGameTest {
@BeforeEach
void givenGame() {
game = new BaseballGame("456");
}
@Test
void exactMatch() {
// when
Score score = game.guess("456");
// then
assertEquals(3, score.strikes());
assertEquals(0, score.balls());
}
}
3) 상황이 없는 경우, 결과에 영향을 주는 상황이 존재하지 않을 때
- 기능을 실행하고 결과를 리턴 값으로 확인
@Test
void meetsAllCriteria_Then_Strong() {
// when
PasswordStrengthMeter meter = new PasswordStrengthMeter();
PasswordStrength result = meter.meter("ab12!@AB");
// then
asserEquals(PasswordStrength.STRONG, result);
}
- 실행 결과가 리턴 값이 아닌 예외가 발생하는 것이 정상 실행 결과인 경우
@Test
void genGame_With_DupNumber_Then_Fail() {
assertThrows(IllegalArgumentException.class,
() -> new BaseballGame("110")
);
}
📝 상황(given), 실행(when) 결과 확인(then) 구조에 너무 집착하지 말기! 테스트 코드를 작성하는데 도움이 되지만 꼭 모든 테스트 메소드를 이 구조로 만들어야 하는 것은 아님, 테스트 코드를 보고 테스트 내용을 이해할 수 있으면 됨
3. 외부 상황과 외부 결과
상황 설정은 테스트 대상만 있는 것이 아닌 외부 요인도 상황 설정에 고려 대상임
파일이 필요한 테스트는 파일이 존재 여부에 따른 상황의 결과 확인도 필요함
file dataFile = new File("file.txt");
long sum = MathUtils.sum(dafaFile);
1) 파일이 존재하지 않는 경우
- 존재하지 않는 파일을 경로로 사용하는 방법이 있지만 항상 테스트 성공이 보장되지 않음, 파일이 우연히 존재한다면? 상황에 따라 결과가 달라진다면 테스트는 신뢰할 수 없음
- 명시적으로 파일이 없는 상황을 만들어서 테스트 , 해당 경로에 파일이 존재하는지 검사해서 존재할 경우 파일 삭제
@Test
void noDataFile_Then_Exception() {
givenNoFile("badpath.txt");
File dataFile = new File("badpath.txt");
assertThrows(IllegalArgumentException.class,
() -> MathUtils.sum(dataFile));
}
private void givenNoFile(String path) {
File file = new File(path);
if (file.exists()) {
boolean deleted = file.delete();
if (!deleted)
throw new RuntimeException("file givenNoFile: " + path);
}
}
2) 파일이 존재하는 경우
- 상황에 알맞은 파일을 src/test/resource 폴더에 미리 만들어 두는 방법, 주의할 점은 다른 개발자들도 테스트가 가능하도록 테스트에 맞게 준비한 파일을 버전 관리 대상에 추가해야함
- 파일을 미리 만들지 않고 테스트 코드에서 상황에 맞는 파일을 생성하는 방법
@Test
void dataFileSumTest() {
givenDataFile("target/datafile.txt", "1", "2", "3", "4");
File dataFile = new File("target/datafile.txt");
long sum = MathUtils.sum(dataFile);
assertEquals(10L, sum);
}
private void givenDataFile(String path, String ... lines) {
try {
Path dataPath = Paths.get(path);
if (Files.exists(dataPath)) {
Files.delete(dataPath);
}
Files.write(dataPath, Arrays.asList(lines));
} catch (IOException e) {
throw new RuntimeException((e));
}
}
4. 외부 상태가 테스트 결과에 영향을 주지 않게 하기
DB 데이터 상태(외부 상태) 에 따라 테스트 결과가 달라지는 경우
예를 들어 회원가입 기능에서 중복된 아이디로 가입 시 가입이 안되는 기능을 테스트 하는 경우, DB에 회원 테이블에 아이디가 없는 경우와 아이디가 있는 경우 테스트 결과가 달라 질 수 있음
1) 테스트 실행 전 외부 상태를 원하는 상태로 만들기: 테스트 실행 전 테스트하려는 아이디를 삭제하는 로직 추가
2) 테스트 실행 후 외부 상태를 원래대로 돌려 놓기: 테스트 메소드 실행 후 트랜잭션을 롤백
5. 외부 상태와 테스트 어려움
상황과 결과에 영향을 주는 외부 요인은 파일, DBMS, 외부 서버(외부 REST API 사용) 등 다양
외부 환경을 테스트에 맞게 구성하는 것이 항상 가능 한 것은 아니기 때문에 대역을 사용하여 테스트 코드 작성에 도움을 받을 수 있음
참고: 최범균의 테스트 주도 개발(TDD) 시작하기
'Spring > Test' 카테고리의 다른 글
AssertJ (0) | 2022.12.01 |
---|---|
TDD, 대역을 이용한 테스트 (0) | 2022.11.30 |
TDD 테스트 주도 개발, 기능 명세와 설계 (0) | 2022.11.27 |
TDD 테스트 코드 작성 방법 (0) | 2022.11.27 |
테스트 주도 개발 시작하기 - TDD 시작, TDD 암호 검사기 3 (0) | 2022.11.27 |