데이터 액세스 계층을 테스트하는 방법은 무엇입니까?


17

Spring 액세스를 위해 JDBC를 사용하는 DAO 메소드가 있습니다. 판매자의 품목 판매 성공률을 계산합니다.

코드는 다음과 같습니다.

public BigDecimal getSellingSuccessRate(long seller_id) {
    String sql = "SELECT SUM(IF(sold_price IS NOT NULL, 1, 0))/SUM(1) 
                  FROM transaction WHERE seller_id = ?";
    Object[] args = {seller_id};
    return getJdbcTemplate().queryForObject(sql, args, BigDecimal.class);
}

JUnit으로이 메소드 또는 DAO 메소드를 테스트하려면 어떻게해야합니까? 데이터 액세스 로직을 테스트하는 모범 사례는 무엇입니까? 일부 데이터가로드 된 내장형 데이터베이스에 대해 테스트 할 생각이지만 RDBMS 및 스키마 측면에서 프로덕션 환경과 유사한 통합 테스트를 수행해서는 안됩니까?


DBUnit을 확인하십시오 . 특별히 문제를 해결하기 위해 만들어졌습니다.
Sergio

답변:


15

단위 테스트에 '실제'데이터베이스를 사용하는 데 따른 문제는 테스트 설정, 중단 및 격리입니다. 완전히 새로운 MySQL 데이터베이스를 가동시키고 단 한 번의 단위 테스트를 위해 테이블과 데이터를 생성 할 필요는 없습니다. 이 문제는 데이터베이스의 외부 특성과 관련이 있으며 테스트 데이터베이스가 다운되어 단위 테스트가 실패합니다. 테스트 할 고유 한 데이터베이스가 있는지 확인하는 데 문제가 있습니다. 그들은 극복 할 수 있지만 더 간단한 대답이 있습니다.

데이터베이스를 조롱하는 것은 하나의 옵션 이지만 실행되는 실제 쿼리는 테스트하지 않습니다. DAO의 데이터가 시스템을 올바르게 통과하는지 확인하려는 경우 훨씬 간단한 솔루션으로 사용할 수 있습니다. 그러나 DAO 자체를 테스트하려면 DAO 뒤에 데이터가 있고 쿼리가 올바르게 실행됩니다.

가장 먼저 할 일은 in-memory 데이터베이스를 사용하는 것입니다. HyperSQL 은 다른 데이터베이스의 방언을 에뮬레이트 할 수 있으므로 데이터베이스 간의 사소한 차이 (데이터 유형, 함수 등)가 동일하게 유지되므로 탁월한 선택입니다. hsqldb에는 단위 테스트를위한 멋진 기능도 있습니다.

db.url=jdbc:hsqldb:file:src/test/resources/testData;shutdown=true;

testData파일 에서 데이터베이스 상태 (테이블, 초기 데이터)를로드 합니다. shutdown=true마지막 연결이 닫히면 데이터베이스가 자동으로 종료됩니다.

의존성 주입을 사용 하여 단위 테스트 가 프로덕션 (또는 테스트 또는 로컬) 빌드에서 사용 하는 것과 다른 데이터베이스를 선택 하도록합니다.

그런 다음 DAO는 데이터베이스에 대해 테스트를 시작할 수있는 주입 된 데이터베이스를 사용합니다.

그러면 단위 테스트는 다음과 같이 보일 것입니다 (간단하게 포함되지 않은 지루한 것들).

    @Before
    public void setUpDB() {
        DBConnection connection = new DBConnection();
        try {
            conn = connection.getDBConnection();
            insert = conn.prepareStatement("INSERT INTO data (txt, ts, active) VALUES (?, ?, ?)");
        } catch (SQLException e) {
            e.printStackTrace();
            fail("Error instantiating database table: " + e.getMessage());
        }
    }

    @After
    public void tearDown() {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addData(String txt, Timestamp ts, boolean active) throws Exception {
        insert.setString(1, txt);
        insert.setTimestamp(2, ts);
        insert.setBoolean(3, active);
        insert.execute();
    }

    @Test
    public void testGetData() throws Exception {
        // load data
        Calendar time = Calendar.getInstance();
        long now = time.getTimeInMillis();
        long then1h = now - (60 * 60 * 1000);  // one hour ago
        long then2m = now - (60 * 1000 * 2);   // two minutes ago
        addData("active_foo", new Timestamp(then1h), true);     // active but old
        addData("inactive_bar", new Timestamp(then1h), false);  // inactive and old
        addData("active_quz", new Timestamp(then2m), true);     // active and new
        addData("inactive_baz", new Timestamp(then2m), false);  // inactive and new

        DataAccess dao = new DataAccess();
        int count = 0;
        for (Data data : dao.getData()) {
            count++;
            assertTrue(data.getTxt().startsWith("active"));
        }

        assertEquals("got back " + count + " rows instead of 1", count, 1);
    }

따라서 DAO를 호출하고 테스트 기간 동안 존재하는 즉석 데이터베이스에 설정된 데이터를 사용하는 단위 테스트가 있습니다. 실행하기 전에 외부 리소스 나 데이터베이스 상태에 대해 걱정하거나 알려진 상태로 복원 할 필요가 없습니다 ( '알려진 상태'는 '존재하지 않음'이므로 되돌릴 수 없습니다).

DBUnit 은 데이터베이스 설정, 테이블 작성 및 데이터로드에서 더 간단한 프로세스를 설명 할 수 있습니다. 어떤 이유로 실제 데이터베이스를 사용해야 할 경우에는 훨씬 더 유용한 도구입니다.

위 코드는 github의 TestingWithHsqldb 개념 증명을 위해 작성한 maven 프로젝트의 일부입니다.


2
HSQL이 다른 DB 공급 업체의 방언을 조롱 할 수있는 부분에 대해서는 몰랐습니다. 감사합니다.
Michael

1
이것이 통해 수행 할 수 있습니다 @Dog 데이터베이스 속성 과 같은 sql.syntax_mys=true방식 HSQLDB의 작품 변화하는 "true로 설정하면이 속성을, 텍스트 및 AUTO_INCREMENT 유형에 대한 지원을 가능하게하고이 방언의 다른 측면과의 호환성을 할 수 있습니다." 반면 sql.syntax_ora=true않습니다 "이 속성을 true로 설정하면, 표준이 아닌 유형에 대한 지원을 할 수 있습니다. 또한 DUAL, ROWNUM, NEXTVAL과 CURRVAL 구문과를 가능하게하고이 방언의 다른 측면과의 호환성을 할 수 있습니다."

DBUnit is way :)
Silviu Burcea

@SilviuBurcea DBUnit은 복잡한 데이터베이스 테스트 환경을 설정하는 '배관'작업을 직접 수행하는 것보다 훨씬 쉽습니다. 필요한 경우 수동으로 수행하는 방법을 아는 것이 여전히 유용합니다 (위에서 언급 한 '수동'접근 방식은 DBUnit이 옵션이 아닌 다른 언어로 마이그레이션 될 수 있음).


2

먼저 프로덕션 환경에서 테스트를 수행해서는 안됩니다. 프로덕션 환경을 미러링하고 통합 테스트를 수행하는 테스트 환경이 있어야합니다.

그렇게하면 여러 가지 일을 할 수 있습니다.

  • Mockito와 같은 조롱 프레임 워크를 사용하여 해당 SQL이 모의 항목에 제출되는지 확인하기 위해 테스트하는 단위 테스트를 작성하십시오. 이렇게하면 메소드가 수행해야 할 작업을 수행하고 그림에서 통합을 수행 할 수 있습니다.
  • 단위 테스트에서 테스트 한 SQL의 적합성을 보여주는 테스트 SQL 스크립트를 작성하십시오. 테스트 스크립트를 기반으로 Explain 및 설명을 실행할 수 있기 때문에 발생할 수있는 튜닝 문제에 도움이 될 수 있습니다.
  • @Sergio가 언급 한대로 DBUnit을 사용하십시오.

프로덕션 환경이라고 말했을 때 실제로 시뮬레이션을 의미했습니다. 답장을 보내 주셔서 감사합니다. Mockito를 살펴 보겠습니다. 저도 배우고 싶었던 것이기 때문입니다.
Michael

1

우리 프로젝트에서 각 개발자는 빈 데이터베이스를 실행하고 있으며 구조는 프로덕션 데이터베이스와 동일합니다.

TestInitialize의 각 단위 테스트에서 데이터베이스에 대한 연결 및 트랜잭션과 각 테스트에 필요한 일부 기본 개체를 만듭니다. 그리고 각 메소드 또는 클래스가 끝나면 모든 것이 롤백됩니다.

이런 식으로 SQL 계층을 테스트 할 수 있습니다. 실제로 모든 쿼리 또는 데이터베이스 호출은이 방식으로 테스트해야합니다.

단점은 속도가 느리기 때문에 일반 단위 테스트와는 별도의 프로젝트에 배치한다는 것입니다. 인 메모리 데이터베이스를 사용하여 속도를 높일 수 있지만 아이디어는 동일합니다.


인 메모리 데이터베이스를 사용하는 경우 롤백 트랜잭션 대신 모든 테스트 스위트를 실행하기 전에 드롭 생성 방식을 사용하면 훨씬 빠릅니다.
Downhillski

전에 그런 식으로 생각하지 마십시오. 테스트에서 대부분의 테스트는 고유하지만 사용자 'x'를 만듭니다. db를 한 번 생성하면 해당 객체를 재사용하기 위해 테스트를 변경해야합니다.
Carra

나는 우리가 같은 페이지에 있고 당신의 접근 방식을 좋아합니다. 접근 방식은 순서에 관계없이 각 테스트 사례를 독립적으로 실행할 수 있으며 실행하기 전에는 데이터 테이블의 상태가 동일합니다.
Downhillski

맞습니다. 순서는 중요하지 않습니다. 빌드 PC와 로컬 컴퓨터에서 단위 테스트 실행 순서가 다르기 때문에 테스트가 실패한 것으로 나타났습니다.
Carra
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.