https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#testcontext-tx
틀린 해석이나 내용이 잘못되었다면 알려주세요, 😎
5.9. Transaction Management ~ 5.10. Executing SQL Scripts
5 다 끝내려니 너무 양이 많다..ㅜㅜㅜ 4로 해서 5.11~5.13 까지 정리할 예정
5.9. Transaction Management
TestContext framework에서 transaction은 test class 에서 @TestExecutionListeners 를 명시적으로 선언하지 않더라도 기본적으로 구성되는 TransactionalTestExecutionListener에 의해 관리된다
트랜잭션 지원을 활성화하려면 load 되는 ApplicationContext에서 @ContextConfiguration 으로 PlatformTransactionManager bean을 구성해야한다
테스트를 위해 클래스 또는 메서드 수준에서 @Transactional 애노테이션을 선언해야한다
5.9.1. Test-managed Transactions
Test-managed Transaction은 TransactionalTestExecutionListener를 사용하여 선엉ㄴ적으로 관리되거나 TestTransaction을 사용하여 programming 방식으로 관리되는 트랜잭션이다
테스트 용으로 로드된 ApplicationContext 내에서 Spring이 직접 관리하는 Spring-managed transactions 또는 테스트에 의해 호출되는 애플리케이션 코드 내 프로그래밍 방식 관리 되는 application-managed transaction 은 다르다
스프링 관리 및 애플리케이션 관리 트랜잭션은 일반적으로 테스트 관리 트랜잭션에 참여한다.
그러나 Spring 관리 또는 애플리케이션 관리 트랜잭션이 REQUIRED 또는 SUPPORTS 이외의 전파 유형으로 구성된 경우 주의해야한다
Transaction-propagation
https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#tx-propagation
Caution must be taken when using any form of preemptive timeouts from a testing framework in conjunction with Spring’s test-managed transactions.
Specifically, Spring’s testing support binds transaction state to the current thread (via a java.lang.ThreadLocal variable) before the current test method is invoked. If a testing framework invokes the current test method in a new thread in order to support a preemptive timeout, any actions performed within the current test method will not be invoked within the test-managed transaction. Consequently, the result of any such actions will not be rolled back with the test-managed transaction. On the contrary, such actions will be committed to the persistent store — for example, a relational database — even though the test-managed transaction is properly rolled back by Spring.
Situations in which this can occur include but are not limited to the following.
- JUnit 4’s @Test(timeout = …) support and TimeOut rule
- JUnit Jupiter’s assertTimeoutPreemptively(…) methods in the org.junit.jupiter.api.Assertions class
- TestNG’s @Test(timeOut = …) support
5.9.2. Enabling and Disabling Transactions
Test method 에 @Transactional 주석을 추가하면 기본적으로 테스트 완료 후 자동으로 롤백시키는 트랜잭션이 실행된다
테스트클래스에 @Transactional 주석이 달린 경우에는 클래스 계층 구조 내 각 테스트 메서드도 트랜잭션 내에서 실행된다
@Transactional 로 주석이 지정되지 않은 경우 트랜잭션 내에서 실행되지 않는다
@Transactional은 테스트 수명 주기 메서드(예: JUnit Jupiter의 @BeforeAll, @BeforeEach 등으로 주석이 달린 메서드)에서는 지원되지 않는다.
또한 @Transactional로 주석이 지정되었지만 전파 속성이 NOT_SUPPORTED 또는 NEVER로 설정된 테스트는 실행되지 않는다
@Transactional attribute supportAttributeSupported for test-managed transactions
value and transactionManager | yes |
propagation | only Propagation.NOT_SUPPORTED and Propagation.NEVER are supported |
isolation | no |
timeout | no |
readOnly | no |
rollbackFor and rollbackForClassName | no: use TestTransaction.flagForRollback() instead |
noRollbackFor and noRollbackForClassName | no: use TestTransaction.flagForCommit() instead |
Method-level lifecycle methods (JUnit Jupiter 의 @BeforeEachor 로 주석이 달린 메서드 @AfterEach 등) 는 테스트 관리 트랜잭션 내에서 실행된다
그러나 suite-level and class-level lifecycle methods (JUnit Jupiter의 @BeforeAll또는 @AfterAll로 주석이 달린 메서드 및 TestNG의 @BeforeSuite, @AfterSuite, @BeforeClass또는 로 주석이 달린 메서드) 는 테스트 관리 트랜잭션 내에서 @AfterCalss 가 실행되지 않는다
If you need to run code in a suite-level or class-level lifecycle method within a transaction, you may wish to inject a corresponding PlatformTransactionManager into your test class and then use that with a TransactionTemplate for programmatic transaction management.
Note that AbstractTransactionalJUnit4SpringContextTests and AbstractTransactionalTestNGSpringContextTests are preconfigured for transactional support at the class level.
일반적인 Hibernate 기반 통합 테스트 시나리오
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
there is no need to clean up the database after the createUser() method runs, since any changes made to the database are automatically rolled back by the TransactionalTestExecutionListener.
5.9.3. Transaction Rollback and Commit Behavior
기본적으로 테스트 트랜잭션은 테스트 완료 후 자동으로 롤백된다.
트랜잭션 커밋 및 롤백 동작은 @Commit, @Rollback 애노테이션으로 선언 구성 할 수 있다
5.9.4. Programmatic Transaction Management
TestTransaction 의 static method 를 사용하여 프로그래밍 방식으로 테스트 관리 트랜잭션과 상호작용할 수 있다
TestTransaction 을 test하는 method내에서 앞, 후에 현재 테스트 관리 트랜잭션을 시작 또는 종료하거나 rollback, commit 하기 위해 현재 테스트 관리 트랜잭션을 구성할 수 있다
TestTransaction에 대한 지원은 TransactionalTestExecutionListener가 활성화될 때마다 자동으로 사용 가능하다
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// assert initial state in test database:
assertNumUsers(2);
deleteFromTables("user");
// changes to the database will be committed!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
5.9.5. Running Code Outside of a Transaction
경우에 따라 트랜잭션 테스트 메서드 전이나 후에 트랜잭션 컨텍스트 외부에서 특정 코드를 실행해야 할 수도 있다.
예시로 테스트를 실행하기 전에 초기 데이터베이스를 확인하거나, 테스트 실행 후 예상되는 커밋 동작을 확인할 때 등이 있다(테스트가 commit할 수 있도록 구성된 코드일 때). TransactionalTestExecutionListener는 정확히 그러한 시나리오에 대해 @BeforeTransaction 및 @AfterTransaction 애노테이션을 지원한다.
이 애노테이션들을 사용하여 테스트 클래스의 무효 메서드 또는 테스트 인터페이스의 무효 기본 메서드에 주석을 달 수 있으며 TransactionalTestExecutionListener는 트랜잭션 전 메서드 또는 트랜잭션 후 메서드가 적절한 시간에 실행되도록 한다.
Any before methods (such as methods annotated with JUnit Jupiter’s @BeforeEach) and any after methods (such as methods annotated with JUnit Jupiter’s @AfterEach) are run within a transaction.
In addition, methods annotated with @BeforeTransaction or @AfterTransaction are not run for test methods that are not configured to run within a transaction.
5.9.6. Configuring a Transaction Manager
(읽다가 점점 해석 너무 머리 아파서 걍 번역기 다 돌린 해석본임)
TransactionalTestExecutionListener는 테스트를 위해 Spring ApplicationContext에서 PlatformTransactionManager 빈이 정의될 것으로 예상한다.
테스트의 ApplicationContext 내에 여러 PlatformTransactionManager 인스턴스가 있는 경우 @Transactional("myTxMgr") 또는 @Transactional(transactionManager = "myTxMgr")을 사용하여 한정자를 선언하거나 @Configuration 클래스로 TransactionManagementConfigurer를 구현할 수 있다.
테스트의 ApplicationContext에서 트랜잭션 관리자를 조회하는 데 사용되는 알고리즘에 대한 자세한 내용은 TestContextTransactionUtils.retrieveTransactionManager()에 대한 javadoc을 참조
5.9.7. Demonstration of All Transaction-related Annotations
(JUnit Jupiter 기반) 모든 transaction 주석을 강조 표시하여 가상 통합 테스트 시나리오를 볼 수 있다
(추가 정보 및 구성은 주석 지원 정보를 참조)
@Sql에 대한 트랜잭션 관리에는 default transaction rollback semantics으로 선언적 SQL 스크립트 실행에 @Sql을 사용하는 추가 예제가 포함되어 있다.
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
Avoid false positives when testing ORM code
Hibernate 세션 또는 JPA 지속성 컨텍스트의 상태를 조작하는 애플리케이션 코드를 테스트할 때 해당 코드를 실행하는 테스트 메서드 내에서 기본 작업 단위를 플러시해야한다. 기본 작업 단위를 플러시하지 못하면 false positives가 생성될 수 있다. 테스트는 통과했지만 동일한 코드는 실제환경에서 예외를 throw한다. 이는 메모리 내 작업 단위를 유지 관리하는 모든 ORM 프레임워크에 적용되기 때문이다. 다음 Hibernate 기반 예제 테스트 사례에서 한 메서드는 false positives을 보여주고 다른 메서드는 세션을 플러시한 결과를 올바르게 보여준다
// ...
@Autowired
SessionFactory sessionFactory;
@Transactional
@Test // no expected exception!
public void falsePositive() {
updateEntityInHibernateSession();
// False positive: an exception will be thrown once the Hibernate
// Session is finally flushed (i.e., in production code)
}
@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
updateEntityInHibernateSession();
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush();
}
// ...
JPA 매칭
@PersistenceContext
EntityManager entityManager;
@Transactional
@Test // no expected exception!
public void falsePositive() {
updateEntityInJpaPersistenceContext();
// False positive: an exception will be thrown once the JPA
// EntityManager is finally flushed (i.e., in production code)
}
@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
updateEntityInJpaPersistenceContext();
// Manual flush is required to avoid false positive in test
entityManager.flush();
}
위의 내용과 유사하게 응용 프로그램이 Entity life-cycle callback(also known as entity listeners) 를 사용하는 경우 코드를 실행하는 테스트 메서드에서 기본 작업 단위에 flush 해야한다. unit of work 을 지우지 못하면 특정 수명 주기 콜백이 호출되지 않을 수 있다
JPA를 사용하는 경우 @PostPersist, @PreUpdate 및 @PostUpdate 콜백은 엔터티가 저장되거나 업데이트된 후 entityManager.flush()가 호출되지 않는 한 호출되지 않는다.
마찬가지로 엔티티가 현재 작업 단위(현재 지속성 컨텍스트와 연결됨)에 이미 연결되어 있는 경우 엔티티를 다시 로드하려고 시도하면 엔티티를 다시 로드하려고 시도하기 전에 entityManager.clear()가 호출되지 않는 한 @PostLoad 콜백이 발생하지 않는다
엔티티가 유지될 때 @PostPersist 콜백이 호출되도록 EntityManager를 플러시하는 예시
@PostPersist 콜백 메서드가 있는 엔터티 리스너가 예제에 사용된 Person 엔터티에 등록되어있음
// ...
@Autowired
JpaPersonRepository repo;
@PersistenceContext
EntityManager entityManager;
@Transactional
@Test
void savePerson() {
// EntityManager#persist(...) results in @PrePersist but not @PostPersist
repo.save(new Person("Jane"));
// Manual flush is required for @PostPersist callback to be invoked
entityManager.flush();
// Test code that relies on the @PostPersist callback
// having been invoked...
}
all JPA lifecycle callbacks.
5.10. Executing SQL Scripts
RDBMS 에 대한 통합 테스트를 작성할 때 SQL 스크립트를 이용하여 db 스키마를 수정하거나 테스트 데이터 등을 테이블에 삽입해야할 경우가 있다. Spring-jdbc module 을 사용하여 Load 될 때 SQL스크립트를 실행할 수 있게 해준다
Embedded Database Support
Testing Data Access Logic with an Embedded Database
한번 테스트 load 에 db를 초기화하는 방법도 있지만, 테스트 중에 ApplicationContext 데이터베이스를 수정할 수 있도록 하는 방법을 제공한다
5.10.1. Executing SQL scripts programmatically
Spring provides the following options for executing SQL scripts programmatically within integration test methods.
- org.springframework.jdbc.datasource.init.ScriptUtils
- org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
- org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
- org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils provides a collection of static utility methods for working with SQL scripts and is mainly intended for internal use within the framework. However, if you require full control over how SQL scripts are parsed and run, ScriptUtils may suit your needs better than some of the other alternatives described later.
ResourceDatabasePopulator provides an object-based API for programmatically populating, initializing, or cleaning up a database by using SQL scripts defined in external resources. ResourceDatabasePopulator provides options for configuring the character encoding, statement separator, comment delimiters, and error handling flags used when parsing and running the scripts. Each of the configuration options has a reasonable default value.
테스트 스키마 및 테스트 데이터에 대한 SQL 스크립트를 지정하고 명령문 구분 기호를 로 설정하는 방법이다
@Test
void databaseTest() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScripts(
new ClassPathResource("test-schema.sql"),
new ClassPathResource("test-data.sql"));
populator.setSeparator("@@");
populator.execute(this.dataSource);
// run code that uses the test schema and data
}
ResourceDatabasePopulator internally delegates to ScriptUtils for parsing and running SQL scripts.
5.10.2. Executing SQL scripts declaratively with @Sql
Spring TestContext Framework 에서 SQL 스크립트를 선언적으로 구성하는 방법이다
@Sql 애노테이션을 이용하여 각각의 SQL문을 구성하거나 SQL 스크립트에 대한 리소스 경로를 구성할 수 있다
Method-level @Sql declarations override class-level declarations by default.
As of Spring Framework 5.2, however, this behavior may be configured per test class or per test method via @SqlMergeMode.
Path Resource Semantics
Test class가 정의된 패키지에 상대적인 클래스 경로 리소스로 처리된다
"/"로 시작될 경우 절대 클래스 경로 리소스로 본다 ("/org/example/schema.sql")
URL을 참조하는 경로(예: 접두사가 classpath:, file:, http:인 경로)는 지정된 리소스 프로토콜을 사용하여 로드된다
@Sql 불러오기
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void emptySchemaTest() {
// run code that uses the test schema without any test data
}
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {
// run code that uses the test schema and test data
}
}
Default Script Detection
If no SQL scripts or statements are specified, an attempt is made to detect a default script, depending on where @Sql is declared. If a default cannot be detected, an IllegalStateException is thrown.
스크립트나 명령문이 지정되지 않은 경우 default 경로에서 감지 시도를 한다
- Class-level declaration: If the annotated test class is com.example.MyTest, the corresponding default script is classpath:com/example/MyTest.sql.
- Method-level declaration: If the annotated test method is named testMethod() and is defined in the class com.example.MyTest, the corresponding default script is classpath:com/example/MyTest.testMethod.sql.
Declaring Multiple @Sql Sets
구문구성, 오류처리 규칙, 실행단계 등이 다른 경우 여러개의 SQL script set을 instance 할 수 있다
you can use @Sql as a repeatable annotation. Otherwise, you can use the @SqlGroup annotation as an explicit container for declaring multiple instances of @Sql.
Java8 버전 에서
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {
// run code that uses the test schema and test data
}
코틀린 또는 다른 JVM언어와 호환하려면 @SqlGroup 을 사용한다
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
void userTest() {
// run code that uses the test schema and test data
}
Script Execution Phases
기본적으로 SQL script는 테스트 전에 실행된다
그러나 메서드 이후에 특정 script 집합을 실행해야하거나(Database status 정리) 할 경우 excutionPhase 특성을 @Sql에 붙여준다
@Test
@Sql(
scripts = "create-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
scripts = "delete-test-data.sql",
config = @SqlConfig(transactionMode = ISOLATED),
executionPhase = AFTER_TEST_METHOD
)
void userTest() {
// run code that needs the test data to be committed
// to the database outside of the test's transaction
}
ISOLATED and AFTER_TEST_METHOD are statically imported from Sql.TransactionMode and Sql.ExecutionPhase, respectively.
Script Configuration with @SqlConfig (ALL 번역기ㅎㅎ..)
@SqlConfig 주석을 사용하여 스크립트 구문 분석 및 오류 처리를 구성할 수 있다.
통합 테스트 클래스에서 클래스 수준 주석으로 선언되면 @SqlConfig는 테스트 클래스 계층 구조 내의 모든 SQL 스크립트에 대한 전역 구성 역할을 한다.
@Sql 주석의 config 속성을 사용하여 직접 선언하면 @SqlConfig는 둘러싸는 @Sql 주석 내에서 선언된 SQL 스크립트에 대한 로컬 구성 역할을 한다.
@SqlConfig의 모든 속성에는 암시적 기본값이 있으며 해당 속성의 javadoc에 문서화되어 있다.
Java 언어 사양의 주석 속성에 대해 정의된 규칙으로 인해 불행하게도 주석 속성에 null 값을 할당할 수 없다.
따라서 상속된 전역 구성의 재정의를 지원하기 위해 @SqlConfig 특성에는 ""(문자열), {}(배열) 또는 DEFAULT(열거)의 명시적 기본값이 있다.
이 접근 방식을 사용하면 @SqlConfig의 로컬 선언이 "", {} 또는 DEFAULT 이외의 값을 제공하여 @SqlConfig의 전역 선언에서 개별 속성을 선택적으로 재정의할 수 있다.
전역 @SqlConfig 속성은 로컬 @SqlConfig 속성이 "", {} 또는 DEFAULT 이외의 명시적 값을 제공하지 않을 때마다 상속된다.
따라서 명시적 로컬 구성이 전역 구성보다 우선한다.
@Sql 및 @SqlConfig에서 제공하는 구성 옵션은 ScriptUtils 및 ResourceDatabasePopulator에서 지원하는 것과 동일하지만 <jdbc:initialize-database/> XML 네임스페이스 요소에서 제공하는 것의 상위 집합이다.
자세한 내용은 @Sql 및 @SqlConfig에서 개별 속성의 javadoc을 참조.
@Sql
@SqlConfig
Transaction management for @Sql
특히 SQL 스크립트는 transactionMode의 구성 값에 따라 기존 Spring 관리 트랜잭션 (예 : @Transactional에서 주석이 달린 테스트의 TransactionalTestExecutionListener에서 관리하는 트랜잭션) 또는 분리 된 트랜잭션 내에서 에서 트랜잭션없이 실행된다. @SqlConfig의 속성과 테스트의 ApplicationContext에 PlatformTransactionManager가 있다.
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
final JdbcTemplate jdbcTemplate;
@Autowired
TransactionalSqlScriptsTests(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
@Sql("/test-data.sql")
void usersTest() {
// verify state in test database:
assertNumUsers(2);
// run code that uses the test data...
}
int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
void assertNumUsers(int expected) {
assertEquals(expected, countRowsInTable("user"),
"Number of rows in the [user] table.");
}
}
there is no need to clean up the database after the usersTest() method is run, since any changes made to the database (either within the test method or within the /test-data.sql script) are automatically rolled back by the TransactionalTestExecutionListener
자동으로 롤백되므로 메서드 실행 후 데이터베이스를 정리할 필요는 없다
Merging and Overriding Configuration with @SqlMergeMode
Spring Framework 5.2부터는 메서드 수준 @Sql 선언을 클래스 수준 선언과 병합할 수 있다.
예를 들어 이를 통해 테스트 클래스당 한 번 데이터베이스 스키마 또는 일부 공통 테스트 데이터에 대한 구성을 제공한 다음 테스트 메서드당 추가 사용 사례별 테스트 데이터를 제공할 수 있다.
@Sql 병합을 활성화하려면 테스트 클래스 또는 테스트 메서드에 @SqlMergeMode(MERGE)로 애노테이션을 추가한다.
특정 테스트 메서드(또는 특정 테스트 하위 클래스)에 대한 병합을 비활성화하려면 @SqlMergeMode(OVERRIDE)를 통해 기본 모드로 다시 전환할 수 있
'Web > spring' 카테고리의 다른 글
[Spring framework testing] 5. Spring TestContext Framework (4) (0) | 2023.02.14 |
---|---|
[Spring framework Web MVC docs] 1.7. CORS (0) | 2023.02.14 |
[Spring framework Web MVC docs] 1.4. Functional Endpoints (0) | 2023.02.12 |
[Spring framework Web MVC docs] 1.2. Filters (0) | 2023.02.11 |
[Spring framework Web MVC docs] 1.12. MVC Config (0) | 2023.02.11 |