В процессе создания приложения, будь то из-за сокращения сроков, отсутствия процесса или лени, тесты становятся запоздалой мыслью. Тесты часто добавляются при обнаружении проблем или для устранения ошибок, которые не были обнаружены на ранних этапах.
Эта статья, второй день нашего проекта, покажет, как написать надежную основу для вашего приложения с тестами в качестве надежной основы. Наши первые встречи определили масштаб и направление, которого мы хотели достичь для этого короткого проекта. У нас было четкое представление о проблеме, которую мы хотели решить, а также об основных компонентах, которые мы собирались реализовать.
В тестировании кода есть несколько циклов, обычно используемых в корпоративном сообществе. Мы начнем наше руководство по тестированию с разговоров о ложных показаниях .
Mocks
Страница Wikipedia на объекте Mock описывает их просто:
В объектно-ориентированном программировании фиктивные объекты — это моделируемые объекты, которые имитируют поведение реальных объектов контролируемыми способами.
В Java для использования объектов Mock обычно требуется использование интерфейсов. Надеемся, что интерфейсы — это то, что возникло в результате первоначальных обсуждений проекта и стало основой для ожидаемых функций и поведения.
Инструментом, который мы используем для создания Mocks, является EasyMock и JUnit для модульных тестов. В поисках лучшего инструмента тестирования я настоятельно рекомендую вам попробовать другие инструменты, такие как TestNG и JMock .
Самым продолжительным инструментом модульного тестирования в мире Java, безусловно, является JUnit. Если вы хотите более подробно ознакомиться с разработкой на основе тестирования и каждой из концепций, изложенных здесь, более подробно, то разработка на основе тестирования на примере — это блестящая книга, написанная Кентом Беком по этой теме. Разработка, управляемая тестами, стоит особняком в теории Agile как наиболее эффективная проповедуемая ценность, особенно когда она выполняется рано и делается всесторонне.
Сейчас у нас есть несколько интерфейсов, которые пришли из первоначального обсуждения того, как мы представляем себе работу пастбина, так что же будет следующим шагом? И как тестирование может помочь нам?
Вот основной интерфейс сервиса:
public interface PasteService { List getLatestItems(String clientToken, int count, boolean threaded) throws InvalidClientException; PasteItem getItem(String clientToken, long id) throws InvalidClientException; PasteItem findPrivateItem(String clientToken, String privateToken) throws InvalidClientException; List findItemsByLanguage(String clientToken, LanguageType languageType, int count, boolean threaded) throws InvalidClientException; long createItem(String clientToken, PasteItem item) throws InvalidClientException; long createReplyItem(String clientToken, PasteItem item, long parentId) throws InvalidClientException, ParentNotFoundException; List getItemsForUser(String clientToken, String userToken) throws InvalidClientException; PasteStats getStats(String clientToken) throws InvalidClientException; }
Это первый шаг в дизайне, теперь у нас есть кое-что, описывающее желаемую функциональность, поэтому мы начнем с насмешек над этими интерфейсами.
EasyMock — удобный инструмент для этого, поэтому мы просто добавляем необходимые кусочки в наш файл pom и перезагружаем проект, чтобы получить правильные зависимости:
<dependency> <groupId>org.easymock</groupId> <artifactId>easymock</artifactId> <version>2.4</version> </dependency>
<dependency> <groupId>org.easymock</groupId> <artifactId>easymockclassextension</artifactId> <version>2.4</version> </dependency>
Мы также внесли некоторые изменения в первоначальный POM, который мы создали, чтобы использовать JUnit версии 4.5. Более новые версии JUnit имеют обратную совместимость и добавляют улучшения в ваше тестирование, такие как аннотации.
Для наших первых макетов мы начнем с расширения стандартного TestCase JUnit . Мы добавили некоторые удобные методы, чтобы помочь нам вернуть поддельный Дао и поддельный Сервис.
private PasteItemDao dao; private PasteService svc; @Test public void testMocks() { dao = getPasteItemDao(); assertNotNull(getPasteItemDao()); svc = getPasteService(); assertNotNull(getPasteService()); } @Test public void testGetLatestItems() throws Exception { svc = getPasteService(); assertNotNull(getPasteService()); expect(svc.getLatestItems("CLIENT", 10, false)).andReturn(getPastes(10, LanguageType.PASCAL, false)); replay(svc); List returnedList = svc.getLatestItems("CLIENT", 10, false); verify(svc); assertEquals(10, returnedList.size()); }
То, что мы здесь делаем, — это, по сути, описание ожидаемых нами звонков, мы воспроизводим и вызываем смоделированную службу и, наконец, проверяем и утверждаем, что результаты соответствуют ожидаемым.
Хотя это может показаться довольно очевидным, это поможет нам сравнить то, что должна вернуть реализация, что код имеет покрытие и макеты этих классов действительно ведут себя, как и ожидалось. Как только у нас будет полное фиктивное покрытие, мы можем начать реализацию кода и продолжать возвращаться к нашим тестам, чтобы убедиться, что все работает так, как ожидалось.
Интеграционные и внедренческие тесты
Как только мы получим работающую реализацию, покрытую нашими фиктивными тестами на ожидаемое поведение, следующим шагом для рассмотрения являются интеграционные тесты . Интеграционные тесты включают более полный тест с участием сторонних компонентов, таких как база данных или сложные сервисы. Основное внимание здесь уделяется отслеживанию потенциальных ошибок в нашем проекте, например, файлов конфигурации, картографирования Дао, проблем сущностей и т. Д. Инструмент, который мы считаем хорошо продуманным и лучшим в своем роде, поможет нам настроить полный испытательный стенд, чтобы включить базу данных в памяти ,
Unitilsобъединяет несколько общих инструментов, используемых по отдельности, в целостную интеграционную среду тестирования. Он обеспечивает возможность запуска контекста Spring и базы данных в памяти, которую мы можем использовать для выполнения полных тестов жизненного цикла всех компонентов. Эти инструменты позволяют нам писать тесты, которые будут проверять поведение наших приложений в реальной жизни. Значение здесь можно увидеть позже в проекте, так как на него будут ссылаться в течение всего жизненного цикла нашего проекта. Мы можем использовать их, чтобы выявлять возможные ошибки, добавлять функции, не беспокоясь о том, чтобы сломать другие части нашего приложения, или просто поддерживать и реорганизовывать базу кода. Еще одно преимущество скрыто, поскольку оно помогает новым разработчикам проекта понять, как все связано друг с другом без необходимости отдельного, отдаленного и, возможно, редко обновляемого документа.
Мы выслеживаем конфигурационный файл Spring наших приложений «applicationContext.xml», делаем его копию и помещаем в «src / test / resources». Кроме того, мы добавляем вторичный файл конфигурации «applicationContext-test.xml», который переопределяет источник данных по умолчанию и предоставляет источник данных Unitils.
ApplicationContext-test.xml
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean id="dataSource" class="org.unitils.database.UnitilsDataSourceFactoryBean"/> </beans>
Затем мы добавляем файл «unitils.properties», где определяем, как должен вести себя unitils.
# Properties for the PropertiesDataSourceFactory database.driverClassName=org.hsqldb.jdbcDriver database.url=jdbc:hsqldb:mem:mysticpaste-test database.userName=sa database.password= # This property specifies the underlying DBMS implementation. Supported values are 'oracle', 'db2', 'mysql', 'hsqldb' and 'postgresql'. # The value of this property defines which vendor specific implementations of DbSupport and ConstraintsDisabler are chosen. database.dialect=hsqldb ## This property specifies the database schema that is used. This schema name is used to qualify all tables when (amongst # others) clearing / dropping tables / inserting test data. # NOTE: schema name is case sensitive database.schemaName=mysticpaste dbMaintainer.generateDTD.enabled=TRUE # DbUnit database DTD file path dtdGenerator.dtd.filename=src/resources/test.dtd
Теперь этот файл позволит нам перейти к реализации, которую мы начинаем с AbstractBaseTest, где мы расширяем класс Unitils под названием UnitilsJUnit4.
public class AbstractIntegrationTest extends UnitilsJUnit4 { @SpringApplicationContext({"applicationContext.xml", "applicationContext-test.xml"}) private ApplicationContext applicationContext; /** * Method testDoNothing ... */ //This is to keep maven surefire quiet. @Test public void testDoNothing() { //Nothing } }
Если вы запустите тест, вы увидите в выходных данных журнала полный вызов весеннего контекста, настройку базы данных и проверку сопоставленных сущностей, довольно изящный для пустого теста!
Теперь мы можем перейти к тестированию реальных реализаций, предоставляемых в среде полного цикла.
Поскольку мы используем Unitils и его утилиты Spring, у нас также есть удобные аннотации для внедрения наших реальных ошибок и их реализаций в наши тесты.
Наш последний тестовый класс, который полностью протестирует нашу среду, выглядит следующим образом:
public class PasteServiceIntegrationTest extends AbstractIntegrationTest { @SpringBeanByType private PasteService svc; @SpringBeanByType private PasteItemDao dao; // We start with verifying that the DB is correctly setup. @Test public void testMapping() { HibernateUnitils.assertMappingWithDatabaseConsistent(); } @Test public void testGetCurrentCount() { PasteItem paste = new PasteItem(); paste.setContent("TEST-DATA"); paste.setUserToken("USER"); paste.setClientToken("CLIENT"); try { svc.createItem("CLIENT", paste); assertTrue(svc.getItemsForUser("CLIENT", "USER").size() == 1); } catch (InvalidClientException e) { e.printStackTrace(); //TODO } } @Test public void testCreateAndRetrieve() throws InvalidClientException { PasteItem paste = new PasteItem(); paste.setContent("TEST-DATA"); paste.setUserToken("USER"); paste.setClientToken("CLIENT"); Long id = svc.createItem("CLIENT", paste); System.out.println(id); PasteItem item2 = svc.getItem("CLIENT", id); assertEquals(item2.getClientToken(), paste.getClientToken()); } }
Вуаля! У нас есть полный тест жизненного цикла. Эти тесты, конечно, работают несколько медленнее, чем макеты. Как инструменты и средства для быстрой разработки, они весьма ценны и помогут найти неуловимые ошибки и проблемные вопросы.
Mystic Coders, LLC занимается веб-магией с 2000 года. Mystic — агентство по разработке программного обеспечения с полным набором услуг, специализирующееся на разработке корпоративных приложений на Java. Они обычно участвуют в разработке программного обеспечения корпоративного уровня для крупных и малых компаний и имеют опыт работы в различных отраслях, включая проекты b2b, b2c и государственные проекты. Mystic работала с крупными компаниями, такими как LeapFrog, Nestlé, Harrah’s Entertainment и Лос-Анджелесское бюро конвенций и посетителей, среди других. Эндрю Ломбарди, технический директор Mystic, может выступать с речами.
Для получения дополнительной информации о Mystic, проверьте нас на http://www.mysticcoders.com