Можете ли вы придумать лучший способ загружать DBbUnit только один раз для тестового класса с помощью Spring? - PullRequest
3 голосов
/ 29 ноября 2011

Я понимаю, что передовая практика может посоветовать загрузку тестовых данных для каждого метода @Test, однако это может быть болезненно медленным для DBUnit, поэтому я нашел следующее решение, чтобы загрузить его только один раз для каждого класса:

  1. Загружать набор данных только один раз для каждого тестового класса
  2. Поддержка нескольких источников данных и тех, которые не названы "dataSource" из ApplicationContext
  3. Откат вставленного набора данных DBUnit не строгоrequired

Хотя приведенный ниже код работает, меня беспокоит то, что мой класс Test имеет статический метод beforeClassWithApplicationContext(), но он не может принадлежать интерфейсу, поскольку он статический.Поэтому мое использование Reflection используется не типичным образом.Есть ли более элегантное решение?

/**
 * My Test class
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, DbunitLoadOnceTestExecutionListener.class})
@ContextConfiguration(locations={"classpath:resources/spring/applicationContext.xml"})
public class TestClass {

    public static final String TEST_DATA_FILENAME = "Scenario-1.xml";

    public static void beforeClassWithApplicationContext(ApplicationContext ctx) throws Exception {

        DataSource ds = (DataSource)ctx.getBean("dataSourceXyz");

        IDatabaseConnection conn = new DatabaseConnection(ds.getConnection());

        IDataSet dataSet = DbUnitHelper.getDataSetFromFile(conn, TEST_DATA_FILENAME);

        InsertIdentityOperation.CLEAN_INSERT.execute(conn, dataSet);
    }

    @Test
    public void somethingToTest() {
        // do stuff...
    }
}

/**
 * My new custom TestExecutioner
 */
public class DbunitLoadOnceTestExecutionListener extends AbstractTestExecutionListener {

    final String methodName = "beforeClassWithApplicationContext";

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

        super.beforeTestClass(testContext);        

        Class<?> clazz = testContext.getTestClass();

        Method m = null;

        try {
            m = clazz.getDeclaredMethod(methodName, ApplicationContext.class);
        }
        catch(Exception e) {
            throw new Exception("Test class must implement " + methodName + "()", e);
        }

        m.invoke(null, testContext.getApplicationContext());
    }
}

Еще одна мысль, которую я имел, возможно, заключалась в создании статического одноэлементного класса для хранения ссылки на ApplicationContext и заполнения его из DbunitLoadOnceTestExecutionListener.beforeTestClass().Затем я мог бы получить эту одноэлементную ссылку из стандартного @BeforeClass метода, определенного в TestClass.Мой код выше для обратного вызова каждого TestClass кажется немного грязным.

Ответы [ 2 ]

2 голосов
/ 29 ноября 2011

После полезного отзыва от Мэтта и Дж. Б. это гораздо более простое решение для достижения желаемого результата

/**
 * My Test class
 */
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, DbunitLoadOnceTestExecutionListener.class})
@ContextConfiguration(locations={"classpath:resources/spring/applicationContext.xml"})
public class TestClass {

    private static final String TEST_DATA_FILENAME = "Scenario-1.xml";

    // must be static
    private static volatile boolean isDataSetLoaded = false;

    // use the Qualifier to select a specific dataSource
    @Autowired
    @Qualifier("dataSourceXyz")
    private DataSource dataSource;

    /**
     * For performance reasons, we only want to load the DBUnit data set once per test class 
     * rather than before every test method.
     * 
     * @throws Exception
     */
    @Before
    public void before() throws Exception {

        if(!isDataSetLoaded) {

            isDataSetLoaded = true;

            IDatabaseConnection conn = new DatabaseConnection(dataSource.getConnection());

            IDataSet dataSet = DbUnitHelper.getDataSetFromFile(conn, TEST_DATA_FILENAME);

            InsertIdentityOperation.CLEAN_INSERT.execute(conn, dataSet);
        }       
    }

    @Test
    public void somethingToTest() {
        // do stuff...
    }
}

Класс DbunitLoadOnceTestExecutionListener больше не требуется и был удален.Это говорит о том, что чтение всех причудливых техник может иногда затуманивать ваше собственное суждение: o)

1 голос
/ 29 ноября 2011

Не специалист, но вы не можете вызвать метод экземпляра вашего тестового объекта в prepareTestInstance() после проверки того, что он реализует соответствующий интерфейс, и вызывать этот метод, только если это первый случай prepareTestInstanceвызывается с тестовым экземпляром этого класса.Вам просто нужно сохранить набор уже увиденных классов:

@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
    MyDbUnitTest instance = (MyDbUnitTest) getTestInstance();
    if (!this.alreadySeenClasses.contains(instance.getClass()) {
        instance.beforeClassWithApplicationContext(testContext.getApplicationContext());
        this.alreadySeenClasses.add(instance.getClass());
    }
}
...