2013-10-15 3 views
2

Это простой класс домена в приложении Grails:Стандартные валидаторы домена Grails: следует ли тестировать или нет?

class User { 
    String username 

    static constraints = { 
     username unique: true 
    } 
} 

Мой вопрос: я должен написать модульный тест, чтобы проверить, если поле имени пользователя является уникальным?

@Test 
void cannotCreateMoreThanOneUserWithTheSameUsername() { 
    new User(username: 'john').save() 

    def secondUser = new User(username: 'john') 
    assert !secondUser.validate() 
} 

Я в сомнении, потому что:

  • Если я пишу класс пользователя в соответствии с принципами TDD я должен написать неудачу тест перед внедрением ограничений, закрытие.

  • С другой стороны, установка уникального ограничения в домене скорее представляет собой конфигурацию модели данных, чем истинную логику. Более того, методы сохранения и проверки выполняются в рамках.

ответ

1

Я бы сосредоточил ваше тестирование на областях, которые могут пойти не так, а не пытаться получить 100% -ный охват.

Учитывая это, я избегаю тестирования всего, что просто объявлено. Нет никакой логики для вас, чтобы сломать, и любой тест будет просто повторять декларацию. Трудно понять, как это избавит вас от случайного нарушения этой функциональности.

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

+0

Это проблема с такими вопросами на SO. ОП не хотел испытывать ограничения. Поэтому он выбрал ответ, который позволил ему успокоиться. Нет никакого реального правильного неправильного ответа, кроме как сказать, что чем больше тестирование, тем лучше. – Gregg

+0

@Gregg Если вы протестировали абсолютно все, я думаю, у вас будет меньше ошибок. Но какой ценой? Таким образом, в конце концов, это всегда сводится к компромиссу, который может решить только ОП, если мы не сможем точно определить целую кучу результатов бизнес-уровня, таких как время выхода на рынок, репутация, стоимость ошибки и т. Д. –

+0

@ Gregg проблема заключается в поддержании тестового кода (у меня почти все ограничения проверены в моих небольших проектах). И речь идет о философии/идее и лучших практиках. – promanski

6

На мой взгляд, модульные тесты CRUD-методов не стоят времени, так как разработчики Grails уже полностью протестировали их. С другой стороны, ограничения тестирования модулей важны, поскольку ограничения могут меняться в течение жизненного цикла вашего приложения, и вы хотите убедиться, что вы их заметили. Вы никогда не знаете, какая бизнес-логика может потребоваться изменить для поддержки указанных изменений. Я хотел бы использовать Спока это и типичный тест ограничение будет выглядеть примерно так:

@TestFor(User) 
class UserSpec extends ConstraintUnitSpec { 

    def setup() { 
    mockForConstraintsTests(User, [new User(username: 'username', emailAddress: '[email protected]')]) 
    } 

    @Unroll("test user all constraints #field is #error") 
    def "test user all constraints"() { 
    when: 
    def obj = new User("$field": val) 

    then: 
    validateConstraints(obj, field, error) 

    where: 
    error  | field     | val 
    'blank' | 'username'   | ' ' 
    'nullable' | 'username'   | null 
    'unique' | 'username'   | 'username' 
    'blank' | 'password'   | ' ' 
    'nullable' | 'password'   | null 
    'maxSize' | 'password'   | getLongString(65) 
    'email' | 'emailAddress'  | getEmail(false) 
    'unique' | 'emailAddress'  | '[email protected]' 
    'blank' | 'firstName'   | ' ' 
    'nullable' | 'firstName'   | null 
    'maxSize' | 'firstName'   | getLongString(51) 
    'blank' | 'lastName'   | ' ' 
    'nullable' | 'lastName'   | null 
    'maxSize' | 'lastName'   | getLongString(151) 
    'nullable' | 'certificationStatus' | null 
    } 
} 

Вот базовый класс ConstraintUnitSpec:

abstract class ConstraintUnitSpec extends Specification { 

    String getLongString(Integer length) { 
    'a' * length 
    } 

    String getEmail(Boolean valid) { 
    valid ? "[email protected]" : "[email protected]" 
    } 

    String getUrl(Boolean valid) { 
    valid ? "http://www.google.com" : "http:/ww.helloworld.com" 
    } 

    String getCreditCard(Boolean valid) { 
    valid ? "4111111111111111" : "41014" 
    } 

    void validateConstraints(obj, field, error) { 


    def validated = obj.validate() 

    if (error && error != 'valid') { 
     assert !validated 
     assert obj.errors[field] 
     assert error == obj.errors[field] 
    } else { 
     assert !obj.errors[field] 
    } 
    } 
} 

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

+0

Спасибо за интересный ответ. Я не спрашивал о тестах CRUD. Я спросил о проверке валидатора. Разве такой тест не повторяет реализацию? Я имею в виду, если вы забыли что-то изменить в коде (скажем, blank: false), вы также можете забыть обновить свои тесты. В чем разница? – promanski

+1

Если вы используете метод, описанный мной, и вы забудете добавить ограничение, тест завершится с ошибкой. Образец тестирования выше фактически проходит, если проверка не выполняется. Это ключ. Вы тестируете, чтобы убедиться, что ваш домен будет инициировать ошибки ограничения (проверки). – Gregg

+0

@Gregg Был ли это блог, из которого вы его получили? http://www.christianoestreich.com/2012/11/domain-constraints-grails-spock-updated/ –

0

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

@Test 
void usernameIsUnique() { 
    def mock = new ConstraintsMock() 
    User.constraints.delegate = mock 
    User.constraints.call() 
    assert mock.recordedUsernameUniqueConstraint == true 
} 

class ConstraintsMock { 
    Boolean recordedUsernameUniqueConstraint = null 

    def username = { Map m -> 
     recordedUsernameUniqueConstraint = m.unique 
     assert m.unique 
    } 
} 

Это очень наивный образец для испытаний. Это скорее проверка реализации, чем поведение, что, на мой взгляд, bad. Но действительно ли это отличается от тестового образца в вопросе?

Прежде всего: какую логику мы хотим проверить? Какова истинная логика закрытия ограничений? Это просто вызов динамических методов gorm для каждого поля, которое мы хотим настроить, и передачи конфигурации в качестве параметра. Так почему бы просто не назвать это закрытие в тесте? Почему я должен вызвать метод save?Почему я бы назвал метод проверки gorm? С этой точки зрения прямое закрытие ограничений в модульных тестах кажется не такой уж плохой идеей.

С другой стороны, в чем разница между закрытием ограничений и закрытием конфигурации в Config.groovy? Мы не тестируем конфигурацию, не так ли? Я думаю, что мы не тестируем конфигурацию, потому что тест конфигурации будет похож на копию этой конфигурации (повторяется). Более того, этот тест не увеличит охват кода, если кто-то по-прежнему заботится об этих показателях сегодня, потому что первый запуск интеграции или функциональный тест должен запускать все ограничения всех доменов.

Последняя вещь: какая ошибка, которую этот тест способен поймать в реальной жизни?

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

Я написал много модульных тестов для ограничений. Теперь я удаляю их во время рефакторинга. Я оставлю только модульные тесты моей собственной логики валидаторов.

Смежные вопросы