Готовлю большой класс с несколькими ДМЛ операциями к отправке в Прод.
Тест написан, но покрытие не впечатляющее - 82%.
Причина - тест не заходит в catch-и которые стоят на вставках\апдейтах.
И я не знаю как спровоцировать ДМЛ исключение - на объектах нет обязательных полей: вставляй, что хочешь - "жалко - нет".
Не знаю, что предпринять.
И по ходу возник еще вопрос: На объектах нет обязательных полей. Решил настраивать "обязательность" поля на лейауте. Причина: чтобы при добавлении новых полей к существующим в проде каст объектам случайно не добавить новое обязательное (на уровне объекта) поле, которые положит код в Проде, работающий с данным объектом и ничего не подозревающий о том новом, обязательном поле. Это правильно?
Тут уже конкретно от задачи и бизнес-логики прыгать надо. Новое поле не должно положить, так как если ты написал грамотно тесты, то оно тебе при деплое на прод сообщит...
Собственно для таких случаев и предусмотренно минимальное покрытие 75%. А 82% это очень даже хорошее покрытие для Salesforce.
А по поводу "не заходит" в try/catch - если не получается воссоздать ситуацию для срабатывания исключени стоит задуматься, а нужно ли оно :). В Salesforce и так предусмотрен механизм отката всех изменений в базе в случае ошибки выполнения в пределах рабочего цикла.
Если все же ошибки нужно перехватывать и выводить пользователю вместо "экрана смерти Salesforce", то можно обернуть весь метод в try/catch и просто сделать обработку и вывод ошибки для всего блока. А для покрытия внутри блока сделать вызвать кастомную ошибку например по определенному параметру, который выставляется из теста.
Ну не знаю у меня catch был больше чем try <!-- s:? --><img src="{SMILIES_PATH}/icon_e_confused.gif" alt=":?" title="Озадачен" /><!-- s:? --> .Ну есть несколько способов решить проблему,первый с которого я бы начал это проверил возможности метода addError() каждый sObject имеет этот метод который принимает Exception экземпляр или Стринг,насколько я помню addError работает только в контексте триггера,и возможно придется писать свой Exception класс для работы в контроллере.Второй способ попытатся про упдайтить не существующий объект или удалить.
Новое поле не должно положить, так как если ты написал грамотно тесты, то оно тебе при деплое на прод сообщит...
у меня не было опыта деплоить на прод.
Получается, что, например, пришел новый inbound change set in Prod, а в нем новые обязательные поля к старым объектам.
И в момент верификации система "крутит" не только те юнит-тесты, которые пришли с новым Сетом, а крутит ВСЕ юнит тесты в Проде, и некоторые из старых юнит тестов просто начнут падать из-за этого нового обязательного поля. И система сообщит об этом и не даст сделать деплой?
И в момент верификации система "крутит" не только те юнит-тесты, которые пришли с новым Сетом, а крутит ВСЕ юнит тесты в Проде, и некоторые из старых юнит тестов просто начнут падать из-за этого нового обязательного поля. И система сообщит об этом и не даст сделать деплой?
Именно так и происходит). При валидации/деплое запускаются выполнения всех тестов на продакшене + тестов из ченж-сета.
По своему опыту скажу, у нас продакшен на одном проекте так разросься, что деплой ченж сетов достигал 8-9 часов. Потом, после обращения клиента в salesforce, оные ребята че-то подкрутили и стало нормальные 1-2 часа.
так вот зачем мы пишем все эти юнит-тесты. не только (и вероятно не сколько) для текущего деплоймента, а для проверки на безопасность будущих, последующих изменений в системе.
Мне однажды даже жизненеобходимо было написать юнит-тесты, потому, что другого варианта проверки кода не было. Суть была в том, что у меня был managed package, который зависел от других пакетов (зависел, т.е не включен...а логика была в других). Орг, на котором я разрабатывал не имел возможности проинсталировать другие пакеты. И спасло то, что я писал юнит-тесты, где имитировал работу стороннего пакета (передаваемые данные) мне в пакет....
Мне однажды даже жизненеобходимо было написать юнит-тесты, потому, что другого варианта проверки кода не было. Суть была в том, что у меня был managed package, который зависел от других пакетов (зависел, т.е не включен...а логика была в других). Орг, на котором я разрабатывал не имел возможности проинсталировать другие пакеты. И спасло то, что я писал юнит-тесты, где имитировал работу стороннего пакета (передаваемые данные) мне в пакет....
Это что-то интересное. Я думаю подробности будут не лишними. Ты говоришь что один пакет ссылается на другой (использует логику другого) но при этом его можно поставить на орг без этих зависимых пакетов. Я так понял что ты используешь динамическое связывание? При явной ссылке одного пакета на другой salesforce даже не даст поставить один пакет без другого.
"Юнит тесты имитировали работу стороннего пакета" - значит твой пакет видел что нет другого пакета, логику которого он использует, поэтому обращался к методам из твоих тестов?
так вот зачем мы пишем все эти юнит-тесты. не только (и вероятно не сколько) для текущего деплоймента, а для проверки на безопасность будущих, последующих изменений в системе.
Мне однажды даже жизненеобходимо было написать юнит-тесты, потому, что другого варианта проверки кода не было. Суть была в том, что у меня был managed package, который зависел от других пакетов (зависел, т.е не включен...а логика была в других). Орг, на котором я разрабатывал не имел возможности проинсталировать другие пакеты. И спасло то, что я писал юнит-тесты, где имитировал работу стороннего пакета (передаваемые данные) мне в пакет....
И в момент верификации система "крутит" не только те юнит-тесты, которые пришли с новым Сетом, а крутит ВСЕ юнит тесты в Проде, и некоторые из старых юнит тестов просто начнут падать из-за этого нового обязательного поля. И система сообщит об этом и не даст сделать деплой?
Именно так и происходит). При валидации/деплое запускаются выполнения всех тестов на продакшене + тестов из ченж-сета.
Почти так и происходит) да запускаются все тесты на продакшене,но в финальный расчет берутся только кастомные то есть если ты имешь manage packages на твоем продакшене тогда тесты тоже начнут отрабатывать,но в расчет для покрытия классов будут браться только кастомные. Я имел очень низкое покрытие для тестов из manage package но в финальные расчет брался только кастомный код то же самое относится и ошибкам. я бы сказал очень важная особенность.
Вся cуть вот в чем: К ответу Den зачем unit-test. Иногда нет возможности проверить свой код, как через апекс тесты
Это я понял) даже подход такой есть TDD.Сначало пишется тест а потом код на контроллер и триггер.Например у нас была проблема,я думаю что у некоторых она может возникнуть падали тест классы при создании пакета в чем дело очень долго никто не мог понять потому что если запускали по отдельности тесты все работало нормально.Оказалось что у нас был инсталирован зависимы пакет в дев орг.Дальше интереснее окзалось что там где в инсталиррованном пакете используются динамические запросы Database.query обязательно должна быть 30 versio api иначе созадать другой пакет где этот инсталирован не возможно.Пришлось пересоздавать пакет и занова исталировать.
У нас нечто похожее было. Правда я с пакетами мало работал. На тех проектах, где сейчас работаю - используются change set'ы. Правда порой тоже были странные проблемы с code coverage 60% на продакшене вместо 75% минимально положенных) И не задеплоить...было..и грязные хаки (a++ в тестах делали)...короче...потом все как-то само вернулось..оказалось проблемы были у SF.
К вопросу поднятому Дмитрием: а нужны ли ставить try-catch на абсолютно всех ДМЛ, даже в тех, в которых мы не ожидаем никаких проблем?
например где-то по ходу выполнения кода я делаю единичный (и по существу - второстепенный ) апдейт. Я не ожидаю в этой оперции никакого подвоха. Нужно ли брать этот апдейт в try-catch или оставить его "голяком" в коде - и если на нем возникнут когда-то проблемы - то пусть весь код падает - все равно нужно будет разбираться детально, что там внезапно произошло.
Не знаю как правильнее: to catch or not to catch каждый ДМЛ в коде?
Try/catch нужно если у тебя предусмотрен альтернативный обработчик ошибки. если просто оборачивать каждую dml операцию чтобы вывести красиво ошибку то я думаю не стоит. Salesforce как CRM и там очень красиво показывает и обрабатывает ошибки.
Кстати в тему - когда пришел из PHP в SF то активно пользовался транзакциями собственно для которых и использовал try catch. Но особенность Salesforce полностью выкурила эту привычку. А именно Salesforce в случае ошибки (которую мы кстати не перехватили в try/catch) полностью откатывает все изменения в базе.
Так что я больше склоняюсь к тому чтобы не перехватывать, а наоборот как можно меньше использовать, а позволить SF самому разруливать ошибочные ситуации.
контроллер который создает запись и может приатачить к ней 1 или 2 или 3 или 4 аттача.
каждый аттач индивидуально проверяется на наличие боди и имени в вставляется с индивидуальном трай-кетче. и если вставка обломилась - в кетче (в зависимости от порядка атача, удаляются предидущие аттачи, головная запись). Т.е. там много строк в Кетчах.
и я не могу в тесте зайти в кетч.
как спровоцировать Insert исключение для Аттачмента? ПарентАйДи приписывается самим контроллером...
или придется делать один трай-кетч блок для всех Аттачей чтоб сократить кол-во недостижимых строк в кетчах...
мне логика в несколькими трай-кетчами больше нравится...
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
а что - идея. значит есть класс со стат булевой переменной и методом который просто выбрасывает исключение.
в тесте первый прогон со булевой переменной false - все прогоняем как положено.
затем переводим булеву переменную в true - и второй прогон: траи выпадают в исключение при условии (Test.isRunningTest&&булеваПеременная) и код уходит в кетчи
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
так и сделал - залез один за другим во все Кетчи. покрытие 100%
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
Так делать в принципе не правильно. Код не должен подстраиваться под тесты. Все должно быть наоборот.
Код не должен подстраиваться под тесты. Все должно быть наоборот.
звучит абсолютно правильно. но как в тесте спровоцировать ДМЛ исключение? изменить на-лету метадату, например сделав какое-то поле Required, а потом вернусь измененияч обратно? Хотя постойте-ка, а можно ли в коде, например, воткнуть в "телефооное" поле буквы? тогда код выпадет на ДМЛе и уйдет в Кетч...
покрывать catch блоки в тестах не обязательно и даже не нужно. Для этого в Salesforce и предусмотрено 75% покрытия (25% можно потратить на catch блоки) А то что при неправильных данных в поле у тебя доходит до exception DML Exception это жесть. А как же валидация входных данных и возвращение human readable error? А если тебе ОЧЕНЬ надо проверить супер логику в catch блоке, то вынеси ее в отдельный класс в static метод а из catch блока вызывай этот метод 1 строкой кода. Особо на покрытии не потеряешь из-за одной строки, а static метод сможешь проверить без всяких страшных извращений с вызовом exceptoin.
PS. Хотя для спортивного интереса можешь и поизвращаться
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
Так делать в принципе не правильно. Код не должен подстраиваться под тесты. Все должно быть наоборот.
Абсолютно с Вами согласен, но иногда есть ситуации, какие нельзя воспроизвести в тесте.
Поддержу Gres, не сталкивался с ситуациями, которые НЕЛЬЗЯ воспроизвести в тестах. Бывает это сложно сделать, особенно если код чужой и приходится заниматься реверс-инженерингом. Любой же код когда-нибудь вызывается. А если вызывается, то и тестами его можно покрыть. А если НЕ вызывается, то может он и не нужен в приложении
Gres, а в чем проблема использовать TDD. Если посмотрим чисто с практической точки зрения. Я конечно не начитанный малый, все познаю тупо из практики и может не понимаю что такое TDD. Но если руководствоваться вот этим
Разработка через тестирование (англ. test-driven development, TDD) — техника разработки программного обеспечения, которая основывается на повторении очень коротких циклов разработки: сначала пишется тест, покрывающий желаемое изменение, затем пишется код, который позволит пройти тест, и под конец проводится рефакторинг нового кода к соответствующим стандартам.
то почему сложно реализовать эту методологию внутки Salesforce? Типичный пример - писал недавно интеграцию и работал именно начиная с тестов. Пока не был готов реальный внешний сервис все данные подготавливались в тестах. И получалось что я сначала делал тесты, которые имитировали запросы и уже потом разрабатывал сами обработчики. Получилось очень классно. Как мне кажется это и есть TDD. Верно?
Посмотрел видео. Отличное и понятное видео, несмотря на то что это для C#. Пока понял что моки в C# предоставляют более продвинутые механизмы для контроля за выполнением кода - те же проверки выполнился ли метод и с какими параметрами. В Salesforce же мы в этом случае ограничены - можем опираться только на возвращаемые функцией данные для того чтобы проверить их с помощью system.assert... Но все равно не увидел ничего такого что нельзя реализовать в Salesforce. Принципы тестирования те же самые, просто реализация другая: arrange - подготовка данных, act - вызов функционала assert - проверка результата. Т.е. если я правильно понял, то в C# это дело происходит красиво внутри моков и мы просто можем контролировать их состояние, то в Salesforce наши тесты это и есть сами моки и мы сами делаем много ручной работы. Но имхо это только упрощает понимание и исключает лишний слой абстракции
Т.е. ты сначала писал тесты, на методы без реализации, а потом релизовывал их так, чтобы тесты проходили?
В последнем случае что я описывал, да, именно сначала тесты и контроль результата, а потом уже реализация метода - прямо так как в видео.
Но в большинстве случаев мой принцип разработки получается не по феншую TDD, но его принципам. Дело в том что я в разработке никогда не проверяю функционал с помощью браузера (если он чуть сложнее простой странички). Т.е. критерием работы кода является именно тест. Я начинаю писать какой-то метод, когда код более менее сформирован (но не разу не запущен) пишу сразу тест и начинаю запускать тест и смотреть на результаты работы метода. Только после того как тест прошел и все user cases протестированы (в том числе искючительные ситуации) уже иду в браузер и наблюдаю идеально работающую страницу. PS. Хотя, да, что-то я погорячился это нифига не TDD, а я просто описал стандартную разработку логики с тестами Все-таки TDD - именно разработка через тестирование и все начинается с тестов !!!
Кстати, первый раз видел исходники на C# - красиво очень напоминает Salesforce. Особенно понравилась структура проекта - сразу выделены BusinessLogic, Domains - сразу намекает на правильное разделение кода по слоям.
Вернее сказать у меня к ним двоякое отношение - с одной стороны абстракции позволяют уменьшить сложность и упростить разработку. А с другой стороны это добавляет "магию", которой надо владеть. С этой точки зрения assembler считаю идеальным языком потому что там надо знать только команды процессора, регистры и структуру памяти, что можно уместить наверное на лист формата А4 (это конечно в упрощенном виде для примера). Весь процесс как на ладони. Но конечно я бы не стал на нем программировать, потому что это ОЧЕНЬ долго. А вот абстракции надо знать чтобы уметь их варить. Вот типичный пример с этими моками. Я не знаю что ты под ними подразумеваешь, а могу лишь догадываться, потому что ты их, наверное, принес с собой из C#, а я тесты писал только под Salesforce. Может мы вообще говорим о разном, пытаясь перетянуть одеяло на свою сторону. А если сейчас придет Wilder, то у него вообще окажется своя реализация моков под Salesforce и кто-тогда будет главным?
С одной стороны статья немного прояснила тему тестирования, но с другой еще больше вопросов появилось:
Внешняя зависимость — это объект, с которым взаимодействует код и над которым нет прямого контроля. Для ликвидации внешних зависимостей в модульных тестах используются тестовые объекты, например такие как stubs (заглушки).
Получается что Mock объект - эта заглушка(одна из пяти разновидностей), которая передается в метод:
— mock object (мок-объект), очень похож на тестовый шпион, однако не записывает последовательность вызовов с переданными параметрами для последующей проверки, а может сам выкидывать исключения при некорректно переданных данных. Т.е. именно мок-объект проверяет корректность поведения тестируемого объекта.
Млин, эта теория только еще больше запутывает. Gres, проясни плиз. Если взять простые стандартные тесты в Salesforce и положить их на эту статью, то чем мы тогда занимаемся? Как это квалифицировать?
Да не парься, ты же не на собеседовании. Главное, чтобы твой код хорошо работал.
Ну, незнание многих теоретических тем, это парит меня меньше всего . Знание практических аспектов webdev, я уверен, перекрывает этот скромный недостаток . Парит то, что мой старый уже мозг отказывается воспринимать английский язык и продавать свои навыки. Вот это косяк.
всегда ли вы берете DML операцию в трай-кетч (в контроллерах к примеру) или даете самому СФ "обработать" исключительную ситуацию с DML? я это к тому, что может все эти мои Кетчи и не нужны вовсе...
всегда ли вы берете DML операцию в трай-кетч (в контроллерах к примеру) или даете самому СФ "обработать" исключительную ситуацию с DML? я это к тому, что может все эти мои Кетчи и не нужны вовсе...
У меня все DML в *DAO классах, которые используют реализацию IDatabase (insert, update, delete) Соответственно, есть всего 3 конструкции на весь проект.
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
Так делать в принципе не правильно. Код не должен подстраиваться под тесты. Все должно быть наоборот.
А как быть, если необходимо отключить триггер в тестах?
Я для таких ситуаций написал тест хелпер и метод в ньом ThrowExceptionIfTesting которий смотрит на Test.isRunningTest и на статическую булевую переменую, которой можно контролировать в каких ситуациях вибрасивать ексепшен. И етот метод пишу try блоке.
Так делать в принципе не правильно. Код не должен подстраиваться под тесты. Все должно быть наоборот.
А как быть, если необходимо отключить триггер в тестах?
Для отключения триггера используются кастом сеттинги.
Возможно вы и правы оба. Только в таком случае не вижу разницы, что проверять - переменную в триггере или сеттинги. Задача была до горя простая. Речь о тест-юните в пакете. 1. Отключить триггер. 2. Залить старые данные. 3. Проверить конвертацию. Эта вся муть выполняется после установки пакета в процессе тестов.