단위 테스트 데이터베이스 기반 응용 프로그램에 가장 적합한 전략은 무엇입니까?


346

백엔드에서 다양한 복잡성의 데이터베이스로 구동되는 많은 웹 응용 프로그램을 사용합니다. 일반적으로 비즈니스 및 프리젠 테이션 로직과 분리 된 ORM 계층이 있습니다. 이것은 비즈니스 로직의 단위 테스트를 매우 간단하게 만듭니다. 개별 모듈로 구현할 수 있으며 테스트에 필요한 모든 데이터는 객체 조롱을 통해 위조 될 수 있습니다.

그러나 ORM 및 데이터베이스 자체를 테스트하는 데 항상 문제와 타협이있었습니다.

몇 년 동안, 나는 몇 가지 전략을 시도했지만 그중 어느 것도 나를 완전히 만족시키지 못했습니다.

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

  • 프로덕션 데이터베이스의 사본을로드하고 테스트하십시오. 여기서 문제는 주어진 시간에 프로덕션 DB에 무엇이 있는지 전혀 모른다는 것입니다. 시간이 지남에 따라 데이터가 변경되면 테스트를 다시 작성해야 할 수도 있습니다.

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

  • 모의 데이터베이스 서버를 사용하고 주어진 메소드 호출에 대한 응답으로 ORM이 올바른 쿼리를 보내는 지 확인하십시오.

데이터베이스 기반 애플리케이션을 테스트하기 위해 어떤 전략을 사용 했습니까? 무엇이 가장 효과가 있었습니까?


고유 인덱스와 같은 경우 테스트 환경에 데이터베이스 인덱스가 여전히 있어야한다고 생각합니다.
dtc

나는이 질문에 대해 마음에 들지 않지만 여기서 규칙을 따르면이 질문은 stackoverflow가 아니라 softwareengineering.stackexchange 웹 사이트를 위한 것입니다 .
ITExpert

답변:


155

실제로 첫 번째 접근 방식을 약간의 성공으로 사용했지만 약간의 다른 방식으로 문제를 해결할 수 있다고 생각합니다.

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

  2. 연속 통합 서버를 사용하여 데이터베이스 스키마를 빌드하고 샘플 데이터를로드하고 테스트를 실행하십시오. 이것이 테스트 데이터베이스를 동기화 상태로 유지하는 방법입니다 (모든 테스트 실행 시마다 데이터베이스를 다시 작성). CI 서버가 자체 전용 데이터베이스 인스턴스에 대한 액세스 권한과 소유권을 가져야하지만, 하루에 3 번 DB 스키마를 구축하면 배달 직전까지는 발견되지 않은 오류를 찾는 데 크게 도움이되었다고합니다. ). 모든 커밋 전에 스키마를 다시 작성한다고 말할 수는 없습니다. 아무도 없어요? 이 방법을 사용하면 할 필요가 없습니다 (아마도 우리는해야하지만 누군가 잊어 버린 경우 큰 문제는 아닙니다).

  3. 내 그룹의 경우 사용자 입력은 db가 아닌 응용 프로그램 수준에서 수행되므로 표준 단위 테스트를 통해 테스트됩니다.

프로덕션 데이터베이스 복사본로드 :
이것은 마지막 작업에서 사용 된 접근 방식입니다. 그것은 몇 가지 문제의 큰 고통의 원인이었습니다.

  1. 프로덕션 버전에서 복사본이 오래되었습니다.
  2. 사본의 스키마가 변경되어 프로덕션 시스템에 전파되지 않습니다. 이 시점에서 우리는 다양한 스키마를 갖게되었습니다. 재미 없어.

Mocking Database Server :
현재 작업 에서도이 작업을 수행합니다. 모든 커밋 후에는 모의 db 접근자가 삽입 된 응용 프로그램 코드에 대해 단위 테스트를 실행합니다. 그런 다음 하루에 세 번 위에서 설명한 전체 DB 빌드를 실행합니다. 나는 두 가지 접근법을 모두 추천한다.


37
프로덕션 데이터베이스 복사본을로드하면 보안 및 개인 정보 보호에 영향을 미칩니다. 일단 커지면 사본을 가져와 개발자 환경에 배치하는 것이 큰 문제가 될 수 있습니다.
WW.

솔직히 이것은 큰 고통입니다. 나는 테스트에 익숙하지 않으며 나는 또한 테스트하고 싶은 orm을 썼다. 나는 이미 첫 번째 방법을 사용했지만 테스트 장치를 만들지 않는다는 것을 읽었습니다. 특정 DB 엔진 기능을 사용하므로 DAO를 조롱하는 것은 어려울 것입니다. 나는 그것이 현재의 방법을 사용한다고 생각하고 다른 사람들이 그것을 사용하기 때문에. 자동 테스트 btw. 감사.
frostymarvelous

2
나는 두 가지 큰 프로젝트를 관리합니다. 그중 하나 에서이 접근법은 완벽했지만 다른 프로젝트에서이를 구현하는 데 많은 문제가있었습니다. 따라서 테스트를 실행할 때마다 스키마를 얼마나 쉽게 다시 만들 수 있는지에 따라 현재이 마지막 문제에 대한 새로운 솔루션을 찾고 있습니다.
교차

2
이 경우 Roundhouse와 같은 데이터베이스 버전 관리 도구 (마이그레이션을 실행할 수있는 도구)를 사용하는 것이 좋습니다. 이것은 모든 DB 인스턴스에서 실행될 수 있으며 스키마가 최신인지 확인해야합니다. 또한 마이그레이션 스크립트를 작성할 때 마이그레이션과 데이터를 동기화하여 테스트 데이터도 작성해야합니다.
jedd.ahyoung

원숭이 패치와 조롱을 더 잘 사용하고 쓰기 작업을 피하십시오
Nickpick

56

다음과 같은 이유로 항상 메모리 내 DB (HSQLDB 또는 Derby)에 대해 테스트를 실행하고 있습니다.

  • 테스트 DB에 보관할 데이터와 그 이유를 생각하게합니다. 프로덕션 DB를 테스트 시스템으로 가져 가면 "내가 무엇을하고 있는지 또는 왜, 왜 문제가 발생했는지 모르겠습니다." ;)
  • 새로운 장소에서 적은 노력으로 데이터베이스를 다시 만들 수 있습니다 (예 : 프로덕션에서 버그를 복제해야하는 경우)
  • DDL 파일의 품질에 큰 도움이됩니다.

테스트가 시작되면 대부분의 테스트 후에 메모리 내 DB에 새로운 데이터가로드되고 ROLLBACK을 호출하여 안정적으로 유지합니다. 항상 테스트 DB의 데이터를 안정적으로 유지하십시오! 데이터가 항상 변경되면 테스트 할 수 없습니다.

데이터는 SQL, 템플릿 DB 또는 덤프 / 백업에서로드됩니다. VCS에 넣을 수 있기 때문에 읽을 수있는 형식 인 덤프를 선호합니다. 그래도 작동하지 않으면 CSV 파일 또는 XML을 사용합니다. 엄청난 양의 데이터를로드해야한다면 ...하지 않습니다. 엄청난 양의 데이터를로드 할 필요가 없습니다. :) 단위 테스트에는 적합하지 않습니다. 성능 테스트는 또 다른 문제이며 다른 규칙이 적용됩니다.


1
인 메모리 DB를 (특히) 사용하는 유일한 이유는 속도입니까?
rinogo 2018 년

2
또 다른 장점은 "투명성 (throwaway)"특성 일 수 있습니다. 자신을 정리할 필요가 없습니다. 인 메모리 DB를 종료하십시오. (그러나 당신이 언급 한 ROLLBACK 접근법과 같은 다른 방법들이 있습니다)
rinogo

1
각 테스트는 전략을 개별적으로 선택할 수 있다는 장점이 있습니다. 우리는 자식 스레드에서 작업을 수행하는 테스트를 가지고 있습니다. 즉, Spring은 항상 데이터를 커밋합니다.
Aaron Digulla

@Aaron : 우리는이 전략도 따르고 있습니다. 메모리 내 모델이 실제 db와 동일한 구조를 가지고 있다고 주장하는 전략이 무엇인지 알고 싶습니다.
기 illa

1
@Guillaume : 동일한 SQL 파일에서 모든 데이터베이스를 만들고 있습니다. H2는 주요 데이터베이스의 대부분의 SQL 특성을 지원하기 때문에 이에 적합합니다. 그래도 작동하지 않으면 원래 SQL을 가져와 메모리 내 데이터베이스의 SQL로 변환하는 필터를 사용합니다.
Aaron Digulla

14

나는 오랫동안이 질문을 해왔지만 그에 대한 은색 총알이 없다고 생각합니다.

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

그 접근 방식에서 볼 수있는 주요 문제는 DAO 계층과 상호 작용하지만 DAO 자체를 테스트하지 않는 코드 만 다루고 있다는 것입니다. 제 경험상 해당 계층에서도 많은 오류가 발생한다는 것을 알았습니다. 또한 TDD 또는 빠른 테스트를 로컬로 사용하기 위해 데이터베이스에 대해 실행되는 몇 가지 단위 테스트를 유지하지만 해당 테스트를 위해 데이터베이스를 유지하지 않기 때문에 이러한 테스트는 연속 통합 서버에서 실행되지 않습니다. CI 서버에서 실행되는 테스트는 독립적이어야한다고 생각하십시오.

매우 흥미롭지 만 그다지 시간이 걸리지 않는 또 다른 접근법은 단위 테스트 내에서 실행되는 임베디드 데이터베이스에서 프로덕션에 사용하는 것과 동일한 스키마를 작성하는 것입니다.

이 방법이 적용 범위를 개선한다는 데는 의문의 여지가 없지만, 현재 DBMS 및 임베디드 교체와 함께 작동하도록 ANSI SQL에 최대한 근접해야하기 때문에 몇 가지 단점이 있습니다.

코드와 관련이 있다고 생각하더라도 DbUnit 과 같이 쉽게 만들 수있는 몇 가지 프로젝트가 있습니다 .


13

(예를 들어, 비록 당신이 어떤 식 으로든에서 데이터베이스를 조롱 할 수있는 도구가있는 경우 jOOQMockConnection에서 볼 수있는 이 답변 , 내가 권합니다 - 나는 jOOQ의 공급 업체에 대한 작동 면책 조항) 하지 복잡와 큰 데이터베이스를 조롱이 쿼리.

ORM을 통합 테스트하려는 경우에도 ORM이 데이터베이스에 매우 복잡한 일련의 쿼리를 발행한다는 점에 유의하십시오.

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

실제로 모의 내부에 약간의 데이터베이스를 작성하지 않고 전송 된 SQL 문을 해석하지 않는 한 합리적인 더미 데이터를 생성하기 위해 모든 것을 조롱하는 것은 매우 어렵습니다. 이미 말했지만 잘 알려진 데이터로 쉽게 재설정 할 수있는 잘 알려진 통합 테스트 데이터베이스를 사용하면 통합 테스트를 실행할 수 있습니다.


5

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

또한 테스트 환경에 대해 데이터베이스 스키마를 포함한 모든 변경을 먼저 수행하므로 다른 방법으로 끝납니다. 모든 테스트가 끝나면 스키마 업데이트를 프로덕션 호스트에 적용하십시오. 또한 실제 개발 상자를 만지기 전에 db 업그레이드가 제대로 작동하는지 확인할 수 있도록 개발 시스템에서 별도의 테스트 대 응용 프로그램 데이터베이스 쌍을 유지합니다.


3

첫 번째 방법을 사용하고 있지만 언급 한 문제를 해결할 수있는 약간 다릅니다.

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));        
}

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

  1. 코드를 유지 관리하는 것이 훨씬 쉽습니다 (예 : Author와 같이 많은 테스트에서 참조되는 일부 엔터티에서 필수 열 추가는 많은 파일 / 레코드를 변경하지 않아도되며 빌더 및 / 또는 팩토리 만 변경하면 됨)
  2. 특정 테스트에 필요한 데이터는 다른 파일이 아닌 테스트 자체에 설명되어 있습니다. 이 근접성은 테스트 이해를 위해 매우 중요합니다.

롤백 및 커밋

테스트가 실행될 때 커밋하는 것이 더 편리하다는 것을 알았습니다. 먼저 일부 효과 (예 :DEFERRED CONSTRAINTS 커밋이 발생하지 않으면 :)를 확인할 수 없습니다. 둘째, 테스트가 실패하면 롤백에 의해 되돌려지지 않으므로 DB에서 데이터를 검사 할 수 있습니다.

원인으로 인해 테스트에서 데이터가 손상 될 수 있고 다른 테스트에서는 실패 할 수 있다는 단점이 있습니다. 이를 해결하기 위해 테스트를 분리하려고합니다. 위의 예에서 모든 테스트는 새로운 테스트 Author를 생성하고 이와 관련된 다른 모든 엔티티는 생성되므로 충돌이 거의 없습니다. 잠재적으로 손상 될 수 있지만 DB 수준 제약 조건으로 표현할 수없는 나머지 불변량을 처리하기 위해 모든 단일 테스트 후에 실행될 수있는 잘못된 조건에 대해 프로그래밍 방식 검사를 사용합니다 (CI에서 실행되지만 일반적으로 성능을 위해 로컬로 꺼집니다) 원인).


SQL 스크립트 대신 엔티티와 orm을 사용하여 데이터베이스를 시드하는 경우 모델을 변경하면 컴파일러가 시드 코드를 수정하도록 강요하는 이점도 있습니다. 정적 유형 언어를 사용하는 경우에만 해당됩니다.
daramasala 2016 년

설명을 위해 : 응용 프로그램 전체에서 또는 테스트 목적으로 유틸리티 기능 / 클래스를 사용하고 있습니까?
엘라

@Ella 이러한 유틸리티 기능은 일반적으로 테스트 코드 외부에서는 필요하지 않습니다. 에 대해 예를 들어보십시오 PostBuilder.post(). 게시물의 모든 필수 속성에 대한 일부 값을 생성합니다. 이것은 프로덕션 코드에서 필요하지 않습니다.
Roman Konoval

2

JDBC 기반 프로젝트 (직접 또는 간접적으로, 예를 들어 JPA, EJB 등)의 경우 전체 데이터베이스가 아니라 실제 RDBMS에서 테스트 DB를 사용하는 것이 더 좋을 것입니다. .

JDBC 데이터 (결과 세트, 업데이트 횟수, 경고 등)가 백엔드와 동일하므로 prod db, 테스트 db 또는 각 테스트에 대해 제공되는 일부 모형 데이터와 동일하므로 장점은 이러한 방식으로 제공되는 추상화입니다. 케이스.

각 경우에 대해 JDBC 연결을 조롱하면 테스트 DB를 관리 할 필요가 없습니다 (정리, 한 번에 하나의 테스트, 픽스처 다시로드 등). 모든 목업 연결은 격리되어 있으며 정리할 필요가 없습니다. JDBC 테스트를 모방하기 위해 각 테스트 케이스에는 최소한의 필수 설비 만 제공되므로 전체 테스트 데이터베이스를 관리하는 복잡성을 피할 수 있습니다.

: 사제는 모형 이런 종류의 JDBC 드라이버 및 유틸리티 포함 내 프레임 워크입니다 http://acolyte.eu.org을 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.