일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 싱글톤
- Stateless
- VUE
- HTTP
- 의존성 주입
- 라이프 사이클
- Repository
- Spring
- js
- 로그인
- HTTP 메서드
- Excel
- DB
- cache
- thymeleaf
- vuex
- Java
- Kotlin
- javascript
- Security
- 캐시
- Singleton
- Vue.js
- BEAN
- Setter
- vue-cli
- 프로토타입
- JPA
- di
- dependency injection
- Today
- Total
jhhan의 블로그
Spring - Test Code 작성 본문
이번에는 Test Code에 대해 작성해보려 합니다.
개발을 진행함에 따라 곧바로 적용해서 로직을 수정할 수도 있겠지만,
항상 곧바로 적용할 수 있지는 않습니다.
그럴 때 테스트 코드를 만들어서 만든 로직이 정상적으로 동작하는지 알 수 있게 할 수 있습니다.
그럼 먼저 테스트 해볼 대상을 만들어보겠습니다.
정말 간단한 회원관리를 해볼 것입니다.
(아이디와 이름만 들어갈 것입니다.)
참고로 지난 번 글인 Spring - Static 글에서 사용한 프로젝트를 그대로 가져와서 사용할 것입니다.
프로젝트 구조입니다.
Member, MemberRepository, MemoryMemberRepository를 만들 것입니다.
먼저 Member 클래스입니다.
public class Member {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
아이디와 이름만 받을 수 있게 설정했습니다.
그리고 getter 와 setter를 설정했습니다.
다음은 MemberRepository입니다.(인터페이스)
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
총 4개의 메소드가 있습니다.
- save: 저장을 합니다.
- findById: id로 검색합니다.
- findByName: name으로 검색합니다.
- findAll: 저장되어 있는 모든 회원을 조회합니다.
간단하죠?
다음은 MemoryMemberRepository 클래스입니다.
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member save(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public Optional<Member> findByName(String name) {
return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
}
@Override
public List<Member> findAll() {
return new ArrayList<>(store.values());
}
public void clearStore() {
store.clear();
}
}
MemberRepository를 구현합니다.
그리고 추가로 clearStore()라는 메소드를 추가했습니다.
store에 저장된 회원들을 모두 지워주는 역할을 합니다.
여기서 Optional<>은 객체의 반환값이 null일 때도 대비할 수 있게 합니다.
findById와 FindByName 메소드는 반환값이 없을 경우도 있기 때문에 null을 반환하는 경우를 생각해야 합니다.
Optional을 사용하면 null을 반환해도 오류가 생기지 않습니다.
(사실 Optional에 대해서는 조금 더 깊숙히 알아야 할 필요가 있는데, 아직 제가 Optional을 잘 몰라서 이정도만 설명하겠습니다. 나중에 기회가 된다면 Optional에 대해서 글을 써보겠습니다....... -- 안 할 듯)
자 여기까지 작성을 완료했음니, 만든 메서드가 제대로 작동하는지 확인하고 싶습니다.
근데 저거 말고 따로 만들어놓은게 없으니 당장 확인할 길이 없습니다.
그렇다고 화면단을 따로 만들기에는 시간이 오래걸립니다.(그리고 그걸 빌드 하는데에도 조금 걸립니다.)
이 때 Test Code를 사용하면 좋습니다.
src 밑에는 main과 test로 구분되어 있습니다.
이제 test 밑에 폴더로 가서 저렇게 클래스를 하나 만들어봅니다.
import static org.assertj.core.api.Assertions.*;
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// System.out.println("result = " + (result==member));
// Assertions.assertEquals(result, null);
assertThat(member).isEqualTo(result);
}
}
그리고 먼저 save를 테스트 해보는 코드를 만들어 보겠습니다.
(assertThat을 썼을 때 성공하면 초록색으로, 실패하면 빨간색으로 뜹니다.)
아마 이런 식으로 코드가 보일텐데 빨간색으로 동그라미를 친 부분을 눌러보면
테스트 코드를 Run할 수 있습니다. Run을 눌러봅니다.
그럼 이렇게 결과가 뜹니다. 초록색으로 떴으니 성공한 것입니다.
만약
assertThat(member).isEqualTo(null); 이라고 바꾼 후 다시 Run을 해본다면
실패했다고 뜰 것입니다.
Assertions.assertEquals(result, member);
Assertions를 쓰면 결과가 맞는지 아닌지 확인할 수 있게 합니다.
참고로 Assertions 뒤에 assertEqauls를 쓰려면 org.junit.jupiter.api를 선택해야 쓸 수 있고,
Assertions.assertThat으로 쓰려면 org.assertj.core.api를 선택해야 사용할 수 있습니다.
Assert 관련해서는 조금 더 검색하는 것을 추천드립니다. (저도 assert에 대해서 잘 몰라서 이정도만 씁니다...)
그럼 나머지 테스트 코드 작성도 해보겠습니다.
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// System.out.println("result = " + (result==member));
org.junit.jupiter.api.Assertions.assertEquals(result, member);
// assertThat(member).isEqualTo(null);
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring2").get();
assertThat(result).isEqualTo(member2);
}
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member member3 = new Member();
member3.setName("spring3");
repository.save(member3);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(3);
}
}
이제 각각의 Test 코드들을 Run 해보면 정상적으로 동작한다는 것을 알 수 있습니다.
근데 지금은 테스트 코드가 적지만, 테스트 코드가 10개만 되도 일일이 테스트를 눌러보는 것이 귀찮을 것입니다.
그럴 때는
테스트 클래스 자체를 Run 하면 됩니다.
이제 Run 해봅니다. 그러면...
오류가 뜨는 것을 확인할 수 있습니다....
보면 테스트가 제가 짠 순서대로 진행이 안되는 것을 알 수 있습니다.
findAll()이 가장 먼저 실행이 되고 있습니다.
그런 다음에 findByName를 실행하면, 이전에 저장한 것이 그대로 유지가 되기 때문에
어떤 메소드는 멀쩡하지만, 결과는 실패로 나올 수도 있습니다.
이런 것을 대비하기 위해서는 각 테스트가 끝날 때마다 저장된 이력을 싹 지워야할 필요가 있습니다.
그래서 다음의 코드를 추가합니다.
public class MemoryMemberRepositoryTest {
MemoryMemberRepository repository = new MemoryMemberRepository();
@AfterEach // Test가 하나 끝날때 마다 repository를 비워줌
public void afterEach() {
repository.clearStore();
}
@Test
public void save() {
Member member = new Member();
member.setName("spring");
repository.save(member);
Member result = repository.findById(member.getId()).get();
// System.out.println("result = " + (result==member));
org.junit.jupiter.api.Assertions.assertEquals(result, member);
// assertThat(member).isEqualTo(null);
}
@Test
public void findByName() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member result = repository.findByName("spring2").get();
assertThat(result).isEqualTo(member2);
}
@Test
public void findAll() {
Member member1 = new Member();
member1.setName("spring1");
repository.save(member1);
Member member2 = new Member();
member2.setName("spring2");
repository.save(member2);
Member member3 = new Member();
member3.setName("spring3");
repository.save(member3);
List<Member> result = repository.findAll();
assertThat(result.size()).isEqualTo(3);
}
}
afterEach라는 메소드를 만들었습니다.
그리고 어노테이션으로 @AfterEach를 넣었습니다.
이렇게 하면 각 테스트가 끝날 때마다 afterEach라는 메소드가 실행됩니다.
메소드의 내용은 저장된 이력을 싹 삭제하는 내용입니다.
이제 클래스 테스트를 다시 Run한다면
모든 테스트가 제대로 작동하는 것을 알 수 있습니다.
이렇게 테스트 코드 작성하는 법에 대해 알아봤습니다.
테스트 코드 작성을 단순하게 생각하면 생각보다는 쉽습니다.
물론 여러 어노테이션을 생각해본다면 무조건 쉽다고 할 수는 없습니다.
테스트 코드 작성을 위해 더 공부해야할 필요성도 있습니다.
어쨌든 이렇게 Spring에서 Test 코드 작성하는 방법에 대해 알아봤습니다.
'Spring' 카테고리의 다른 글
Spring - DI(의존성 주입) (0) | 2020.09.20 |
---|---|
Spring - Test Code 작성(2) (0) | 2020.09.13 |
Spring - static (0) | 2020.09.06 |
Spring - textarea 관련 (0) | 2020.06.23 |
Spring-JPA 활용(2) (0) | 2020.06.09 |