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


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.