Статьи

Обновление приложения Grails с версии 1.3.7 до версии 2.4.4

Итак, вы хотите обновить свое приложение до Grails 2.4.4  с этой устаревшей версии 1.3.7  сегодня!

Но ждать! В этом процессе вас ждет мало испытаний.

1. Старт


Подумайте: с чего начать обновление? Это довольно сложная задача для корпоративного приложения, на котором может работать весь ваш бизнес. Любая ошибка вводится в процессе, и вы тост ! Но вот идут испытания,  чтобы спасти нас ! Вы можете спросить: «Какие тестыЯ объясню их, я обещаю.

Давайте начнем с некоторой теории и предстоящих проблем!

  • Обновление приложения — это не просто изменение номера версии в  файле application.properties . Это больше, чем это! У вас есть «x» количество плагинов, некоторые из которых вы сделали своими силами, которые обслуживаются из вашего хранилища и размещаются в централизованно работающем  экземпляре Artificatory ! И, конечно же, плагины от Maven  имеют свою собственную историю. Есть еще плагины; помните плагины на месте? Вы должны обновить их также.

  • Затем вы понимаете, что ваш код плохо себя ведет, и вы не имеете ни малейшего представления о том, что произошло! Это означает, что вы знаете, в каком домене работает приложение, но не знаете, какие отношения установлены между объектами разработчиками, и вам нужно убедиться, что эти отношения не повреждены после обновления (здесь вас просят обновить чужое приложение). ).

  • У вас есть различные параметры среды и свойства конфигурации в файлах BuildConfig.groovy  и Config.groovy  . Теперь им потребуется ваше особое внимание, то есть проверка их совместимости.

  • Ваше приложение использует систему Grails i18n  , а управление пользователями осуществляется в соответствии с их языковым стандартом. Вы знаете, что здесь много работы, но будьте уверены, поскольку Grails  и  класс java.util.Locale окажут вам поддержку.

  • У вас есть очень большой  файл UrlMappings.groovy, который содержит огромные записи URL-адресов, и если этого недостаточно, вы получаете их в разных локализованных версиях!

Это были некоторые из проблем, которые я только что упомянул.

2. Подготовка и процесс мышления


Каждая новая версия Grails  предоставляет некоторые новые возможности улучшения и обновление старых или принизить функции , то есть, библиотеки тегов , GSPs , контроллеры  получить некоторые новые улучшения с каждым новым выпуском. Среди этих наиболее заметных улучшений — тесты .

Между версиями Grails 1.3.7  и 2.4.4 произошли довольно серьезные изменения,  например ApplicationHolder , ConfigurationHolder  устарели в 2.2.4, но были удалены в 2.4.4 и т. Д. Поэтому вместо прямого перехода к самой последней версии в то время ( 2.4.4 ), мы начали серию обновлений с 1.3.7  до 2.2.4  и затем до 2.4.4 . Таким образом, мы застраховали, что наше приложение не сломалось, и таким образом спасли нас от бедствий

Приложение, которое мы только что обновили, имело почти 100% тестовое покрытие, которое включало модульные , интеграционные  и функциональные  тесты. Вот несколько вещей, которые следует учитывать при работе с предыдущими письменными тестами:

  • Что ж, вещи постоянно меняются, как и наши инфраструктуры тестирования, однако их работа не меняется, благодаря  концепции Abstraction , которая позволяет нам продолжать использовать что-то независимо от внутренней работы и использовать тот же интерфейс  и синтаксис , но не семантику , конечно.

  • Вы не должны на  любой цене изменить утверждение из испытаний  (или спецификацию ,  как мы говорим , их Спки ), являются ли этот блок , интеграция  или функциональная . Если тесты не пройдены, то при обновлении возникает какой-то недостаток, или код предыдущей версии просто больше не работает (ниже я покажу пример с этим). Этот момент чрезвычайно важен, чтобы иметь обновленное приложение, которое работает как надо ( вы отметите мои слова на нем )!

  • Я слышал, что у нас  также есть функциональные тесты! «Кто они такие?» Вы можете спросить. Это  тесты Selenium, работающие под прикрытием Geb , для автоматизации браузера, так что вам не нужно каждый раз проверять его вручную, например, Работает ли вход в систему по-прежнему? Просто выполните тест Geb, и он проверит вас. Еще одно замечание: эти тесты работают только с конкретными версиями браузеров. В нашем случае нам пришлось использовать Firefox 32.0.1 (вы также можете использовать Chrome). Конечно, это полностью зависит от версии библиотеки Selenium и драйвера .

3. Обновление


Итак, пришло время обновить приложение, оставив позади свои наблюдения, но придерживаясь их все время.

1. Первая вещь первая

Здесь я опишу начальные шаги, которые необходимо предпринять для обновления приложения. Это важно, и никто не должен терять объяснение.

  • Возможно, вы захотите начать с настройки значений в файлах Config.groovy  и DataSource.groovy  . Ну, вместо того , чтобы модифицировать эти файлы , вам предлагается указать свой собственный внешний конфигурационный файл , как мой-Config.groovy  и добавить его в grails.config.locations  собственности в Config.groovy. Конфигурация в этом внешнем файле переопределит вашу конфигурацию в файлах Config.groovy и DataSource.groovy. Я рекомендую внешний конфигурационный файл, потому что каждый может иметь свои собственные настройки в одном файле. Конечно, этот файл не должен быть зафиксирован в хранилище и должен быть в списке игнорирования VCS . Это хорошая идея. Это не?

  • Одна наиболее заметная конфигурация, которую я нашел полезной, состояла в том, чтобы включить перезагрузку изменений GSP. Grails обычно выполняет свои функции автоматически, но в моем случае это сломалось. Если это также относится и к вам, попробуйте добавить эти свойства во внешний файл конфигурации:

    grails.gsp.enable.reload = true
    grails.reload.enabled = true

  • Затем мы должны начать обновление зависимостей в нашем  файле BuildConfig.groovy . Найдите зависимости от репозитория Maven, которые совместимы с текущим  выпуском Grails , каталог плагинов Grails  поможет в этом. По сути, здесь мы пытаемся добиться того, чтобы при обновлении приложения были разрешены все наши зависимости и плагины, чтобы любой код, использующий их, не сломался / не умер во время компиляции.

2. Исправление тестов

Как я уже говорил ранее, я бы поговорил о тестах и ​​объяснил, зачем они так нужны. Я не буду говорить конкретно о TDD, так как код и тесты уже были написаны заранее.

  • Модульные тесты

Их не так много, хотя они не могут обещать полную целостность приложения, потому что они используются для тестирования только единицы работы, например, методов  и классов . И я сказал, что, поскольку есть поговорка — покрытие 100% модульных тестов не гарантирует работающее приложение, однако ваши модули будут работать индивидуально . Независимо от этого, модульные тесты являются необходимостью .

В Grails модульные тесты в основном поддерживаются Споком. Работа тестов с версии 1.3.7 до 2.4.4 сильно изменилась как по синтаксису,  так и по семантике . Поэтому для исправления тестов мы должны использовать новые аннотации  и поведение, предоставляемые инфраструктурой тестирования, и вы готовы к работе. Например, чтобы протестировать  класс Service, скажем, MyService.groovy,  вы должны сделать следующее:

// Class under test
class MyService {
def someMethod() { return "returning a string." }
}

// Test written with version 1.3.7
import grails.plugin.spock.Unitspec

class MyServiceUnitSpec extends UnitSpec {
    def myService

    def "testing someMethod"() {
        when:
        def result = myService.someMethod()
      then:
      "returning a string." == result
}
}

// Now same test written with version 2.4.4
import spock.lang.Specification

@TestFor(MyService)
class MyServiceUnitSpec extends Specification {
def "testing someMethod"() {
when:
def result = service.someMethod() // Notice the service object!
then:
         "returning a string." == result
    }
}

Как мы видим здесь, мы обновили наш тест до новой версии. Для этого нам пришлось изменить семантику, изменив способ, которым мы сейчас подходим к соответствующему тестируемому классу.

  • Интеграционные тесты

Вы можете выполнить те же шаги, что и выше, для модульных тестов . Как мы все знаем, интеграционный тест запускает определенную функцию в целом. Таким образом, они ведут себя более или менее как модульные тесты.

  • Функциональные тесты

Функциональные тесты сильно отличаются от наших модульных и интеграционных тестов как по функциональности, так и по реализации.  Для реализации этих тестов мы используем отдельный вызов библиотеки Geb. Geb в основном обрабатывает страницы, которые отображаются в пользовательском интерфейсе. Это помогает нам автоматизировать поведение страницы, например, открывается ли это всплывающее окно с определенным содержимым внутри, если мы нажимаем эту кнопку, не заполняя нужное текстовое поле на странице поиска продукта? Подобные вопросы нужно проверять вручную / отвечать каждый раз, когда мы меняем наш код, и именно здесь функциональные тесты блестят. Тесты Geb требуют некоторого дополнительного времени и должны обновляться всякий раз, когда меняется наша логика представления, но инвестиции — это будущее!

Имейте в виду, что для проверки правильности работы функциональных тестов нам нужно запустить приложение, и если что-то не работает в тесте, это либо ошибка в коде, либо в самом тесте. Чтобы определить, где лежит ошибка, вот несколько быстрых советов:

  1. Отсутствует ли какой-либо элемент на странице или его  атрибут id  или class изменился, и вы не можете идентифицировать элемент? Это может быть связано с изменением нашего определения правила CSS или рефакторинга.

  2. Мы тестируем любой div  или span  для видимости? Это общий источник неудачных тестов, поскольку требуемое значение не просто заполняется внутри тестируемого элемента.

  3. Иногда пропущенный  метод waitFor вызывает сбой теста.

  4. Вы должны использовать Browser.via () вместо Browser.to () , если ваша страница использует перенаправление.

Это были некоторые рекомендации, но ни одно из них не было связано с процессом обновления нашего приложения. Итак, давайте обратимся к ним сейчас:

  1. Самая первая проблема, с которой мы обычно сталкиваемся, — какие данные передаются из каждого controller.action  в представление. Прежде чем мы исправим это, мы должны решить еще одну проблему. Так как может быть много переадресаций controller.action, довольно сложно найти правильную версию controller.action, которая выполняется прямо сейчас. Кроме того, эти controller.action были сильно отображены с помощью  файла UrlMappings.groovy . Видишь проблему? Поэтому одним из решений, которое я нашел, было создание фильтра,  который бы перечислял каждый контроллер , действие , параметры и результирующую модель для каждого вызова / запроса controller.action.

Конечно, я мог бы использовать уже созданный фильтр, но это было бы излишним ! Итак, я только что создал новый фильтр  следующим образом:

// Prints every route and associated data
class AppDebugFilters {
    def filters = {
        all (controller: '*', action: '*') {
            before = {
                log.debug "..........AppInfoFilters Start........"
                log.debug "$controllerName/$actionName : $params"
            }
            after = { Map model ->
              log.debug "$model"
            log.debug "..........AppInfoFilters End.........."
            }
            afterView = { Exception e ->
            // Don't need it
            }
        }
    }
}

Осторожно!!!  Убедитесь, что вы удалите этот фильтр при развертывании на производстве, так как он будет распечатывать каждую деталь в журналах!

2. Теперь мы можем определить, какой URL  попадает в приложение. Но другой момент: что если в вашем тесте не получится какое-то утверждение? Как я уже сказал, это тот случай, когда ваш текущий код больше не работает после обновления. Предположим, у вас есть следующее положение в GSP, и в нашем тесте Geb мы подсчитываем элементы на основе примененного класса CSS:

// BabyController.groovy
class BabyController {
def babbies() {
def baby = new Baby()
      [newBornBaby: baby] // There could be other parameters also e.g., all the babbies in system currently.
    }
}

// babbies.gsp
<g:render template="list" model="[baby:newBornBaby]" />

// _list.gsp
<g:render template="baby" />

// _baby.gsp
<g:set var="clazz" value="${baby ? 'cute' : 'very cute'}" />

Can you guess now what would be the effect of using baby inside _baby.gsp, which was not explicitly passed to it and the value of the clazz variable? Here is the answer: clazz will always contain cute as its value. Why? Because baby model key was defined to be a Baby object, and in this case it’s never null. You got the point, right?

So, what’s the solution? It’s as simple as that you pass your models explicitly as follows:

// _list.gsp
<g:render template="baby" model="[baby: false]" />

3. This step discusses configuring the Geb itself so that you would be in position to test the app manually when browser opens, as normally browsers are tend to get shutdown after a failed or successful test. So make sure to never add a quit() method call, as follows:

FirefoxDriver driver

def setupSpec() {
driver = new FirefoxDriver(new FirefoxProfile())
}

def cleanupSpec() {
driver.quit() // Never do this!
}

Even if this driver.quit() line is written somewhere, then just comment it out. The importance of removing this statement is it would preserve the state of the browser session and you would be able to manually inspect the elements on the page. You would also be able to answer the questions like, if there is something wrong going on with the AJAX call (which is made on click of a button), by just checking it inside the browser console.

4. Conclusion/Final Result/Advice


So it was all about testing and upgrading (okay, not everything!). To upgrade an app is really not a simple task, and when you don’t have idea about the code but except the logic, situation becomes really complex. In this case you are just reliant on tests, whether they are unit, integration or functional.

You may be thinking that I’m little biased towards testing — and I am. To test the app or some feature every time manually is not something that I say is a wise decision. Suppose I changed some code in one part of app. Now how can I be sure if some other module in the app is not affected due to this change, or that the app is affected altogether? Just automate the things using tests and you are good to go. There were situations when I modified some code to get past a test but due to the change I made some other tests started to fail. This way I didn’t have to test the app again (manually), instead tests provided me feedback. Seeing the power of tests!

People usually say they have 100% unit tests, and should they still need integration and functional tests? And answer is YES. Unit tests only guarantee that every unit would work fine. As an app works with individual units combined together they should be tested as a group. And this is where integration tests helps us. So then why functional tests? Functional tests are necessary to see if software is working as the user is asking it to. Integration tests are just to ensure all units are working together to produce desired results, and functional tests are just to ensure software is working as per the user requirements and desired behavior.

If someone comes here and asks me if their app will be working fine if I’ve all the tests (unit, integration and functional) passing, my experience says, YES!

So, this was about the upgrading of a Grails app. This might not be the complete checklist but you got the idea where to tackle the problem and come up with a solution.

Thanks for joining in the conversation. 


PS: You can find the Markdown version of this article at https://gist.github.com/ManvendraSK/c8c9035e92cd5ec34ca2.