Почему новый поток не видит тестовые данные, подготовленные основным потоком в транзакционном тесте Spring JUnit? - PullRequest
0 голосов
/ 15 февраля 2019

Я написал тест Junit с помощью Spring-boot-test, в методе test, я сначала подготовил некоторые тестовые данные, которые должны быть сохранены в БД MySQL, затем я вызвал целевой метод, который должен быть протестирован в 100 подпрограммах.потоки, чтобы проверить, хорошо ли работает целевой метод в параллельном режиме.Этот метод теста выглядит следующим образом:

public class SysCodeRuleServiceImplTest extends BaseServiceTest {

    @Autowired
    private SysCodeRuleService sysCodeRuleService;

    @Autowired
    private SysCodeRuleDtlService sysCodeRuleDtlService;

    private final String codeRuleNo = "sdkfjks443";

        @Test
    public void testCreateSheetIdWithoutUniformedSerial_2() throws InterruptedException {
                //------ prepare test data start-----------
        SysCodeRule sysCodeRule = new SysCodeRule();
        sysCodeRule.setCodeRuleNo(codeRuleNo);
        sysCodeRule.setIfDateCode(1);
        sysCodeRule.setPadChar("0");
        sysCodeRule.setSerialDigits(6);
        sysCodeRule.setResetMode(1);
        sysCodeRule.setIfUniteSerial(0);
        sysCodeRule.setIfCache(0);
        sysCodeRule.setConstValue("PETREL");
        sysCodeRule.setStatus(1);
        sysCodeRule.setName(codeRuleNo);
        sysCodeRule.setCurSerialNo("0");
        sysCodeRule.setCurSerialDate(new Date());
        sysCodeRule.setCreateTime(new Date());
        sysCodeRule.setCreator("自动");
        sysCodeRule.setDateCutBeginPosition(3);
        sysCodeRule.setDateCutEndPosition(8);
        boolean insertSysCodeRuleSucc = sysCodeRuleService.insert(sysCodeRule);
        assertThat(TestMessageConstants.PREPARE_TEST_DATA_FAILED, insertSysCodeRuleSucc);
        assertThat("", sysCodeRule.getId(), notNullValue());

        SysCodeRuleDtl sysCodeRuleDtl1 = new SysCodeRuleDtl();
        sysCodeRuleDtl1.setSysCodeId(sysCodeRule.getId() + "");
        sysCodeRuleDtl1.setOrderNo(1);
        sysCodeRuleDtl1.setFieldValue("locno");
        sysCodeRuleDtl1.setCutEndPosition(0);
        sysCodeRuleDtl1.setCutBeginPosition(0);
        sysCodeRuleDtl1.setCreateTime(new Date());
        sysCodeRuleDtl1.setCreator("自动");
        boolean insertDtl1Succ = sysCodeRuleDtlService.insert(sysCodeRuleDtl1);
        assertThat("", insertDtl1Succ);

        SysCodeRuleDtl sysCodeRuleDtl2 = new SysCodeRuleDtl();
        sysCodeRuleDtl2.setSysCodeId(sysCodeRule.getId() + "");
        sysCodeRuleDtl2.setOrderNo(2);
        sysCodeRuleDtl2.setFieldValue("fieldName1");
        sysCodeRuleDtl2.setCutBeginPosition(1);
        sysCodeRuleDtl2.setCutEndPosition(3);
        sysCodeRuleDtl2.setCreateTime(new Date());
        sysCodeRuleDtl2.setCreator("自动");
        boolean insertDtl1Succ2 = sysCodeRuleDtlService.insert(sysCodeRuleDtl2);
        assertThat("", insertDtl1Succ2);
                //------prepare test data end------------------------

                //startLatch used to make sure all task threads start after 
                //prepared test data done
        CountDownLatch startLatch = new CountDownLatch(1);
                //parameters needed by the target method
        Map<String, String> fieldValueMap = new HashMap<>(2);
        fieldValueMap.put("locno", "cangku1");
        fieldValueMap.put("fieldName1", "ABCDEFGH");
                //doneLatch used to make sure all task threads done before the
                //the transaction which started in main thread roll back
        CountDownLatch doneLatch = new CountDownLatch(100);
        for(int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    startLatch.await();
                //this is the target method which i want to test
                    String result = sysCodeRuleService.createSheetIdWithoutUniformedSerial(codeRuleNo, JsonUtils.writeValueAsString(fieldValueMap));
                    if(CommonUtil.isNotNull(result)) {
                        logger.debug(">>>>>>>>>>>>>" + result);
                    }
                } catch(InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    doneLatch.countDown();
                }
            }).start();
        }

                //guarantee the test data truly saved before all task treads 
                //start
        EntityWrapper<SysCodeRule> ew = new EntityWrapper<>();
        ew.eq("code_rule_no", codeRuleNo);
        SysCodeRule codeRule = sysCodeRuleService.selectOne(ew);
        if(codeRule != null) {
            startLatch.countDown();
        }
                //main thread keep waiting until all task threads done their 
                //work
        doneLatch.await();
    }

BaseServiceTest выглядит следующим образом:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = TestApplication.class)
@Transactional
@Rollback
public class BaseServiceTest {
    protected  Logger logger = LoggerFactory.getLogger(BaseServiceTest.class);
}

И подпись целевого метода выглядит следующим образом:

public synchronized String createSheetIdWithoutUniformedSerial(String codeRuleNo, String fieldValuesJson)

в целевом методе он запрашивает данные, которые были сохранены с помощью блока кодов «подготовить тестовые данные», затем выполняет некоторые коды бизнес-логики и затем возвращает результат.Кстати, целевой метод, записанный в «уровне бизнес-сервисов», имеет транзакцию, управляемую Spring AOP, и файл конфигурации транзакции выглядит следующим образом:

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="doReweight" propagation="REQUIRES_NEW"/>
            <tx:method name="doClear*" propagation="REQUIRES_NEW"/>
            <tx:method name="doSend*" propagation="REQUIRES_NEW"/>
            <tx:method name="doBatchSave*" propagation="REQUIRES_NEW"/>

            <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="count*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="list*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config expose-proxy="true" proxy-target-class="true">
        <aop:pointcut id="txPointcut"
                      expression="execution(* com.xxx..service..*+.*(..))"/>
        <aop:advisor id="txAdvisor" advice-ref="txAdvice"
                     pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

Мой ожидаемый результат - синхронизированныйЦелевой метод работает хорошо

Но!В каждом потоке подзадачи целевой метод не может запрашивать подготовленные тестовые данные, подготовленные в основном потоке!

Итак, я запутался и не могу понять, что былоне так? Нужна помощь или советы, я действительно ценю некоторую помощь от вас, ребята, заранее спасибо!

ps: версия для весенней загрузки: 1.5.10.RELEASE, версия Juite:4,12

1 Ответ

0 голосов
/ 17 февраля 2019

При первоначальном взаимодействии с базой данных через SysCodeRuleService вставляются тестовые данные в управляемую тестом транзакцию, которая не зафиксирована в базе данных.

Затем выполняется вызов createSheetIdWithoutUniformedSerial() в SysCodeRuleService.в новой ветке;однако Spring не распространяет транзакции на вновь созданные потоки.Таким образом, вызов createSheetIdWithoutUniformedSerial() выполняется в другом потоке, который не может видеть незафиксированные тестовые данные в приостановленной управляемой тестом транзакции.

Чтобы createSheetIdWithoutUniformedSerial() мог видеть такие тестовые данные в базе данных вновый поток, вам придется зафиксировать тестовые данные в базе данных, прежде чем создавать новые потоки.

Есть несколько вариантов для достижения этого.

Если вы ищете очень низкийНа этом уровне вы можете использовать Spring's TransactionTemplate для программной фиксации базы данных.Это должно работать даже при наличии управляемой тестом транзакции (т. Е. Через @Transactional для класса теста или метода теста).

Если вы хотите выполнить настройку базы данных, специфичную для текущего метода @TestДругой вариант - использовать TestTransaction API.Подробнее см. Управление программными транзакциями в Справочном руководстве Spring Framework.

Если вы хотите выполнить одинаковую настройку базы данных для всех методов тестирования в текущем классе, вы можете ввести @BeforeTransactionметод, который вставляет тестовые данные в базу данных, и метод @AfterTransaction, который удаляет тестовые данные из базы данных.См. Выполнение кода вне транзакции .

Если вы хотите или заинтересованы в переносе настроек тестовых данных в операторы вставки SQL (возможно, во внешний файл), вы можете использовать Поддержка @Sql в Spring .

В качестве примечания можно безопасно удалить объявление @Rollback, поскольку вы фактически переопределяете семантику по умолчанию семантикой по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...