Статьи

Требует ли Groovy больше тестов, чем Java-код?

Многие из жалоб, которые я слышал в отношении Groovy и Grails, связаны с одной и той же проблемой: компилятор не распознает ошибки типа. Люди беспокоятся о том, что простые опечатки превратят их в рабочую среду и что они будут менее производительными из-за исключений MissingMethod и MissingProperty, возникающих при запуске приложения.

Стандартный ответ на этот вопрос прост: напишите тесты для своего кода. На самом деле, предпочитайте тестовую (или сначала тестовую) разработку. И все же это не удовлетворяет некоторых людей. «Какие проекты, которые вы знаете, имеют 100% тестовое покрытие?» Один или два, которые близки к 100%. Но надежны ли остальные? Нет, даже на основе Java. Любой код, который не был протестирован, имеет значительную вероятность ошибок. Как это может быть серьезным аргументом против динамических языков?

Это не значит, что я думаю, что динамические языки должны вытеснить статические языки. У обоих есть свое место, и решение о том, какой из них использовать для решения какой-либо задачи, часто зависит от личных предпочтений. У них обоих есть свои сильные и слабые стороны, но обсуждение их придется провести в другой раз.

Слишком много испытаний!

Еще один аргумент, который я слышал, состоит в том, что динамические языки требуют большего тестирования, потому что вы должны явно проверять ошибки типов, которые иначе были бы обнаружены компилятором. Я думаю, что это откровенно мусор. Что мы делаем, когда тестируем? Мы проверяем, что код ведет себя так, как ожидалось, при определенных входных данных. Если в тестируемом коде есть ошибки типа, вы не увидите ожидаемого поведения.

Позвольте мне продемонстрировать. Скажем, у нас есть контроллер Grails MyController с соответствующим модульным тестом. Тест начинается просто:

package org.example

class MyControllerUnitTests extends grails.test.ControllerUnitTestCase {
    void testIndex() {
        controller.index()
    }
}

Все, что мы делаем, это вызываем действие index на тестовом контроллере. Теперь посмотрим, что произойдет, когда мы запустим тест для этого кода контроллера:

package org.example

class MyController {
    def index = {
        def id = parms.id
        render "You have reached ID ${ids}"
    }
}

Если вы запустите тест в SpringSource Tool Suite , вы увидите что-то вроде этого:

Как вы, вероятно, можете сказать, тест не пройден, поскольку переменная parms не существует. Не удивительно, так как это была преднамеренная опечатка. Как только вы исправите эту опечатку, тест не пройдёт по следующему, идентификаторам . Измените это на id, и тест сейчас проходит.

Таким образом, хотя тест просто выполняет действие и даже не проверяет результаты, мы получаем отзывы о отсутствующих свойствах и методах. Конечно, обратная связь не так мгновенна, как в среде IDE со статически типизированным языком, но, по моему опыту, это не огромный удар по производительности. И хотя мы находимся в IDE, я знаю, что и STS, и IntelliJ IDEA будут подчеркивать свойства и методы, которые он не может разрешить, поэтому вы, вероятно, заметите, что parm и id были напечатаны неправильно перед запуском теста.

Это на самом деле мало что значит для моего аргумента, потому что я только что показал, что вам нужен «дополнительный тест», чтобы получить те же проверки, которые вы получаете со статическим языком. Но помните, что цель теста — убедиться, что код ведет себя так, как должен, поэтому мы должны проверить, что действие отображает ожидаемую строку! Это легко сделать:

package org.example

class MyControllerUnitTests extends grails.test.ControllerUnitTestCase {
    void testIndex() {
        controller.params.id = 10
        controller.index()

        assertEquals "You have reached ID 10", mockResponse.contentAsString
    }
}

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

Как насчет проверки типов?

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

def n = 100
n = n.substring(1)

что вы можете увидеть как ошибку типа (число обрабатывается как строка), но что касается языка, все, что у нас есть, — это отсутствующий метод для значения n . Это важно, потому что вы могли бы очень хорошо добавить метод substring () к целым числам, в этом случае код будет работать:

Integer.metaClass.substring = { start ->
    return delegate.toString().substring(start)
}

def n = 100
n = n.substring(1)

Другими словами, «ошибки типа» в динамическом языке — это просто отсутствующие исключения метода или свойства, которые, как я уже продемонстрировал, будут покрыты вашими обычными тестами (без каких-либо дополнительных махинаций).

Я бы остановился на этом, но это еще не все, что касается Groovy. Ведь у него есть статические типы! Он также часто взаимодействует с (статически типизированным) кодом Java. Как это влияет на наше тестирование? Ах, я бы хотел, чтобы был простой ответ на это. В некотором смысле, передача неверного типа в метод будет подобрана. Попробуйте запустить этот скрипт:

def someMethod(String str) {
    println "Some method: $str"
}

someMethod(100)

Boom! Вы получите исключение отсутствующего метода, потому что Groovy не может найти экземпляр someMethod (), который принимает целое число. Модульные тесты могут обрабатывать подобные случаи, но на практике такие проблемы поднимают голову, когда вы начинаете соединять ваши объекты вместе, и они начинают взаимодействовать друг с другом. В этот момент вы должны начать думать об интеграционных тестах — тема для другого времени.

Я тебя убедил?

Основная идея этого поста в блоге заключалась в том, чтобы подчеркнуть, что ваши стандартные модульные тесты на болоте, которые вы можете легко запустить из вашей IDE, подберут те опечатки и «ошибки типа», которые вас беспокоят, без необходимости выполнять дополнительную работу. , Все, что вам нужно сделать, это сосредоточиться на написании тестов, которые проверяют поведение вашего кода. Писать подробные тесты нетривиально, но это не зависит от языка, который вы используете.

Что-то еще, что вы должны иметь в виду, это то, что намного проще тестировать код, написанный на динамическом языке, потому что вам не всегда нужно создавать объекты правильного типа — они просто должны иметь соответствующие методы и свойства. Таким образом, динамические языки настоятельно рекомендуют вам применять передовой опыт и упрощают его.

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

Из поста в блоге автора