Здравствуйте,
Сегодня тесты выявили такой баг: при сохранении нового клиента, вместо того, чтобы ему выставить некоторую цену доставки груза по умолчанию, эта цена доставки груза выставлялась вообще всем клиентам. Грубо говоря код такой был:
...
if (customer.ID == 0)
{
using (var transactionScope = new TransactionScope())
{
CustomerAccessor.Insert(customer);
CustshipmentsAccessor.SetShipmentPricesForAllCustomers(...); // а надо было CustshipmentsAccessor.SetShipmentPricesForCustomer(...);
transactionScope.Complete();
}
}
...
Т.е. не ту функцию вызывал... К счастью в релиз это не успело уйти.
А обнаружилось это почти случайно, какого-то специального теста на этот случай не было, но так как всем клиентам выставились цены доставки, то слетели пара других тестов. Ну так и обнаружил баг.
Т.е. получается, то, что тест был плохо изолирован (не в отдельной транзакции я его выполнял), сыграло в данном случае мне на руку. А то вообще бы не обнаружил проблему.
Но вопрос в том — как лучше тестировать чтобы такая ошибка обнаружилась. Ведь если для каждой функции писать кучу тестов и код, который проверяет как изменилось состояние БД (не затронулись ли другие строки и таблицы), то с ума можно сойти (я все-таки не маньяк-тестировщик, тесты в основном пишу только на наиболее критичные для бизнеса, либо сложные функции). Можно ли как-то проще?
Здравствуйте, MozgC, Вы писали:
MC>Но вопрос в том — как лучше тестировать чтобы такая ошибка обнаружилась.
Неправильный вопрос.
Правильный такой: Что нужно сделать, чтобы обезопасить себя от таких ошибок.
Тесты — это только один из способов — оборона. Наверное, оборону можно как-то тоже прокачать, но я бы в данной ситуации не сидел в обороне, а наносил бы упреждающие удары. А именно я бы перестал давать методам имена вида ЧтоТоОченьОченьОченьДлинное и ЧтоТоОченьОченьОченьДлинноеДляВсех, чтобы их было не так просто спутать.
Здравствуйте, XopoSHiy, Вы писали:
XSH>А именно я бы перестал давать методам имена вида ЧтоТоОченьОченьОченьДлинное и ЧтоТоОченьОченьОченьДлинноеДляВсех, чтобы их было не так просто спутать.
А предложите для примера свой вариант названия этих двух методов?
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, MC>Сегодня тесты выявили такой баг: при сохранении нового клиента, вместо того, чтобы ему выставить некоторую цену доставки груза по умолчанию, эта цена доставки груза выставлялась вообще всем клиентам. Грубо говоря код такой был:
Не уверен по поводу "проще", но в этой функциональности наверняка есть требование "У каждого клиента своя цена доставки", и проверяется, допустим, следующими тестами:
Ничего особо маньячного в этом не вижу, сценарии как сценарии. Полностью защититься от ситуации "а вот на это теста у нас не было" нельзя в принципе. Всегда будут находиться новые ситуации (хотя данный конкретный случай достаточно простой).
MC> CustshipmentsAccessor.SetShipmentPricesForAllCustomers(...); // а надо было CustshipmentsAccessor.SetShipmentPricesForCustomer(...);
MC>Но вопрос в том — как лучше тестировать чтобы такая ошибка обнаружилась. Ведь если для каждой функции писать
Затрудняюсь сказать как тестировать, но знаю как сделать, чтоб такого вообще не появлялось:
НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной.
И не должно быть похожих имён функций, на буковку различающихся. Сильно помогает -- проверено электроникой.
Здравствуйте, fk0, Вы писали:
fk0> НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной. fk0>И не должно быть похожих имён функций, на буковку различающихся. Сильно помогает -- проверено электроникой.
я бы тоже дал такое же имя, можете пример нормального, для данного случая, имени привести?
Здравствуйте, MozgC, Вы писали:
MC>Но вопрос в том — как лучше тестировать чтобы такая ошибка обнаружилась. Ведь если для каждой функции писать кучу тестов и код, который проверяет как изменилось состояние БД (не затронулись ли другие строки и таблицы), то с ума можно сойти (я все-таки не маньяк-тестировщик, тесты в основном пишу только на наиболее критичные для бизнеса, либо сложные функции). Можно ли как-то проще?
В общем случае тестировать то что код не сделал чего-то лишнего (кроме того что сделал то что нужно) слишком накладно.
Но если говорить о коде, работающем с БД, то проверять нужно не в той формулировке, которую я выделил в цитате, а в немного другой: правильно ли произошло изменение состояния БД.
Т.е. нам нужно иметь набор тестовых данных, причем шире чем данные, которые затрагиваются тестируемым кодом. Нужно иметь набор эталонных данных и умение сравнивать состояние БД с эталонными данными. Например, эталонные данные могут быть в формате plain text, тогда нужно уметь выгрузить состояние БД в plain text и сравнить с эталонным набором.
Здесь даже иногда технически проще делать именно интеграционные тесты такого плана, чем юнит-тесты. Юнит тесты потребуют отделения кода BLL от кода, сливающего изменения в БД, и проверок на уровне графов бизнес-сущностей. В то время как интеграционные тесты с БД потребуют лишь утилиты сопоставления состояния БД с набором эталонных данных.
Но так или иначе, эта методика остается все равно относительно неудобной и дорогой для процесса разработки. И принимать решение о необходимости такого тестирования следует руководствуясь оценками рисков, связанных с поставкой неправильно работающего кода, наличием команды (бета)тестеров и т.п.
Или ты поднимаешь вопрос полезности неизолированных тестов?
Здравствуйте, fk0, Вы писали:
fk0> НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной.
Понимаете, в чем дело, щас не старинные экраны в которые по 60 символов влезает, а широкоформатные мониторы, к тому же современные IDE избавляют от необходимости полностью имена классов и методов печатать. Зато если я сам посмотрю на свой код через год — у меня не возникнет вопросов "а что делает этот метод?" — это ясно из названия. То же самое если в проект придет новый человек — ему будет проще понять что делает конкретный класс или метод просто по одному названию.
Здравствуйте, samius, Вы писали:
S>Или ты поднимаешь вопрос полезности неизолированных тестов?
Нет , хотя в данном случае мой плохо изолированный тест безусловно сыграл мне на руку.
В общем пока я сделал пару выводов:
1) Действительно меня подвело имя метода. В изначальном посте я просто по памяти напечатал пример. На самом деле метод назывался CustshipmentsAccessor.UpdateShipmentPrices(int countryID, ...). Если бы имя метода включало упоминание о стране (т.е. что цены выставляются для всех клиентов из страны), то ошибиться было бы сложнее.
2) Такие ситуации протестировать можно, и в принципе несложно (пример привел rlabs), но все-таки это добавит лишних трудозатрат, и при этом все равно не гарантирует того что метод не умудрится изменить чего-то чего не должен Гарантировать можно, только если делать в каждом методе полное сравнение получившегося состояния базы данных с эталонным состоянием, но че-то по-моему это будет геморойный подход (сходу даже не знаю как такое простым способом реализовать для большой базы) и такие тесты будут долго выполняться.
// Есть custService через который можно управлять клиентами
// и только через него.
// У сервиса есть DefaultPriceSet (для клиентов, там все
// ценники по умолчанию для клиентов)
// Метод создающий новый объект Customer требует параметр PriceSet
PriceSet prs = custService.getDefaultPriceSet().clone();
prs.ShipmentPrice = 5.22;
Customer customer = custService.newCustomer(prs);
Вариант 2 (Проверка суперсостояния)
Для БД пишется один метод который проверяет суперсостояние.
Целостность данных в БД трудно проверить полностью констраинтами самой
БД. Поэтому все такие моменты прописываем в отдельный код, который можем
вызывать перед и после тестов.
Однако у такого подхода низнкая применимость. Писать такой тест может
архитектор или ведущий проктировщик, а в таком случае развивать эту
часть кода рядовые программисты либо не могут (нехватает целостного
видения системы), либо не хотят (стремаются лезть в работу вышестоящего).
Т.е. такой подход могут применять небольшие команды 2-4 человека,
исповедующие XP, или хотябы использующие парное программирование и TDD.
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, fk0, Вы писали:
fk0>> НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной.
MC>Понимаете, в чем дело, щас не старинные экраны в которые по 60 символов влезает, а широкоформатные мониторы, к тому же
А глаза всё те же, что и в 80-х, как и впрочем мозги. Да и в 80-х в текстовом режиме 132 символа в строке поддерживалось.
fk0>> современные IDE избавляют от необходимости полностью имена классов и методов печатать. Зато если я сам посмотрю на свой код через год — у меня не возникнет вопросов "а что делает этот метод?" — это ясно из названия.
Автодополнение нужно, в основном, где имена функций шириной в пол-экрана -- да... Набрать уж что-нибудь на клавиатуре, при
многолетней тренировке обычно затруднений не вызывает. Что делает метод из названия нифига не ясно. Могут быть многочисленные ньюансы. И что в действительности позволяют современные IDE -- это хотя бы перейти к определению метода и почитать там трёхстрочный комментарий, что он делает в действительности. Не, я конечно тоже против 6-буквенных имён, но есть две крайности...
Здравствуйте, MozgC, Вы писали:
MC>2) Такие ситуации протестировать можно, и в принципе несложно (пример привел rlabs), но все-таки это добавит лишних трудозатрат, и при этом все равно не гарантирует того что метод не умудрится изменить чего-то чего не должен Гарантировать можно, только если делать в каждом методе полное сравнение получившегося состояния базы данных с эталонным состоянием, но че-то по-моему это будет геморойный подход (сходу даже не знаю как такое простым способом реализовать для большой базы) и такие тесты будут долго выполняться.
Такого можно добиться, если каждый Accessor абстрактен настолько, что взамен
реальной базы позволяет виртуальную (in-memory db) использовать. Тогда удаётся
большее число инвариантов проверять за приемлимое время. Да и воссоздать
эталонное состояние внутри теста становится более-менее простым делом.
Кстати, для справки, еще пара примеров, где упрощенный юнит-тест может
обмануть:
1) Самодельные имплементации коллекций. На скорую руку обычно проверяют, что
после add()/remove() элемент присутствует/отсутствует в коллекции и всё.
Правильный подход — как минимум, дополнительно убедиться, что количество
элементов изменилось надлежащим образом и все остальные элементы по-прежнему
в коллекции.
2) Реальный случай с fall-through в switch-конструкции, что в джаве, к сожалению,
допустимо. Информация в торговой системе о трейде приходила с нескольких внешних
систем и switch-case решал с какой комбинацией параметров сохранять этот трейд для
каждого из источников (штук пять кейсов было). В один прекрасный момент случайно
потеряли break — получили по две записи на каждый трейд в базе для одной из систем.
Нашли случайно, когда пользователи возбудились из-за неверной агрегатной суммы по
всем трейдам.
И, да, код-ревью резко снижает вероятность подобных дефектов.
Здравствуйте, MozgC, Вы писали:
MC>Понимаете, в чем дело, щас не старинные экраны в которые по 60 символов влезает, а широкоформатные мониторы, к тому же современные IDE избавляют от необходимости полностью имена классов и методов печатать. Зато если я сам посмотрю на свой код через год — у меня не возникнет вопросов "а что делает этот метод?" — это ясно из названия. То же самое если в проект придет новый человек — ему будет проще понять что делает конкретный класс или метод просто по одному названию.
Даже с большими мониторами, удобнее работать с короткими и лаконичными названиями методов. Об этом и дядя Боб писал, по-моему. Суть скорее не в длине названия, а в его логичности и не спутываемости: если названия отличаются только непного, и это отличие не бросается в глаза, повышается риск ошибки.
Я сам видел код с методами типа ПоискатьЗначениеПоТаблицеИДобавитьЕслиЕгоЕщеНет(), и поддерживать его — совсем не здорово. Другой пример — методы с булевыми параметрами: когда смотришь только на вызов, не всегда ясно отличие между CreateReport( true ) и CreateReport( false ).
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, samius, Вы писали:
MC>2) Такие ситуации протестировать можно, и в принципе несложно (пример привел rlabs), но все-таки это добавит лишних трудозатрат, и при этом все равно не гарантирует того что метод не умудрится изменить чего-то чего не должен :( Гарантировать можно, только если делать в каждом методе полное сравнение получившегося состояния базы данных с эталонным состоянием, но че-то по-моему это будет геморойный подход (сходу даже не знаю как такое простым способом реализовать для большой базы) и такие тесты будут долго выполняться.
Можно пойти от обратного. Если есть возможность подменить БД стабом(а без этого как-то странно проводить модульное тестирование), можно отслеживать _затронутые_ вызовом метода объекты БД, и проверять, что других вызовов не было. То есть, какбы, считаем, что метод обновления цен может трогать только таблицы Price и Customer, и если был вызов update любой другой (не важно какой) — тест валится. Хотя, так себе подход.
Здравствуйте, MozgC, Вы писали:
MC>А предложите для примера свой вариант названия этих двух методов?
Что-то я забыл про этот топик
Впрочем можете считать, что я все это время думал над названием
MC> CustshipmentsAccessor.SetShipmentPricesForAllCustomers
Просто потому что после второй точки придется явно выбрать ForAll или не фор олл.
А вообще, конечно, все зависит от того, что там ещё в этом CustshipmentAccessor живет. Может быть есть варианты и более натуральные.
Здравствуйте, rlabs, Вы писали:
R>Здравствуйте, MozgC, Вы писали:
MC>>Здравствуйте, samius, Вы писали:
MC>>2) Такие ситуации протестировать можно, и в принципе несложно (пример привел rlabs), но все-таки это добавит лишних трудозатрат, и при этом все равно не гарантирует того что метод не умудрится изменить чего-то чего не должен Гарантировать можно, только если делать в каждом методе полное сравнение получившегося состояния базы данных с эталонным состоянием, но че-то по-моему это будет геморойный подход (сходу даже не знаю как такое простым способом реализовать для большой базы) и такие тесты будут долго выполняться.
R>Можно пойти от обратного. Если есть возможность подменить БД стабом(а без этого как-то странно проводить модульное тестирование), можно отслеживать _затронутые_ вызовом метода объекты БД, и проверять, что других вызовов не было. То есть, какбы, считаем, что метод обновления цен может трогать только таблицы Price и Customer, и если был вызов update любой другой (не важно какой) — тест валится. Хотя, так себе подход.
А я бы еще добавил, что юнит тестирование, почти всегда не совместимо с проверкой чего-то, что не написано вами (вашей командой).
В данном случае, тестировать надо то, что был вызван именно тот метод, который предполагалось использовать.
Проверка того, как изменились данные в базе при вызове метода — это уже задача интеграционных тестов.
Если брать пример топик стартера, то тут тест был бы примерно таким:
Если писать в режиме TDD, то чтобы допустить такой баг, нужно ошибиться и в тесте и в коде одновременно.
Все что остается, это проверить (уже в наборе интеграционных тестов), что SetShipmentPricesForAllCustomers действительно обновляет все записи, а SetShipmentPricesForCustomer только для конкретного покупателя.
Ну и потом провести рефакторинг, чтобы не путать имена.
Здравствуйте, Gmoorick, Вы писали:
G>В данном случае, тестировать надо то, что был вызван именно тот метод, который предполагалось использовать.
Я боюсь, что если писать тест после кода, то так же просто возьмешь и в тесте тоже выберешь не тот метод (потому что перед этим сначала поглядишь на тестируемый метод и увидишь там ...FoAllCustomers() — и поэтому автоматом напишешь Mock.VerifyWasCalled(()=>MockedCustshipmentsAccessor.SetShipmentPricesForAllCustomers(...));
Но наверное это уже переходит в разряд, что если мозг не напрягать, то что угодно можно сломать.
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, Gmoorick, Вы писали:
G>>В данном случае, тестировать надо то, что был вызван именно тот метод, который предполагалось использовать.
MC>Я боюсь, что если писать тест после кода, то так же просто возьмешь и в тесте тоже выберешь не тот метод (потому что перед этим сначала поглядишь на тестируемый метод и увидишь там ...FoAllCustomers() — и поэтому автоматом напишешь Mock.VerifyWasCalled(()=>MockedCustshipmentsAccessor.SetShipmentPricesForAllCustomers(...));
MC>Но наверное это уже переходит в разряд, что если мозг не напрягать, то что угодно можно сломать.
именно поэтому я написал про TDD, юнит-тест после кода (если это не дебаггинг)- это имхо время на ветер.
Чисто в теории, наверное надо использовать mock объекты, в частности для CustshipmentsAccessor. А дальше писать тест-кейс, в котором изобразить что-то вроде:
EXPECT_CALL( SetShipmentPricesForCustomer )
Но это в теории все прекрасно, а как написать нормальные тесты с mock объектами — я не знаю. Хотя опять же в теории тест-кейсы с мок-объекатми должны также прекрасно документировать код. Типа посмотрел на кейс и сразу виден весь алгоритм. Но это видимо сродни искусству....
Оставлю свое мнение по вопросу, вдруг кому-то пригодится.
Первое что хочу сказать: не стоит пытаться обезопасится от таких ошибок с помощью тестирования. Придется написать тонны тестов, из которых хорошо если один стрельнет (а как извесно, хороший тест -- это тест который повалился).
Что же можно сделать чтобы уберечь себя от подобных ошибок? ИМХО, проектировать удобный "error free" интерфейс.
У меня есть 3 предложения по улучшению этого интерфейса:
1) ForAllCustomers и ForCustomer вынести в начало названия.
Получим ForAllCustomersSetShipmentPrices и ForCustomerSetShipmentPrices. Ошибиться стало намного сложнее.
Для пущего удобства чтения можно добваить подчеркивание: ForAllCustomers_SetShipmentPrices и ForCustomer_SetShipmentPrices (и шлите в лес всех кто говорит, что подчеркивчания "некошерны". ИМХО, кошерно все что удобно. Тут подчеркивание удобно).
Как бонус получаем разделение методов на две группы в автодополнении. (это, кстати, наведет вас на мысль, что класс играет две роли: работа с одним кастомером и со всем кастомерам, а оттуда недалеко до двух классов)
Не совсем понимаю как получается, что SetShipmentPricesForCustomer не получает параметром конкретного кастомера. Возможно, он передается из CustomerAccessor через какой-то контекст.
2) Можно передавать кастомера параметром в метод. Тогда SetShipmentPricesForAllCustomers банально не скомпилируется.
3) Можно проверять наличие кастомера в контексте из SetShipmentPricesForAllCustomers. И если он там есть выкидывать исключение (с пояснением что как и почему).
В общем нужно проектировать удобные интерфейсы -- те, которые кроме решения своих непосредственных задач не позволяют использоать себя неправильно.
Здравствуйте, Aikin, Вы писали:
A>1) ForAllCustomers и ForCustomer вынести в начало названия.
Я уже типа того и сделал, только еще больше переименовал
A>Не совсем понимаю как получается, что SetShipmentPricesForCustomer не получает параметром конкретного кастомера. Возможно, он передается из CustomerAccessor через какой-то контекст. A>2) Можно передавать кастомера параметром в метод. Тогда SetShipmentPricesForAllCustomers банально не скомпилируется.
Одна функция принимает первым параметром int customerID, а другая int countryID =) Поэтому так и вышло
Re[3]: Как поймать тестами такой баг?
От:
Аноним
Дата:
27.05.10 16:57
Оценка:
Здравствуйте, MozgC, Вы писали:
A>>2) Можно передавать кастомера параметром в метод. Тогда SetShipmentPricesForAllCustomers банально не скомпилируется. MC>Одна функция принимает первым параметром int customerID, а другая int countryID =) Поэтому так и вышло
Недавно, кстати, AndrewK писал о типизированных Id.
Т.е. вместо int CountryId пишем Key<Country>, мне понравилось, начал использовать у себя
Здравствуйте, MozgC, Вы писали:
A>>Не совсем понимаю как получается, что SetShipmentPricesForCustomer не получает параметром конкретного кастомера. Возможно, он передается из CustomerAccessor через какой-то контекст. A>>2) Можно передавать кастомера параметром в метод. Тогда SetShipmentPricesForAllCustomers банально не скомпилируется. MC>Одна функция принимает первым параметром int customerID, а другая int countryID =) Поэтому так и вышло
Теперь понятно. Опять проблема интерфейса.
Надо узнать что же предлагал AndrewK, а то у меня кроме экстеншен метода для int (id.of<Customer>()) и new Customer(id) ничего в голову не приходит.
Здравствуйте, <Аноним>, Вы писали:
А>Недавно, кстати, AndrewK писал о типизированных Id. А>Т.е. вместо int CountryId пишем Key<Country>, мне понравилось, начал использовать у себя
А можно пример? А еще лучше ссылку?
Здравствуйте, Aikin, Вы писали:
А>>Недавно, кстати, AndrewK писал о типизированных Id. А>>Т.е. вместо int CountryId пишем Key<Country>, мне понравилось, начал использовать у себя A>А можно пример? А еще лучше ссылку?
Угу, об этом я знаю. Еще МакКоннел в совершенном коде писал. Вот только совсем не затронут более важный вопрос: как удобно создать этот ключ.
Мест где нужно передать ключ намного больше чем мест где ключ объявлен. Поэтому нужно думать о юзабилити создания ключа из int'а, а не о том как же удобно описать сам ключ.
Я до сих пор использовал new Customer(id) в случаях когда нужна типизация.
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, fk0, Вы писали:
fk0>> НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной.
MC>Понимаете, в чем дело, щас не старинные экраны в которые по 60 символов влезает, а широкоформатные мониторы, к тому же современные IDE избавляют от необходимости полностью имена классов и методов печатать. Зато если я сам посмотрю на свой код через год — у меня не возникнет вопросов "а что делает этот метод?" — это ясно из названия. То же самое если в проект придет новый человек — ему будет проще понять что делает конкретный класс или метод просто по одному названию.
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, fk0, Вы писали:
fk0>> НАДО ДАВАТЬ ФУНКЦИЯМ НОРМАЛЬНЫЕ ИМЕНА. ЧИИИИТААААЕЕЕМЫЫЫЫЕЕЕЕЕ! А не блаблабла в пол-экрана шириной.
MC>Понимаете, в чем дело, щас не старинные экраны в которые по 60 символов влезает, а широкоформатные мониторы, к тому же современные IDE избавляют от необходимости полностью имена классов и методов печатать. Зато если я сам посмотрю на свой код через год — у меня не возникнет вопросов "а что делает этот метод?" — это ясно из названия. То же самое если в проект придет новый человек — ему будет проще понять что делает конкретный класс или метод просто по одному названию.
И что, теперь моск тоже со скоростью видеокарты работает
От перепутывания и опечаток не спасут и короткие имена.
Попробуйте не перепутать i и j в каком-нибудь алгоритме сортировки.
Здравствуйте, MozgC, Вы писали:
MC>Здравствуйте, fk0, Вы писали: fk0>>Не, я конечно тоже против 6-буквенных имён, но есть две крайности... MC>Т.е. по вашему SetCustomerShipmentPrices() — это крайность?
А ты как сам печатаешь подобные названия? Вручную или через автокомплит? ИМХО длинность названия как раз и провоцирует пользоваться автокомплитом, а он как раз создает благодатную почву для таких ошибок.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>А ты как сам печатаешь подобные названия? Вручную или через автокомплит? ИМХО длинность названия как раз и провоцирует пользоваться автокомплитом, а он как раз создает благодатную почву для таких ошибок.
код чаще читают, чем пишут
Стив МакКонел. Совершенный код
Во-вторых, здесь неверно выбрано название функции, вернее порядок слов в ней. Что уже неоднократно обсуждалось.
MC>Но вопрос в том — как лучше тестировать чтобы такая ошибка обнаружилась. Ведь если для каждой функции писать кучу тестов и код, который проверяет как изменилось состояние БД (не затронулись ли другие строки и таблицы), то с ума можно сойти (я все-таки не маньяк-тестировщик, тесты в основном пишу только на наиболее критичные для бизнеса, либо сложные функции). Можно ли как-то проще?
первое что пришло на ум
1) Ослабить связь между слоями логики и доступа к данным, так чтобы ко всем XXXAccessor можно было MOK-объекты сделать. (можно и на слой ниже)
2) дальше само собой получится, что CustshipmentsAccessor.SetShipmentPricesForCustomer будет ожидаемым вызовом в тесте
3) НО это не спасет от случая когда 2 раза и в коде и в тесте будет вызов не того метода.
Для себя выработал правило: не должно быть в интерфейсе имен вида CommonName
CommonNameExtra
Допустимы только непустые суффиксы: CommonNameOne
CommonNameTwo