sourcecode

봄의 각 테스트 전에 데이터베이스를 재생성하려면 어떻게 해야 합니까?

copyscript 2023. 3. 25. 11:45
반응형

봄의 각 테스트 전에 데이터베이스를 재생성하려면 어떻게 해야 합니까?

의 데이터베이스 설정은 Spring-Boot-Mvc-Web으로 .application.properties 삭제:

spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

이게 내가 만든 유일한 설정이야내가 만든 다른 구성은 어디에도 없어.그러나 스프링 및 하위 시스템은 각 웹 응용 프로그램이 실행될 때마다 자동으로 데이터베이스를 다시 작성합니다.데이터베이스는 응용 프로그램 종료 후 데이터를 포함하는 동안 시스템 실행 시 다시 생성됩니다.

이 디폴트를 이해하지 못했기 때문에 테스트에 적합하다고 생각하고 있었습니다.

그러나 테스트를 시작했을 때 데이터베이스가 한 번만 다시 작성된다는 것을 알게 되었습니다.테스트는 사전 정의된 순서에 따라 수행되기 때문에 이는 전혀 의미가 없습니다.

그래서 질문은, 어떻게 말이 되는가? 즉, 애플리케이션을 처음 시작할 때처럼 각 테스트 전에 데이터베이스를 재생성하려면 어떻게 해야 합니까?

테스트 클래스 헤더는 다음과 같습니다.

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {

하다, 하다@DirtiesContext전혀 도움이 안 됐어요

갱신하다

나는 콩이 있다.

@Service
public class DatabaseService implements InitializingBean {

방법이 있다.

@Override
    @Transactional()
    public void afterPropertiesSet() throws Exception {
        log.info("Bootstrapping data...");
        User user = createRootUser();
        if(populateDemo) {
            populateDemos();
        }
        log.info("...Bootstrapping completed");
    }

이렇게 .populateDemos()데이터베이스에서 모든 데이터를 지우는 방법을 지정합니다.시험 에는 호출하지 .@DirtiesContext

사실, 내 생각엔 네가 이걸 원하는 것 같아.

@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)

http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html

@DirtiesContext는 동일한 클래스 내에서 클래스 수준 및 메서드 수준 주석으로 사용할 수 있습니다.이러한 시나리오에서 Application Context는 이러한 주석이 달린 메서드 후와 클래스 전체 후에 더티로 표시됩니다.Dirties Context의 경우.ClassMode는 AFTER_EACH_TEST_METHOD로 설정되어 있습니다.클래스의 각 테스트 메서드 후에 컨텍스트는 더티 마크가 붙습니다.

시험 수업 시간에 붙여놨잖아

Spring-Boot 2.2.0에서 승인된 답변을 사용하면 제약조건과 관련된 JDBC 구문 오류가 나타납니다.

원인: org.h2.jdbc.JdbcSQLSyntaxErrorException:제약사항 "FKEFFD698EA2E75FXEERWBO8"IOT"가 이미 있습니다. SQL 문: alter table foo add restraint FKeffd698ea2e75fxeerwbo8iut 외래 키 참조 바 [90045-200]

저는 이 부분을 .@AutoConfigureTestDatabase"spring-boot-test-autoconfigure" (스프링 부트 테스트 자동 구성):

import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class FooRepositoryTest { ... }

는 다른 '아주 좋다'를 .spring.jpa.hibernate.ddl-auto=create-drop은 매우

@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {

되어 있습니다.org.springframework.test.context.jdbc.Sql;사전 테스트 방법과 사후 테스트 방법을 실행할 수 있습니다.데이터베이스를 채웁니다.

매번 데이터베이스 작성과 관련하여, "테스트에서 이 주석을 사용하여 사용자 지정 속성을 사용하여 테스트를 구성할 수 있는 create-drop 옵션만 갖기를 원합니다."

@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{

도움이 되었으면 좋겠다

@DirtiesContext, 아래의 코드가 도움이 됩니다.는 이 대답에서 몇 가지 코드를 사용했다.

H2 를 H2로 합니다.application.yml다음 파일을 테스트 리소스 폴더에 저장합니다.

spring: 
  datasource:
    platform: h2
    url: jdbc:h2:mem:test
    driver-class-name: org.h2.Driver
    username: sa
    password:

후에 '아,아,아,아'라는를 만듭니다.ResetDatabaseTestExecutionListener:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;

public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {

    @Autowired
    private DataSource dataSource;

    public final int getOrder() {
        return 2001;
    }

    private boolean alreadyCleared = false;

    @Override
    public void beforeTestClass(TestContext testContext) {
        testContext.getApplicationContext()
                .getAutowireCapableBeanFactory()
                .autowireBean(this);
    }

    @Override
    public void prepareTestInstance(TestContext testContext) throws Exception {

        if (!alreadyCleared) {
            cleanupDatabase();
            alreadyCleared = true;
        }
    }

    @Override
    public void afterTestClass(TestContext testContext) throws Exception {
        cleanupDatabase();
    }

    private void cleanupDatabase() throws SQLException {
        Connection c = dataSource.getConnection();
        Statement s = c.createStatement();
   
        // Disable FK
        s.execute("SET REFERENTIAL_INTEGRITY FALSE");

        // Find all tables and truncate them
        Set<String> tables = new HashSet<>();
        ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES  where TABLE_SCHEMA='PUBLIC'");
        while (rs.next()) {
            tables.add(rs.getString(1));
        }
        rs.close();
        for (String table : tables) {
            s.executeUpdate("TRUNCATE TABLE " + table);
        }

        // Idem for sequences
        Set<String> sequences = new HashSet<>();
        rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
        while (rs.next()) {
            sequences.add(rs.getString(1));
        }
        rs.close();
        for (String seq : sequences) {
            s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
        }

        // Enable FK
        s.execute("SET REFERENTIAL_INTEGRITY TRUE");
        s.close();
        c.close();
    }
}

위의 코드는 데이터베이스(테이블 트리네이트, 시퀀스 리셋 등)를 리셋하고 H2 데이터베이스와 연동할 수 있도록 준비합니다.다른 메모리 데이터베이스(HsQLDB 등)를 사용하는 경우 동일한 작업을 수행하려면 SQL 쿼리에서 필요한 변경을 수행해야 합니다.

후 하여 '어느 정도'를 추가합니다.@TestExecutionListeners★★★★

@TestExecutionListeners(mergeMode =
        TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {

이거면 될 거야.

@DirtiesContext사용하고 있을 가능성이 있습니다.@MockBean테스트에서는 스프링 콘텍스트를 더티로 마크하고 콘텍스트 전체를 자동으로 새로고침합니다.

스프링 부트에서는 각 테스트에 대해 h2 데이터베이스를 일의로 정의할 수 있습니다.각 테스트의 데이터 소스 URL을 덮어씁니다.

 @SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})

테스트는 병렬로 실행할 수 있습니다.

테스트 중 데이터는 다음과 같이 재설정할 수 있습니다.

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)

JUnit 5 테스트의 "H2 데이터베이스 리셋" 기능을 다루는 라이브러리가 있습니다.

https://github.com/cronn/test-utils#h2util

사용 예:

@ExtendWith(SpringExtension.class)
@Import(H2Util.class)
class MyTest {

    @BeforeEach
    void resetDatabase(@Autowired H2Util h2Util) {
        h2Util.resetDatabase();
    }

    // tests...
}

메이븐 좌표:

<dependency>
    <groupId>de.cronn</groupId>
    <artifactId>test-utils</artifactId>
    <version>0.2.0</version>
    <scope>test</scope>
</dependency>

면책사항:저는 추천 도서관의 저자입니다.

「 」를 사용하고 spring.jpa.hibernate.ddl-auto=create-drop데이터베이스를 작성/폐기하기에 충분한가?

Spring-Data 통합(전혀 모르는 것)을 사용하고 있지 않는 한, 이것은 커스텀 로직으로서 실장이 필요한 것 같습니다.Spring은 데이터베이스, 스키마 및 테이블에 대해 알지 못합니다.

가정하고 JUnit을 .@Before ★★★★★★★★★★★★★★★★★」@After데이터베이스, 테이블 및 데이터를 설정하고 정리하는 방법.테스트 자체에서 필요한 데이터를 쓸 수 있으며, 필요에 따라 스스로 정리할 수도 있습니다.

시도/리소스를 사용하는 솔루션과 이 답변을 기반으로 구성 가능한 스키마를 사용합니다.문제는 H2 데이터베이스가 테스트 케이스 간에 데이터를 유출했다는 것입니다.그래서 이게Listener는 각 테스트 방식보다 먼저 기동합니다.

Listener:

public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {

    private static final List<String> IGNORED_TABLES = List.of(
        "TABLE_A",
        "TABLE_B"
    );

    private static final String SQL_DISABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY FALSE";
    private static final String SQL_ENABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY TRUE";

    private static final String SQL_FIND_TABLE_NAMES = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='%s'";
    private static final String SQL_TRUNCATE_TABLE = "TRUNCATE TABLE %s.%s";

    private static final String SQL_FIND_SEQUENCE_NAMES = "SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='%s'";
    private static final String SQL_RESTART_SEQUENCE = "ALTER SEQUENCE %s.%s RESTART WITH 1";

    @Autowired
    private DataSource dataSource;

    @Value("${schema.property}")
    private String schema;

    @Override
    public void beforeTestClass(TestContext testContext) {
        testContext.getApplicationContext()
            .getAutowireCapableBeanFactory()
            .autowireBean(this);
    }

    @Override
    public void beforeTestMethod(TestContext testContext) throws Exception {
        cleanupDatabase();
    }

    private void cleanupDatabase() throws SQLException {
        try (
            Connection connection = dataSource.getConnection();
            Statement statement = connection.createStatement()
        ) {
            statement.execute(SQL_DISABLE_REFERENTIAL_INTEGRITY);

            Set<String> tables = new HashSet<>();
            try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_TABLE_NAMES, schema))) {
                while (resultSet.next()) {
                    tables.add(resultSet.getString(1));
                }
            }

            for (String table : tables) {
                if (!IGNORED_TABLES.contains(table)) {
                    statement.executeUpdate(String.format(SQL_TRUNCATE_TABLE, schema, table));
                }
            }

            Set<String> sequences = new HashSet<>();
            try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_SEQUENCE_NAMES, schema))) {
                while (resultSet.next()) {
                    sequences.add(resultSet.getString(1));
                }
            }

            for (String sequence : sequences) {
                statement.executeUpdate(String.format(SQL_RESTART_SEQUENCE, schema, sequence));
            }

            statement.execute(SQL_ENABLE_REFERENTIAL_INTEGRITY);
        }
    }
}

사용자 지정 주석 사용:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@TestExecutionListeners(mergeMode =
    TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
    listeners = { ResetDatabaseTestExecutionListener.class }
)
public @interface ResetDatabase {
}

데이터베이스를 리셋하는 각 테스트를 쉽게 표시할 수 있습니다.

@SpringBootTest(
    webEnvironment = RANDOM_PORT,
    classes = { Application.class }
)
@ResetDatabase
public class SomeClassIT {

테스트 클래스에 주석을 달 수 있습니다.@Transactional:

import org.springframework.transaction.annotation.Transactional;
...

...
@RunWith(SpringRunner.class)
@Transactional
public class MyClassTest {

    @Autowired
    private SomeRepository repository;

    @Before
    public void init() {
       // add some test data, that data would be rolled back, and recreated for each separate test
       repository.save(...);
    }

    @Test
    public void testSomething() {
       // add some more data
       repository.save(...);
       // update some base data
       repository.delete(...);
       // all the changes on database done in that test would be rolled back after test finish
    }
}

모든 테스트는 트랜잭션에 포함되며 트랜잭션은 각 테스트가 끝날 때 롤백됩니다.물론 이 주석에는 몇 가지 문제가 있습니다. 예를 들어 프로덕션 코드에서 점수가 다른 트랜잭션을 사용하는 경우 각별히 주의해야 합니다.

컨테이너 내에서 데이터베이스를 실행하는 데 도움이 되는 https://www.testcontainers.org/을 시험해 볼 수도 있고 각 테스트 실행에 대해 새로운 데이터베이스를 작성할 수도 있습니다.그러나 컨테이너를 만들고 데이터베이스 서버를 시작, 구성 및 마이그레이션 작업을 수행해야 하므로 테스트 실행이 매우 느립니다.

아무것도 안 먹혔어.모든 테스트 클래스에 대해 다음 주석을 추가할 수 있습니다.

@TestMethodOrder(MethodOrderer.OrderAnnotation.class) //in case you need tests to be in specific order
@DataJpaTest // will disable full auto-configuration and instead apply only configuration relevant to JPA tests
@AutoConfigureTestDatabase(replace = NONE) //configures a test database to use instead of the application-defined or auto-configured DataSource

클래스 내에서 특정 테스트를 주문하려면 @Order 주석도 추가해야 합니다.

@Test
    @Order(1) //first test
@Test
    @Order(2) //second test, etc.

이전에 db를 조작했기 때문에 테스트를 다시 실행해도 실패하지 않습니다.

언급URL : https://stackoverflow.com/questions/34617152/how-to-re-create-database-before-each-test-in-spring

반응형