Как откатить конкретную запись после теста?

У меня есть тест spock/spring, который изменяет содержимое базы данных, и мне интересно, как откатить эту запись.

В настоящее время я выполняю необработанный оператор sql, сохраняю данные поля и после успешного теста восстанавливаю эти данные. Но я предполагаю, что это можно сделать проще?

@ContextConfiguration(locations = "classpath*:applicationContext-test.xml")
class RepositoryTest extends Specification {

    @Shared sql = Sql.newInstance("jdbc:sqlserver://...")
    ResourceRepository resourceRepository;

    def "Save test"() {
        setup:
        // copy the row before we do changes! we need to restore this later on!
        def row = sql.firstRow("select id, content, status from table-name where id = 12345")

        when:
        ...

        then:
        ..

        cleanup:
        sql.execute("""
                    update table-name set content = ${row.content}, status = ${row.status}
                    where id = ${row.id}
                    """)
    }
}

person Marco    schedule 30.06.2011    source источник
comment
Вы действительно хотите откатить конкретную запись, а не то, что сделал тестовый метод?   -  person Peter Niederwieser    schedule 04.07.2011


Ответы (3)


Лучший способ, который я нашел для этого:

  • начать тест
  • начать транзакцию
  • (необязательно) загрузите любые данные БД, которые требуются тесту, используя что-то вроде DBUnit
  • запустить тест
  • откат транзакции

Обратите внимание, что все операции с БД происходят в рамках одной и той же транзакции. Поскольку эта транзакция откатывается в конце теста (даже если возникает исключение), база данных всегда должна находиться в том же состоянии в конце теста, что и в начале.

Spring предоставляет несколько действительно полезных классов, которые позаботятся о запуске и откате транзакции для каждого теста. Если вы используете Spring и JUnit4 и не возражаете, что ваши тестовые классы должны расширять класс Spring, самый простой вариант, вероятно, — расширить AbstractTransactionalJUnit4SpringContextTests

// Location of the Spring config file(s)
@ContextConfiguration(locations = {"/application-context-test.xml", "classpath*:/application-context-persistence-test.xml"})

// Transaction manager bean name
@TransactionConfiguration(transactionManager = "hsqlTransactionManager", defaultRollback = true)
@Transactional(propagation=Propagation.REQUIRES_NEW)
public class MyTransactionalTests extends AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void thisWillRunInATransactionThatIsAutomaticallyRolledBack() {}
}

Если вы не хотите расширять класс Spring, вместо этого вы можете настроить средство запуска тестов, используя аннотации. Spring также поддерживает многие другие основные среды модульного тестирования и более старые версии JUnit.

person Dónal    schedule 30.06.2011
comment
Поскольку у Spock есть прямая поддержка Spring TestContext framework, вам не нужен ни базовый класс, ни пользовательский бегун. @TransactionConfiguration можно опустить в пользу именования bean-компонента transactionManager. Чтобы класс Groovy Sql работал с транзакциями Spring, вам нужно будет инициализировать его с источником данных, предоставленным Spring. Тот факт, что класс Sql не выполняет преобразование исключений а-ля Spring, может повлиять на то, в каких случаях транзакции откатываются. - person Peter Niederwieser; 06.07.2011

Если вы смешиваете Sql Groovy с аннотациями Spring (@RunWith, @ContextConfiguration, @Transactional, @Rollback, ...) в своих тестах, вы можете захотеть обернуть источник данных с помощью org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy.

<bean id="db-dataSourceReal" 
   class="org.springframework.jndi.JndiObjectFactoryBean">
  <property name="jndiName" value="jdbc/foo" />
  <property name="resourceRef" value="true" />
  <property name="lookupOnStartup" value="true" />
</bean>

<bean id="db-dataSource"
   class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
  <constructor-arg ref="db-dataSourceReal" />
</bean>

Затем используйте TransactionAwareDataSourceProxy в качестве источника данных для Groovy Sql. Например, в вашем тестовом классе (в данном случае с использованием отказоустойчивого плагина maven)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations=[
        "classpath*:applicationContext-test.xml"
])
class SimplePojoDaoIT {

    @Resource(name="dao-pojoDao")
    PojoDao pojoDao

    @Test
    @Transactional("transactionManager")
    @Rollback
    public void testShouldPersistToDB(){

      SomePojo pojo = new SomePojo()
      pojo.with{
        id = 5
        name = 'foo'
      }

      pojoDao.persist(pojo)

      def sql = new Sql(pojoDao.dataSource)
      sql.rows("select * from POJO_TBL where id = :id", [['id':pojo.id]]).each{ row ->
//      println row
        pojo.with{
          assertEquals(id, row.ID.longValue())
          assertEquals(name, row.NAME)
        }
      }
    }
}
person Volt0    schedule 22.12.2011

CREATE TABLE table_name
(
   id        NUMBER,
   content   NUMBER,
   status    NUMBER
);

INSERT INTO table_name
     VALUES (1, 2, 3);

INSERT INTO table_name
     VALUES (4, 5, 6);

INSERT INTO table_name
     VALUES (7, 8, 9);

COMMIT;

Перед запуском теста вы сохраняете строку, полученную в результате этого SELECT, в переменной VARCHAR2(4000), а после теста вам нужно только выполнить строку:

SELECT    'UPDATE TABLE_NAME SET CONTENT = '
       || CONTENT
       || ', STATUS = '
       || STATUS
       || ' WHERE ID = '
       || ID
  FROM TABLE_NAME
 WHERE ID = 1;

В моем предыдущем примере я предположил, что запись для резервного копирования имеет ID = 1. Строка в этом примере содержит следующий оператор UPDATE:

UPDATE TABLE_NAME SET CONTENT = 2, STATUS = 3 WHERE ID = 1
person UltraCommit    schedule 30.06.2011