programing

데이터베이스 기반 애플리케이션을 유닛 테스트하는 가장 좋은 전략은 무엇입니까?

telecom 2023. 11. 6. 21:38
반응형

데이터베이스 기반 애플리케이션을 유닛 테스트하는 가장 좋은 전략은 무엇입니까?

저는 백엔드의 다양한 복잡성을 가진 데이터베이스에 의해 구동되는 많은 웹 애플리케이션을 사용합니다.일반적으로 비즈니스 및 프레젠테이션 로직과는 별개의 ORM 계층이 있습니다.이를 통해 비즈니스 로직을 단위 테스트하는 작업은 매우 간단해집니다. 개별 모듈로 구현할 수 있으며 테스트에 필요한 모든 데이터는 개체 조롱을 통해 위조될 수 있습니다.

그러나 ORM과 데이터베이스 자체를 테스트하는 것은 항상 문제와 타협으로 가득 차 있었습니다.

지난 몇 년간 몇 가지 전략을 시도해 보았지만, 그 어느 것도 저를 완전히 만족시키지 못했습니다.

  • 테스트 데이터베이스에 알려진 데이터를 로드합니다.ORM에 대한 테스트를 실행하고 올바른 데이터가 돌아오는지 확인합니다.여기서 단점은 테스트 DB가 응용프로그램 데이터베이스의 스키마 변경 내용을 따라가야 하고 동기화되지 않을 수 있다는 것입니다.또한 인공 데이터에 의존하며, 바보 같은 사용자 입력으로 인해 발생하는 버그를 노출하지 않을 수 있습니다.마지막으로, 테스트 데이터베이스가 작으면 누락된 인덱스와 같은 비효율이 나타나지 않습니다. (좋아요, 마지막 테스트는 실제로 단위 테스트에 사용해야 하는 것은 아니지만, 아무런 문제가 되지 않습니다.)

  • 생산 데이터베이스의 복사본을 로드하고 이에 대해 테스트합니다.여기서 문제가 되는 것은 언제라도 운영 DB에 무엇이 있는지 모를 수 있다는 것입니다. 시간이 지남에 따라 데이터가 변경되면 테스트를 다시 작성해야 할 수도 있습니다.

어떤 사람들은 이 두 가지 전략이 모두 특정 데이터에 의존하며 단위 테스트는 기능성만 테스트해야 한다고 지적했습니다.이를 위해 다음과 같이 제안했습니다.

  • 모의 데이터베이스 서버를 사용하고 ORM이 지정된 메서드 호출에 대한 응답으로 올바른 쿼리를 보내고 있는지만 확인합니다.

데이터베이스 기반 애플리케이션을 테스트하기 위해 사용한 전략은 무엇입니까?무엇이 당신에게 가장 효과적이었습니까?

저는 실제로 귀사의 첫 번째 접근 방식을 상당히 성공적으로 사용했지만, 약간 다른 방식으로 귀사의 문제를 해결할 수 있을 것으로 생각합니다.

  1. 체크아웃 후 누구나 현재 데이터베이스 스키마를 작성할 수 있도록 전체 스키마와 작성 스크립트를 소스 제어에 보관합니다.또한 빌드 프로세스의 일부로 로드되는 데이터 파일에 샘플 데이터를 보관합니다.오류를 유발하는 데이터를 발견하면 샘플 데이터에 추가하여 오류가 다시 나타나지 않는지 확인합니다.

  2. 연속 통합 서버를 사용하여 데이터베이스 스키마를 구축하고 샘플 데이터를 로드하며 테스트를 실행합니다.이렇게 하면 테스트 데이터베이스를 동기화할 수 있습니다(테스트 실행 시마다 재구축).이를 위해서는 CI 서버가 자체 전용 데이터베이스 인스턴스에 대한 액세스 및 소유권을 가져야 하지만, db 스키마를 하루에 3번 구축하는 것이 전달 직전까지 발견되지 않았을 수도 있는 오류를 찾는 데 큰 도움이 되었다고 생각합니다(나중에 발견되지 않았다면).모든 커밋 전에 스키마를 재구축한다고 말할 수는 없습니다.아무도 없습니까?이 접근 방식을 사용하면 그럴 필요가 없습니다(글쎄요, 그래야 할지도 모르지만 누군가 잊어버린다면 큰 문제가 아닙니다).

  3. 제 그룹의 경우, 사용자 입력은 (db가 아닌) 어플리케이션 레벨에서 이루어지므로 표준 단위 테스트를 통해 테스트됩니다.

프로덕션 데이터베이스 복사본 로드 중:
이것이 제 지난 직장에서 사용된 방법이었습니다.이는 몇 가지 문제로 인한 엄청난 고통의 원인이었습니다.

  1. 복사본이 프로덕션 버전에서 오래되었습니다.
  2. 복사본의 스키마가 변경되고 프로덕션 시스템에 전파되지 않습니다.이 시점에서 우리는 서로 다른 도식을 갖게 될 것입니다.재미없어요.

모킹 데이터베이스 서버:
우리는 지금 직장에서도 이 일을 합니다.매 커밋 후에 모의 DB 접근자가 주입된 응용 프로그램 코드에 대한 단위 테스트를 실행합니다.그런 다음 우리는 위에 설명한 전체 db 빌드를 하루에 세 번 실행합니다.저는 두 가지 방법을 모두 추천합니다.

이러한 이유로 항상 인메모리 DB(HSQLDB 또는 Derby)에 대한 테스트를 실행하고 있습니다.

  • 테스트 DB에 어떤 데이터를 보관하고 그 이유를 생각하게 합니다.프로덕션 DB를 테스트 시스템으로 끌어오기만 해도 "내가 무엇을 하고 있는지, 왜 그런지, 그리고 무언가가 고장나면, 그건 내가 아니었다!"로 해석됩니다. ;)
  • 데이터베이스를 새로운 장소에서 별도의 노력 없이 재작성할 수 있습니다(예: 운영 환경에서 버그를 복제해야 할 때).
  • DDL 파일의 품질에 큰 도움이 됩니다.

인메모리 DB는 테스트가 시작되면 새로 로드되고 대부분의 테스트가 끝나면 롤백을 실행하여 안정성을 유지합니다.항상 테스트 DB의 데이터를 안정적으로 유지합니다!데이터가 항상 바뀌면 테스트할 수 없습니다.

데이터는 SQL, 템플릿 DB 또는 덤프/백업에서 로드됩니다.저는 덤프를 VCS에 넣을 수 있기 때문에 읽을 수 있는 형식이면 더 좋습니다.안 되면 CSV 파일이나 XML을 사용합니다. 엄청난 양의 데이터를 로드해야 한다면...없어.절대로 엄청난 양의 데이터를 로드할 필요가 없습니다 :) 단위 테스트용이 아닙니다.성능 테스트는 또 다른 문제이며 다양한 규칙이 적용됩니다.

어떤 식으로든 데이터베이스를 조롱할 수 있는 도구(예: jOOQ's, 이 답변에서 볼 수 있는 면책 사항, jOOQ의 공급업체에서 근무)가 있더라도 복잡한 질의가 있는 더 큰 데이터베이스를 조롱하지 말 것을 조언합니다.

ORM을 통합 테스트하고 싶더라도 ORM은 데이터베이스에 매우 복잡한 일련의 쿼리를 발행하며, 이는 다음과 같이 달라질 수 있습니다.

  • 통사론
  • 복잡성
  • 주문(!)

전송된 SQL 문을 해석하는 모의실험에서 실제로 데이터베이스를 약간 구축하지 않는 한, 합리적인 더미 데이터를 생성하기 위해 이 모든 것을 모의실험하는 것은 매우 어렵습니다.그렇다면 잘 알려진 데이터로 쉽게 재설정할 수 있는 잘 알려진 통합 테스트 데이터베이스를 사용하여 통합 테스트를 실행할 수 있습니다.

오래전부터 이 질문을 했지만, 그것에 대한 은탄은 없는 것 같습니다.

현재 제가 하는 일은 DAO 객체를 조롱하고 데이터베이스에 살 수 있는 흥미로운 데이터 사례를 나타내는 좋은 객체 모음을 메모리에 저장하는 것입니다.

이 접근 방식의 가장 큰 문제는 DAO 계층과 상호 작용하는 코드만 다루고 DAO 자체는 테스트하지 않는다는 것입니다. 제 경험으로는 해당 계층에서도 오류가 많이 발생한다는 것을 알 수 있습니다.또한 TDD를 사용하거나 로컬에서 빠른 테스트를 수행하기 위해 데이터베이스에 대해 실행되는 몇 가지 단위 테스트를 유지하고 있지만, 이러한 테스트는 지속적인 통합 서버에서 실행되지 않습니다. 이러한 목적을 위해 데이터베이스를 유지하지 않으며 CI 서버에서 실행되는 테스트는 자체적으로 포함되어야 한다고 생각하기 때문입니다.

매우 흥미롭지만 시간이 많이 걸리기 때문에 항상 가치가 있는 것은 아닌 또 다른 접근 방식은 유닛 테스트 내에서 실행되는 내장형 데이터베이스에서 프로덕션에 사용하는 동일한 스키마를 만드는 것입니다.

이러한 접근 방식이 적용 범위를 개선하는 데는 의심의 여지가 없지만, 현재 DBMS와 내장형 대체 기능을 모두 사용하기 위해서는 ANSI SQL에 최대한 가까이 접근해야 하므로 몇 가지 단점이 있습니다.

코드에 더 적합하다고 생각하는 프로젝트가 무엇이든 간에, DbUnit과 같이 더 쉽게 만들 수 있는 프로젝트가 몇 개 있습니다.

첫 번째(테스트 데이터베이스에 대한 코드 실행)를 사용합니다.이 접근 방식에서 제기하는 유일한 실질적인 문제는 스키마가 동기화되지 않을 가능성입니다. 이 문제는 버전 번호를 데이터베이스에 유지하고 각 버전 증가에 대한 변경 사항을 적용하는 스크립트를 통해 모든 스키마를 변경하는 방식으로 해결합니다.

또한 테스트 환경에 대한 모든 변경(데이터베이스 스키마 포함)을 먼저 수행하기 때문에 그 반대가 됩니다. 모든 테스트가 통과되면 운영 호스트에 스키마 업데이트를 적용합니다.또한 실제 프로덕션 박스를 터치하기 전에 DB 업그레이드가 제대로 작동하는지 확인할 수 있도록 별도의 테스트 대 애플리케이션 데이터베이스 쌍을 개발 시스템에 보관합니다.

저는 첫 번째 접근 방식을 사용하고 있지만 말씀하신 문제를 해결할 수 있는 약간의 차이가 있습니다.

DAO에 대한 테스트를 실행하는 데 필요한 모든 것은 소스 제어에 있습니다.DB를 만들기 위한 스키마와 스크립트가 포함되어 있습니다(도커는 이에 매우 적합합니다).내장된 DB를 사용할 수 있다면 - 저는 속도를 위해 사용합니다.

설명된 다른 접근 방식과 중요한 차이점은 테스트에 필요한 데이터가 SQL 스크립트나 XML 파일에서 로드되지 않는다는 것입니다.모든 것(일부 효과적으로 일정한 사전 데이터 제외)은 유틸리티 함수/클래스를 사용하여 응용 프로그램에서 생성됩니다.

주된 목적은 테스트에서 사용하는 데이터를 만드는 것입니다.

  1. 시험에 아주 근접한
  2. 명시적(데이터에 SQL 파일을 사용하면 어떤 데이터가 어떤 테스트에 사용되는지 확인하는 것이 매우 문제가 됩니다.)
  3. 테스트를 관련 없는 변경 사항으로부터 분리합니다.

기본적으로 이러한 유틸리티는 테스트 자체에서 테스트에 필수적인 것만을 선언적으로 명시하고 관련성이 없는 것은 생략할 수 있도록 한다는 것을 의미합니다.

실제로 어떤 의미를 갖는지 이해하기 위해, 어떤 DAO 테스트를 고려해 보십시오.Comment에 대하여Post에 의해서 쓰여진Authors. 이러한 DAO에 대한 CRUD 작업을 테스트하기 위해서는 DB에 일부 데이터를 생성해야 합니다.테스트는 다음과 같습니다.

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

테스트 데이터가 있는 SQL 스크립트나 XML 파일에 비해 몇 가지 이점이 있습니다.

  1. 코드를 유지하는 것이 훨씬 더 쉽습니다(예를 들어 Author와 같이 많은 테스트에서 참조되는 일부 엔티티에서 필수 열을 추가하는 것은 많은 파일/레코드를 변경할 필요가 없으며 빌드 및/또는 공장에서만 변경할 수 있습니다).
  2. 특정 테스트에서 필요한 데이터는 다른 파일이 아닌 테스트 자체에 설명되어 있습니다.이러한 근접성은 시험 이해성에 매우 중요합니다.

롤백 대 커밋

저는 테스트가 실행될 때 커밋하는 것이 더 편리하다고 생각합니다.첫째, 몇 가지 효과(예:DEFERRED CONSTRAINTS) 커밋이 발생하지 않으면 확인할 수 없습니다.둘째, 테스트 실패 시 롤백에 의해 데이터가 반환되지 않으므로 DB에서 데이터를 검사할 수 있습니다.

왜냐하면 이는 테스트에서 데이터가 깨질 수 있고 다른 테스트에서 실패할 수 있다는 단점이 있기 때문입니다.이 문제를 해결하기 위해 검사를 분리하려고 합니다.위의 예에서 모든 테스트는 새로운 것을 만들 수 있습니다.Author그리고 다른 모든 개체들은 이와 연관되어 생성되기 때문에 충돌이 거의 발생하지 않습니다.잠재적으로 깨질 수 있지만 DB 수준 제약 조건으로 표현할 수 없는 나머지 불변량을 처리하기 위해 저는 매 테스트 후 실행될 수 있는 잘못된 조건에 대해 몇 가지 프로그래밍 검사를 사용합니다(CI에서 실행되지만 성능상의 이유로 대개 로컬에서 꺼짐).

JDBC 기반 프로젝트(예: 직접 또는 간접적으로 JPA, EJB, ...)의 경우 전체 데이터베이스(실제 RDBMS에서 테스트 db를 사용하는 것이 더 좋습니다)가 아니라 JDBC 수준에서만 mockup할 수 있습니다.

JDBC 데이터(결과 집합, 업데이트 횟수, 경고, ...)는 백엔드가 무엇이든 동일하므로, 추상화는 이러한 방식으로 제공됩니다. 즉, prod db, test db 또는 각 테스트 사례에 대해 제공되는 일부 mockup 데이터입니다.

각 경우마다 JDBC 연결을 모의평가하므로 테스트 DB를 관리할 필요가 없습니다(정리, 한 번에 하나의 테스트만 가능, 고정 장치 다시 로드 등).모든 목업 연결부는 분리되어 있으므로 정리할 필요가 없습니다.JDBC 교환을 모의평가하기 위해 각 테스트 케이스에 필요한 최소한의 고정 장치만 제공되므로 전체 테스트 DB를 관리하는 복잡함을 방지할 수 있습니다.

이런 종류의 목업을 위한 JDBC 드라이버와 유틸리티를 포함한 나의 프레임워크는 다음과 같습니다. http://acolyte.eu.org .

언급URL : https://stackoverflow.com/questions/145131/whats-the-best-strategy-for-unit-testing-database-driven-applications

반응형