개념, 대역

Gyeongjae Ham·2023년 6월 18일
0

TEST

목록 보기
2/7
post-thumbnail

이 시리즈는 TDD를 숙달하기 전에 TEST 자체에 대한 이해를 높이기 위한 학습 시리즈입니다

SUT

  • System under test: 테스트하려는 대상을 뜻합니다
@Test
void 유저는_북마크를_toggle_추가_할_수_있다() {
	// given
    User sut = User.builder()
    			.bookmark(new ArrayList<>())
                .build();

	// when
    sut.toggleBookmark("my-link");
    
    // then
    boolean result = user.hasBookmark("my-link");
    assertThat(result).isTrue();

BDD

  • Behaviour driven development(given-when-then)
  • 테스트를 작성하다 보면 어느 순간 '어디에 어떻게 테스트를 넣어야 하지?'라는 질문을 마주하게 됩니다. 이 질문에 대한 답이 바로 BDD입니다
    • 행동에 집중해라
    1. 어떤 상황이 주어졌을 때(given)
    2. 이 행동을 하면(when)
    3. 결과가 이렇다(then)

상호 작용 테스트(Interaction test)

  • 대상 함수의 구현을 호출하지 않으면서 그 함수가 어떻게 호출되는지를 검증하는 기법
  • 캡슐화에 위배되는 테스트라는 생각도 있습니다(개개인마다 의견이 다른 부분입니다)
@Test
void 유저는_북마크를_toggle_추가_할_수_있다() {
	// given
    User sut = User.builder()
    			.bookmark(new ArrayList<>())
                .build();

	// when
    sut.toggleBookmark("my-link");
    
    // then
    boolean result = user.hasBookmark("my-link");
    assertThat(result).isTrue();
    
    // Interaction test
    verify(sut).markModified();

상태 기반 검증(state-based-verification)

  • 어떤 값을 시스템에 넣었을 때 나오는 결과값을 기대값과 비교하는 방식

행위 기반 검증(behaviour-based-verification)

  • 어떤 값을 시스템에 넣었을 때 협력 객체의 어떤 메서드를 실행하는가

테스트 픽스처

  • 테스트에 필요한 자원을 생성하는 것
private User sut;

@BeforeEach
void 사용자를_미리_할당합니다() {
	sut = User.builder()
			.bookmark(new ArrayList<>())
            .build();
            
@Test
void 유저는_북마크를_toggle_삭제_할_수있다() {
	// given
    sut.appendBookmark("my-link");
    
    // when
    sut.toggleBookmark("my-link");
    
	// then
    assertThat(sut.hasBookmark("my-link")).isTrue();
    verify(sut).removeBookmark; // Interaction test

비욘세 규칙

  • 유지하고 싶은 상태나 정책이 있다면, 알아서 테스트를 만들어야 한다

Test Double

  • 회원가입에 이메일 발송이 필요하다면?
    • 테스트를 돌릴때마다 진짜 이메일을 전송하는 건 말이 안되므로
    • 가짜 객체(테스트 대역)을 두는 것을 말합니다
@Test
public void 이메일_회원가입을_할_수_있다() {
	// given
    UserCreateRequest userCreateRequest = UserCreateRequest.builder()
    										.email("foo@localhost.com")
                                            .password("123445")
                                            .build();
                                            
	// when
    UserService sut = UserService.builder()
    					.registerEmailSender(new DummyEmailSender())
                        .userRepository(userRepository)
                        .build();
	sut.register(userCreateRequest);
    
    // then
    User user = userRepository.getByEmail("foo@localhost.com");
    assertThat(user.isPending()).isTrue();
}

Test Double(대역)의 종류

dummy

  • 아무런 동작도 하지 않고, 그저 코드가 정상적으로 돌아가기 위해 전달하는 객체
  • 위 예제에서 봤던 이메일센더의 대역인 DummyEmailSender가 아무런 동작도 하지 않는 dummy의 종류입니다
  • 저 객체를 보면 내용은 아무것도 없을 겁니다
  • 결과 부분에서 사용자가 메일 인증을 대기하고 있는 상태인지 검증만 하게 해주는 객체
class DummyEmailSender implements RegisterEmailSender {
	@override
    public void send(String email, String message) {
    	// do nothing
    }
}

fake

  • dummy와 다르게 자체 로직을 가지고 있다는 게 특징입니다
  • Local에서 사용하거나 테스트에서 사용하기위해 만들어진 가짜 객체

fake

class FakeRegisterEmailSender implements RegisterEmailSender {
	private final Map<String, List<String>> latestMessages = new HashMap<>();
    
    @override
    public void send(String email, String message) {
    	List<String> records = latestMessages.getOrDefault(email, new ArrayList<>());
        records.add(message);
        latestMessages.put(email, records);
    }
    
    public Opional<String> findLatestMessage(String email) {
    	return latestMessages.getOrDefault(email, new ArrayList<>())
        		.stream()
                .findFirst();
    }
}

fake를 사용한 테스트 코드

@Test
public void 이메일_회원가입을_할_수_있다() {
	// given
    UserCreateRequest userCreateRequest = UserCreateRequest.builder()
    										.email("foo@localhost.com")
                                            .password("123445")
                                            .build();
	FakeRegisterEmailSender registerEmailSender = new FakeRegisterEmailSender();
                                            
	// when
    UserService sut = UserService.builder()
    					.registerEmailSender(registerEmailSender)
                        .userRepository(userRepository)
                        .build();
	sut.register(userCreateRequest);
    
    // then
    User user = userRepository.getByEmail("foo@localhost.com");
    assertThat(user.isPending()).isTrue();
    assertThat(registerEmailSender.findLatestMessage("foo@localhost.com")
    				.isPresent()).isTrue();
 	assertThat(registerEmailSender.findLatestMessage("foo@localhost.com")
    				.get()).isEqualTo("~~~");
  • 위 예제에서 fake 대역은 메시지를 latestMessages에 들고 있는 로직을 구현하고 있습니다
  • 덕분에 테스트에서 호출을 통해 어떤 메시지인지 확인할 수 있습니다

stub

  • 미리 준비된 값을 출력하는 객체
  • 주로 외부 연동하는 컴포넌트들에 많이 사용합니다
  • 어떤 행동을 시켰을 때 미리 준비된 값을 내려주는 용도로 사용합니다
class StubUserRepository implements UserRepository {
	public User getByEmail(String email) {
    	if (email.equals("foo@localhost.com")) {
        	return User.builder()
            		.email("foo@localhost.com")
                    .status("PENDING")
                    .build();
        }
        throw new UsernameNotFoundException(email);
    }
}

  • 위와 같이 구현할 수도 있지만 보통은 mockito 프레임워크를 사용해서 구현합니다
// given
given(userRepository.getByEmail("foo@bar.com")).willReturn(User.builder()
						.email("foo@bar.com")
                        .status("PENDING")
                        .build());
// when
// do something

// then
// result something

mock

  • 메소드 호출을 확인하기 위한 객체, 자가 검증 능력을 갖췄습니다
  • 사실상 요즘은 테스트 더블과 동일한 의미로 사용되어 dummy, fake, stub 등도 모두 mock이라고 칭해지고 있습니다

spy

  • 모든 메서드의 호출을 낱낱이 기록해두고 있는 객체를 칭합니다
  • 메서드가 몇번 호출됐는지, 잘 호출됐는지 등을 검증합니다
profile
Always be happy 😀

0개의 댓글