Inflearn brand logo image

인프런 커뮤니티 질문&답변

신예찬님의 프로필 이미지
신예찬

작성한 질문수

Spring Boot TDD - 입문부터 실전까지 정확하게

보안 구성 추상화

거짓 양성 감지 노하우 질문입니다

작성

·

597

4

안녕하세요 규원님. 강의 잘 듣고 있습니다.

 

제가 실습 코드를 따라하다가 오탈자가 생겨 이를 해결하는 과정에서 의문점이 생겨 질문 남깁니다.

 

shopper 토큰 발급 엔드포인트 구현 과정에서 @RequestBody 어노테이션을 누락했습니다

@PostMapping("/shopper/issueToken")
ResponseEntity<?> issueToken(IssueShopperToken query) {
    return repository.findByEmail(query.email())
            .map(shopper -> composeToken())
            .map(AccessTokenCarrier::new)
            .map(ResponseEntity::ok)
            .orElseGet(() -> ResponseEntity.badRequest().build());
}


문제는 이런 상황에서 400이 발생하며 실패해야할 테스트 코드가 통과하게 됩니다.

@Test
void 잘못된_비밀번호가_사용되면_400_Bad_Request_상태코드를_반환한다(
    @Autowired TestRestTemplate client
){
    // Arrange
    var email = generateEmail();
    var wrongPassword = generatePassword();
    var password = generatePassword();

    client.postForEntity(
        "/shopper/signUp",
        new CreateShopperCommand(email, generateUsername(), password),
        Void.class
    );

    // Act
    ResponseEntity<AccessTokenCarrier> response = client.postForEntity(
        "/shopper/issueToken",
        new IssueShopperToken(email, wrongPassword),
        AccessTokenCarrier.class
    );

    // Assert
    assertThat(response.getStatusCode().value()).isEqualTo(400);
}

이유를 고민해보고 다음과같은 결론을 짓게 되었습니다.

  • @RequestBody가 없는 경우 컨트롤러 메서드의 매개변수는 기본적으로 form data로 인식하게 된다.

  • 따라서 IssueShopperToken이 form data로 인식되게 된다.

  • 현재 테스트에서는 상태코드가 400인지만을 확인한다.

  • 하지만 form data가 없는 경우에도 400이 발생한다.

테스트가 통과한다.

 

 

거짓 양성의 사례라고 보여집니다.

 

이경우에는 Assert절을 강화해 상태코드 말고도 검증되어야할 항목들을 추가해 거짓 양성을 방지할 수 있을거라고 생각됩니다.

 

이렇듯 테스트코드도 사람이 작성하다 보니 Assert절을 어느정도 수준까지 구체적으로 작성해야 할지를 TDD가 익숙하지 않다면 빠르게 식별하기 어렵다고 생각합니다.

실무에서는 이와같은 상황은 매우 치명적일 수 있을거 같고요.


그래서 실무에서 필요한 Assert절을 구체화하는 명확한 기준이나 노하우가 있으신지가 궁금합니다!

답변 2

2

이규원님의 프로필 이미지
이규원
지식공유자

예찬님 안녕하세요. 강의 수강해주셔서 감사드립니다. :)

좋은 질문이라고 생각합니다! 실수에서 비롯된 것 같기는 하지만 좋은 경험을 하신 것 같아요. 최대한 이해하실 수 있도록 답변해 보겠습니다.


먼저 가볍게 용어 사용을 바로 잡아보면, 테스트가 통과했으니 '거짓 양성'은 아니고 음성의 일종입니다.

  • 양성: 테스트 실패

  • 음성: 테스트 통과


그러면 이제 예찬님이 @RequestBody를 누락해서 테스트를 통과한 것이 '참 음성'인지 '거짓 음성'인지 생각을 해보죠.

거짓 음성이라면 테스트는 통과했지만 운영 환경에서는 테스트 통과 기준과 다른 결과가 있을 거에요. 여기서 다른 결과라면,

assertThat(response.getStatusCode().value()).isEqualTo(400);

코드를 통과하지 못할 결과여야 하니까 상태 코드가 400이 아니어야 합니다.

하지만 제 예상으로는 운영 환경에서도 잘못된 비밀번호를 입력했을 때 400 상태 코드가 반환될 것 같습니다. 즉, 테스트 환경과 운영 환경의 결과가 다르지 않은 것이죠.

그렇다면 이 테스트 통과는 '거짓 음성'이 아니라 '참 음성'입니다.


그런데 예찬님은(그리고 아마도 대부분의 다른 프로그래머들도) 구현에 문제가 있다고 느끼실 거에요. 그래서 단언(assertion)문이 부족한 것이 아닌가 하고 고민하시는 거고요. 정말 무엇인가 부족한 건지 살펴보겠습니다.

 

구매자 접근 토큰 발행 기능에는 다음 시나리오가 있습니다.

  1. 올바르게 요청하면 200 OK 상태코드와 접근 토큰을 반환한다

  2. 접근 토큰은 JWT 형식을 따른다

  3. 존재하지 않는 이메일 주소가 사용되면 400 Bad Request 상태코드를 반환한다

  4. 잘못된 비밀번호가 사용되면 400 Bad Request 상태코드를 반환한다

IssueShopperToken query 매개변수에서 @RequestBody 애노테이션을 제거하고 판매자 접근 토큰 발행 테스트를 실행하면 결과는 이렇습니다.

image.png

위 테스트 시나리오 목록에서 1번과 2번을 만족하지 못합니다.

그러니까 @RequestBody 애노테이션은 4번 시나리오 만족을 위해 필요한 코드라기 보다는, 4개 테스트 시나리오를 모두 만족시키기 위해 필요한 코드입니다. 그렇다면 아직은 누락된 테스트 시나리오가 있다고 판단할 근거는 발견하지 못한 것이고요, 테스트가 잘 동작하고 있으니 아직 우리는 치명적인 상황에 놓인 것 같지는 않습니다.


정리하면, 잘못된_비밀번호가_사용되면_400_Bad_Request_상태코드를_반환한다 테스트는 통과해야 할 테스트가 통과한다고 할 수 있겠고요. @RequestBody 애노테이션 누락에 대해서는 올바르게_요청하면_200_OK_상태코드와_접근_토큰을_반환한다 테스트와 접근_토큰은_JWT_형식을_따른다 테스트가 실패해서 우리에게 알려줍니다.

 

구현 코드에서 @RequestBody 애노테이션 빠졌는지 보다는 준비한 테스트 시나리오가 충분한지, 그리고 시스템이 이 시나리오들을 모두 만족시키는지에 더 관심을 가져보면 어떨까요? 구현 코드인 @RequestBody 애노테이션에 집중해서 테스트 코드를 작성하다가는 테스트가 내부 구현에 의존해서 설계가 경직되고, 또 오히려 거짓 음성이 생겨날 수도 있습니다.

 

만약에, @RequestBody 애노테이션을 제거했는데도 모든 테스트를 통과하고, 누락된 시나리오가 있다는 근거도 없고, 사용자도 아무 불편함 없이 시스템을 사용하고 있다고 가정을 한다면, 그건 테스트에 문제가 있는 것이 아니라 @RequestBody 애노테이션이 불필요한 코드입니다.

신예찬님의 프로필 이미지
신예찬
질문자

답변 감사합니다.


그럼 "현재 테스트는 제 역할을 다 하고 있으며, 테스트 시나리오 커버리지를 촘촘하게 하는것이 애플리케이션 신뢰성을 높히는 방향이다." 라고 이해해도 될까요?

이규원님의 프로필 이미지
이규원
지식공유자

네, 저는 말씀하신 것과 동일하게 생각합니다. 어떤 요구사항(테스트 시나리오)들을 만족하면 애플리케이션의 올바른 동작을 신뢰할 수 있을지 고민하는 연습을 하면 안정적인 개발에 도움이 됩니다. 그리고 이런 연습은 AI에게 올바른 지시를 하는 데에도 큰 도움이 됩니다.

신예찬님의 프로필 이미지
신예찬
질문자

좋은 답변 감사합니다!

0

안녕하세요, 인프런 AI 인턴입니다. 신예찬님.

테스트 코드에서 발생한 거짓 양성(False Positive)은 테스트의 Assert 절이 충분히 구체적이지 않아서 발생할 수 있습니다. 이 문제를 해결하기 위해서는 검증 절차를 강화하여 문제를 정확히 파악해야 합니다. 테스트 코드에서 Assert 절을 얼마나 구체적으로 작성할 것인지를 결정할 때 도움이 되는 몇 가지 원칙과 노하우를 공유드리겠습니다:

  1. 명확한 요구사항 정의: 테스트를 작성할 때, 해당 기능이 어떤 요구사항을 만족해야 하는지를 명확히 정의하는 것이 중요합니다. 단순히 상태 코드만 확인하는 것이 아니라, 반응해야 할 특정 오류 메시지나 데이터 구조 등을 함께 검토해야 합니다.

  2. 다양한 시나리오 테스트: 가능하면 다양한 경로와 예외 사항을 테스트에 포함시켜야 합니다. 예를 들어, 입력 값이 올바르지만 요청 방식이 잘못된 경우와 같은 예외적인 상황도 테스트에 포함하여 거짓 양성을 막을 수 있습니다.

  3. 상태 코드 외의 추가 검증: 상태 코드만 검증하는 것이 아니라, 응답 내용(response body)나 헤더 등도 함께 검증하는 것이 바람직합니다. 요청에 대한 오류 메시지나 특정한 데이터 형태가 반환되는지를 확인하는 것이 좋습니다.

  4. 실제 데이터와 비교: 테스트에서 사용되는 데이터가 실제 운영 환경에서 사용되는 데이터와 동일한지 확인합니다. 이로 인해 발생할 수 있는 예상치 못한 문제를 미리 발견할 수 있습니다.

  5. 일관성 있는 테스트 구조: 통일된 구조를 사용하여 작성할 경우, 코드를 유지보수하기 쉬워집니다. 이는 테스트 코드의 가독성을 높이고, 테스트 코드 내의 일관성 있는 로직 검증을 통해 디버깅을 쉽게 할 수 있게 해줍니다.

관련해서 다른 개발자들이 공유한 실무 사례를 찾아보실 수 있습니다. 아래의 링크에서 TDD와 관련된 다양한 노하우와 실질적인 예시를 참고하시기 바랍니다:

위 링크에서는 프로젝트의 규모가 클 때 테스트 실행을 최적화하는 방법에 대해 논의하지만, 그 맥락에서 테스트 케이스를 더욱 세분화하고 효과적으로 관리하는 방법도 알아볼 수 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

신예찬님의 프로필 이미지
신예찬

작성한 질문수

질문하기