14.03.2017, 17:08 | #41 |
Участник
|
Цитата:
Цитата:
CU12 Known Issue – Incorrect Posting amounts – Shipment Packing slip posting
Known scenarios The released product is associated with an item model group where Post physical inventory to ledger = True or Post to Deferred Revenue Account on sales delivery = True, and Sales order line lot ID is split based on output orders and grouped in the same shipment, and Sales packing slip is posted from Inventory management>Common>Shipments. |
|
14.03.2017, 17:52 | #42 |
Moderator
|
Цитата:
вообще говоря, unit-тестирование предполагает, что все запуски выполняются в одном и том же окружении. поэтому при правильном подходе "вокруг" ничего меняться не должно.
|
|
14.03.2017, 17:56 | #43 |
Moderator
|
Цитата:
да, я услышал, что в этой ветке говорилось только о регрессии.
и таки да, наверное вряд ли стоит ожидать чего-то другого от тестирования в коде. Я же правильно понимаю, что слово "регрессия" ты используешь в контексте сравнения текущего состояния с каким то предыдущим ? Цитата:
Сообщение от Андре Посмотреть сообщение
Вообще, целью unit тестирования для меня является минимальная гарантия того, что я смогу сегодня ночью спокойно спать и меня не разбудят со словами, что что-то отвалилось. другими словами, контроль регрессии. пусть так. p.s. Еще мне нравятся unit-test-ы как средство документирования проекта/библиотеки. Лучший способ понять, как использовать какую-то библиотеку - посмотреть ее unit тесты. Последний раз редактировалось Андре; 14.03.2017 в 18:00. |
|
14.03.2017, 21:08 | #44 |
Участник
|
Цитата:
тогда формально он будет при - "все возможные комбинации" при запуске для каждый из параметров = "по умолчанию"\"не по умолчанию" дают "зеленый" результат |
|
14.03.2017, 21:43 | #45 |
Участник
|
Цитата:
именно. и как правильно выполнять unit-тестирование таких методов? да, именно так вопрос и был поставлен. Цитата:
Цитата:
https://ru.wikipedia.org/wiki/%D0%A0...BD%D0%B8%D0%B5 Цитата:
это точно сейчас не присутствует. но с удовольствием послушаю размышления применительно к аксапте. Цитата:
Сообщение от Андре
Для меня unit test-ы это еще одна проверка на то, что код ведет себя именно так, как я ожидаю. В том числе и только что написанный. Это не значит что он работает правильно. И не значит, что ничего не поломалось. Это значит что мое представление о коде более-менее соответствует действительности.
можешь рассказать об этом применительно к методам с параметрами по умолчанию? каковы критерии необходимости и достаточности? |
|
14.03.2017, 22:33 | #46 |
Moderator
|
Цитата:
можешь рассказать об этом применительно к методам с параметрами по умолчанию?
На мой взгляд ничего. Методы с параметрами по умолчанию надо тестировать точно также, как и методы с обычными параметрами. Все вот эти вопросы: Цитата:
например, сколько тестирующих методов должно быть для метода с дефолтными параметрами?
= один тестирующий с несколькими ассертами? = столько тестирующих, сколько различных комбинаций параметров для того, чтобы покрыть все значимые комбинации параметров? причем в каждом тестирующем методе должен быть только один ассерт? = какое-то "достаточное" число test-методов? каков критерий достаточности? Цитата:
каковы критерии необходимости и достаточности?
У меня нет полного кода метода, а на твоей картинке я вижу только локальные методы (собственно даже начало метода класса на картинку не попало), поэтому никаких примеров привести не могу. Я обычно проверяю типичные сценарии использования и какие-то граничные условия. Многие фреймворки для тестирования позволяют все возможные case-ы для проверки записывать в лаконичном табличном виде в виде комментариев и по ним уже генерить вызовы методов тестирования.Как то так: PHP код:
PHP код:
Попробую объяснить. Этой мой unit test и это мой код, который я тестирую. Я его знаю. Я могу быть не уверен, работает ли он корректно, но подразумевается, что я достаточно хорошо его понимаю, чтобы выделить какие-то сценарии его использования (как стандартные, так и наоборот - неожиданные). Их и надо тестировать. Последний раз редактировалось Андре; 14.03.2017 в 22:55. |
|
14.03.2017, 23:08 | #47 |
Участник
|
Цитата:
кроме того, что это фишка аксапты, если я правильно понимаю. поэтому ответ на вопрос не гуглится на стековерфлоу ))) Цитата:
понятно. Цитата:
Сообщение от Андре
Многие фреймворки для тестирования позволяют все возможные case-ы для проверки записывать в лаконичном табличном виде в виде комментариев и по ним уже генерить вызовы методов тестирования. Есть фреймворки, которые позволяют случайно генерить данные для тестирования (https://github.com/fscheck/FsCheck). Но я не сторонник таких подходов.
есть такие фреймоврки. но в аксапте их нет. стоит ли добавлять в аксапту? стоит ли писать свой велосипед для подобных случаев? Цитата:
В аксапте чаще наоборот. И об этом писал fed выше. Мой код использует что-то из стандартного функционала аксапты. как правило, я не до конца понимаю как работает стандарт на всех граничных значениях. как правило, я не понимаю нафига сделано именно так. мало того, как правило, мой код всего лишь расширяет поведение какого-то стандартного метода. мало того, с Аксаптой работают разные команды людей. причем могут работать одновременно. у разных команд может быть свое представление о происходящем. причем это не только в мс. на проектах абсолютно то же самое. и в этих условиях я должен изобразить что-то "сторожевое". ))) ну, т.е. понятно, что завсегда можно использовать "тяп-ляп и продакшн". понятно, что каждую конкретную задачу решить можно. вопрос то: как правильно? ))) пока есть ответ "Здравый смысл" ================= не уверен, что понадобиться, но вот полный код метода. повторюсь, что это очень и очень давний метод, который можно проследить до самых первых версий аксапты, в которых он был простым как пареная репа. X++: private boolean findDisc(PriceType _relation, InventDimId _inventDimId, TableGroupAll _itemCode = 0, ItemId _itemRel = '', TableGroupAll _accountCode = 0, CustVendAC _accountRel = '', UnitOfMeasureSymbol _unitID = '', Qty _quantityAmount = 0, CurrencyCode _currency = CompanyInfoHelper::standardCurrency(), AgreementHeaderExtRecId_RU _agreementHeaderExtRecId = 0, CustVendAC _agreementPartnerCode = '') { PriceDiscTable priceDiscTable; boolean discExist; container key; container cacheValue; int i; FromDate localFromDate; ToDate localToDate; AmountQty localQuantityAmountFrom; AmountQuantityTo localQuantityAmountTo; RecId localRecid; boolean cacheMode; // <GEERU> CustVendAC accountRelation = _accountRel; // </GEERU> void reselectBuffer() { if (cacheMode) { priceDiscTable = PriceDiscTable::findRecId(localRecid); } } void findDisc() { if ((discDate >= localFromDate || ! localFromDate) && (discDate <= localToDate || ! localToDate)) { if (_relation == PriceType::EndDiscPurch || _relation == PriceType::EndDiscSales ) { // for end discounts, the QuantiyAmountField field contains order total amounts, not quantities if (this.calcCur2CurPriceAmount(localQuantityAmountFrom, priceDiscTable) <= qty && ((qty < this.calcCur2CurPriceAmount(localQuantityAmountTo, priceDiscTable)) || !localQuantityAmountTo)) { reselectBuffer(); discExist = true; discAmount += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit(); percent1 += priceDiscTable.Percent1; percent2 += priceDiscTable.Percent2; actualDiscTable = priceDiscTable.data(); quantityAmount += priceDiscTable.QuantityAmountFrom; } } else { if (localQuantityAmountFrom <= qty && (qty < localQuantityAmountTo || !localQuantityAmountTo)) { reselectBuffer(); discExist = true; discAmount += this.calcCur2CurPriceAmount(priceDiscTable.Amount, priceDiscTable)/ this.priceUnit(); percent1 += priceDiscTable.Percent1; percent2 += priceDiscTable.Percent2; actualDiscTable = priceDiscTable.data(); quantityAmount += priceDiscTable.QuantityAmountFrom; this.mcrPriceDiscTableFound(priceDiscTable); } else { // If quantity does not qualify, but calculation potential then add as found if (this.parmMCRPriceHistoryPotentialCalc()) { reselectBuffer(); this.mcrPriceDiscTableFound(priceDiscTable); } } } } } // <GEERU> if (countryRegion_RU) { if (_accountCode == TableGroupAll::Table && _agreementHeaderExtRecId && _agreementPartnerCode) { accountRelation = _agreementPartnerCode; } } // </GEERU> if (!_inventDimId) { return false; } // To avoid flooding the cache the most granualated setup isn't cached. cacheMode = (_itemCode != TableGroupAll::Table || _accountCode != TableGroupAll::Table) && !this.parmMCRPriceHistoryPotentialCalc(); cacheValue = conNull(); if (cacheMode) { key = this.makeKey(_relation, _itemCode, _itemRel, _accountCode, // <GEERU> accountRelation, // </GEERU> _unitID, _currency, _inventDimId // <GEERU> ,_agreementHeaderExtRecId // </GEERU> ); cacheValue = PriceDisc::getPriceDiscCacheValue(#cacheScope_FindDisc, key); } qty = abs(_quantityAmount); if (cacheValue == conNull()) { if (_itemCode != TableGroupAll::Table) { _unitID = ''; } while select priceDiscTable order by QuantityAmountFrom, FromDate where priceDiscTable.Relation == _relation && priceDiscTable.ItemCode == _itemCode && priceDiscTable.ItemRelation == _itemRel && priceDiscTable.AccountCode == _accountCode // <GEERU> && priceDiscTable.AccountRelation == accountRelation // </GEERU> && priceDiscTable.UnitId == _unitID && (priceDiscTable.Currency == _currency || (priceDiscTable.GenericCurrency && priceDiscTable.Currency == genericCurrency)) // <GEERU> && (!countryRegion_RU || priceDiscTable.AgreementHeaderExt_RU == _agreementHeaderExtRecId) // </GEERU> && (priceDiscTable.InventDimId == _inventDimId || this.parmMCRPriceHistoryPotentialCalc()) { if (cacheMode) { cacheValue += [[priceDiscTable.FromDate, priceDiscTable.ToDate, priceDiscTable.QuantityAmountFrom, priceDiscTable.QuantityAmountTo, priceDiscTable.RecId]]; } else { localFromDate = priceDiscTable.FromDate; localToDate = priceDiscTable.ToDate; localQuantityAmountFrom = priceDiscTable.QuantityAmountFrom; localQuantityAmountTo = priceDiscTable.QuantityAmountTo; localRecid = priceDiscTable.RecId; findDisc(); if (discExist && !priceDiscTable.SearchAgain) { searchAgain = false; break; } } } if (cacheMode) { //We also want to cache the absence of discounts. if (cacheValue == conNull()) { cacheValue = [[0]]; } PriceDisc::insertPriceDiscCache(#cacheScope_FindDisc, key, cacheValue); } } if (cacheMode && cacheValue) { discExist = false; for (i=1;i<=conLen(cacheValue);i++) { [localFromDate, localToDate, localQuantityAmountFrom, localQuantityAmountTo, localRecid] = conPeek(cacheValue, i); if (localRecid) { findDisc(); if (discExist && !priceDiscTable.SearchAgain && !this.parmMCRPriceHistoryPotentialCalc()) { searchAgain = false; break; } } } } return discExist; } Последний раз редактировалось mazzy; 14.03.2017 в 23:12. |
|
14.03.2017, 23:16 | #48 |
Moderator
|
Цитата:
кроме того, что это фишка аксапты, если я правильно понимаю.
PHP код:
Цитата:
А вот тут похоже и есть корень.
В аксапте чаще наоборот. И об этом писал fed выше. Мой код использует что-то из стандартного функционала аксапты. как правило, я не до конца понимаю как работает стандарт на всех граничных значениях. как правило, я не понимаю нафига сделано именно так. Тест который тестирует твой код, и тест который тестирует стандартный функционал, который использует твой код - это разные тесты. В тесте, который тестирует твой код - не надо проверять стандартный функционал. Может быть его вообще лучше замокать. Для проверки стандартного функционала будут свои unit тесты. Каждый unit тест не проверяет весь мир вокруг. Он проверяет изолированный кусочек кода. Код настолько легко будет тестировать с помощью unit-тестирования, насколько легко из него можно будет вычленять вот эти независимые кусочки. Код, который ты привел, с точки зрения тестирования - жесть. Его невозможно рассматривать независимо от всего мира вокруг. Он работает напрямую с таблицами БД, он обращается к статическим методам других таблиц. В него встроена (а не передается снаружи) обвязка для кеширования уже посчитанных значений. Это означает, что я не могу передать для нее mock. А надо. Так как это кеширование должно проверяться отдельно от этого метода, в отдельном unit тесте. А здесь я хочу тестировать логику метода. У кода есть разные характиристики. Например, его производительность (насколько быстро он работает), читаемость (насколько легко читать код), поддерживаемость (насолько легко добавлять в код новые возможности). А еще есть такая характеристика, как тестируемость. Насколько легко код тестировать. Как и во всех остальных случаях, тестируемость кода не возникает сама по себе. Для этого надо прилагать дополнительные усилия. Иногда тестируемость может вхождить в конфликт с другими требованиями к коду. Например, с производительностью или даже читаемостью. Очевидно, что у разработчиков Ax тестируемость кода не была приоритетом номер один.Скорее всего вообще, она не была в приоритетах. Я не говорю, что это плохо для продукта в общем. Наверное, расставив приоритеты таким образом они что-то и выиграли. Но, на мой взгляд, этот выбор привел к тому, что Ax и unit тестирование не рассматривают вместе. Последний раз редактировалось Андре; 14.03.2017 в 23:35. |
|
14.03.2017, 23:29 | #49 |
Участник
|
они там появились совсем недавно.
в принципе народ пока только осваивает их. раньше в C# нужно было делать кучу overload методов. для каждого метода свой unit-тест и вася-кот. все было логично. дефолтные параметры есть в basic, php. но в этих системах не было слоев, из-за которых интерфейс был очень стабильным. в общем, дефолтные параметры есть, конечно. буду рад увидеть ссылки на рекомендации "как правильно" и для других систем ))) Цитата:
Сообщение от Андре
Нет, мне кажется мы по разному понимаем unit тестирование. И мне кажется что мое понимание ближе к "каноническому"
Тест который тестирует твой код, и тест который тестирует стандартный функционал, который использует твой код - это разные тесты. В тесте, который тестирует твой код - не надо проверять стандартный функционал. Может быть его вообще лучше замокать. Для проверки стандартного функционала будут свои unit тесты. Каждый unit тест не проверяет весь мир вокруг. Он проверяет изолированный кусочек кода. тут непонятно что делать, если свалился мой тест, но из-за того, что изменилось поведение в стандарте. если отделять мух от котлет, то как отделить правильно? |
|
14.03.2017, 23:43 | #50 |
Участник
|
Цитата:
Ну значит надо покрыть тестами свои изменения, потому что мы знаем что мы делаем и забить на все остальное. Глядя на существующие юнит тесты в АХ многие из них написаны просто чтобы нагнать покрытие и получить волшебную зеленую лампочку, некоторые из них даже не асертят ничего и тут либо в МС сидят одни дураки либо это самый правильный подход с точки зрения отношения затрат к результату. Да и по моему скромному опыту и чекинам которые я видел, если АХ тесты падают, то проблема чаще в тесте чем в тестируемом методе... Последний раз редактировалось skuull; 14.03.2017 в 23:45. |
|
14.03.2017, 23:43 | #51 |
Moderator
|
Цитата:
тут непонятно что делать, если свалился мой тест, но из-за того, что изменилось поведение в стандарте.
если отделять мух от котлет, то как отделить правильно? Потом свалится твой тест. Это уже хорошо. Ради этого все и затевалось. То есть проблема не прошла незамеченной. И что делать дальше уже не так, по сути важно Вроде только один вариант. Править свой код и свой тест, чтобы оно работало корректно с новой версией функционала. Особенно с учетом того, что править стандартный код становится все сложнее |
|
15.03.2017, 00:36 | #52 |
Дмитрий Ерин
|
Я почти уверен, что в Аксапте именно так и транслируются методы со значениями по умолчанию - в кучу overload методов. Если только в семерке в Х++ не добавили "именованные" параметры, как в шарпе, то получаем не 2^8 комбинаций дефолтности, а всего лишь 9.
Таким образом, можно свести дальнейший поиск стратегии к умозрительному варианту, что у нас в классе есть 9 методов findDisc: X++: boolean findDisc(PriceType _relation, InventDimId _inventDimId, TableGroupAll _itemCode, ItemId _itemRel, TableGroupAll _accountCode, CustVendAC _accountRel, UnitOfMeasureSymbol _unitID, Qty _quantityAmoun, CurrencyCode _currency, AgreementHeaderExtRecId_RU _agreementHeaderExtRecId, CustVendAC _agreementPartnerCode, LogisticsPostalAddressRecId _deliveryPostalAddress) { // основной код } // здесь еще 7 методов // ... boolean findDisc(PriceType _relation, InventDimId _inventDimId) { return findDisc(_relation, _inventDim, 0,'',0,'','',0,CompanyInfo::standardCurrency(),0,'',0); } |
|
15.03.2017, 02:45 | #53 |
Microsoft Dynamics
|
Ой, что-то много понаписали. Не все прочитал. Напишу, как я бы действовал в таком случае.
1. Если я автор этого метода, то я бы написал ровно столько юнит-тестов, сколько сценариев его использования было бы мне известно на момент написания метода. 2. Если мне это метод достался мне в наследство, то написал бы столько юнит-тестов, что-бы покрыть весь код (100%) 3. Если мне пришла бага на фикс (или запрос на изменение) в этом методе, то написал бы ровно один юнит-тест, что бы проверить/покрыть только внесенные мной изменения. Ни один из моих вариантов не зависит от количества параметров и количетсва параметров заданных по умолчанию. Полагаю, что корректно написаный метод должен (может) сам проверять свои праметры на валидность. При этом не нужно тестировать коней в вакууме перебирая все возможные значения параметров. Последний раз редактировалось AlexSD; 15.03.2017 в 02:47. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
15.03.2017, 10:41 | #54 |
Moderator
|
Знаешь, я когда-то перевнедрял аксапту в одной фирме. И там местные финансисты заставили местных программистов в прошлой версии Аксапты переписать рассчет себестоимсти с ноля. Понятно что задача это была нелегкая - финансовый контролер не очень хорошо понимал аксапту, программист не очень хорошо понимал экономическую сущность задачи, поэтому финансовый контролер закономерно подозревал что себестоимость рассчитывается неверно. Поэтому он попросил другого программиста написать систему выверки для результатов работы расчета себестоимости. По факту - второй программист просто написал альтернативную систему рассчета себестоимости - в меру своего понимания объяснений финансового контроллера.
Время от времени, результаты выверки не сходились с результатами рассчета себестоимости. После этого финансист и оба разработчика садились и чего-то там где-то меняли (опять таки в меру своего понимания ситуации). В итоге - когда я взялся за эту задачу (не было у меня шанса прогнуть клиента под стандартный функционал себестоимости), я обнаружил что в результате параллельной разработки, и себестоимости и выверка местами просто тупо имеют сходные баги и выдают одинаково неправильный результат. К чему это я все написал: Во первых - не употребляй, пожалуйста, иностранный термин "юнит-тест". Используй лучше хорошо знакомый тебе по годам автоматизации российской бухгалтерии термин выверка. Это сразу сделает тебе понятнее назначение этого процесса и поможет с формированием правильного к нему отношения. Во вторых - надо очень четко понимать, что и юнит-тесты и выверка и предполетный досмотр в аэропорту - это все частные случаи того что в английском языке называется 'security theater'. Как известно, человеку с минимальной специфической подготовкой совсем не сложно обойти предполетный досмотр и пронести на борт что-то нехорошее. Поэтому реальная цель предполетного досмотра - это успокоить обычных пассажиров. Просто чтобы во время полета они не паниковали если их сосед арабской наружности встает с кресла, чтобы сходить в туалет. Нет, конечно от наиболее тупых террористов предполетный досмотр может помочь, но по факту - обычно наиболее тупые персонажи слишком бедны, чтобы подготовить столь сложный в организации террористический акт. Вот и к выверке/юнит-тесту надо относиться точно также как к предполетному досмотру. От сложных ошибок они мало помогают, а тупые баги, скорее всего, будут достаточно очевидны и без юнит-тестов... |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
15.03.2017, 10:58 | #55 |
Участник
|
Цитата:
правда выверка - это что-то постфактум происходящее. так отрезается TDD всякое и прочие автоматизированные код-генераторы... но выверка - это пять!!! да, люди в основном говорят о регрессии. в этом смысле выверка - самый подходящий термин. причем, выборочная выверка )))) |
|
15.03.2017, 11:07 | #56 |
Участник
|
Цитата:
Цитата:
От сложных ошибок они мало помогают, а тупые баги, скорее всего, будут достаточно очевидны и без юнит-тестов..
Автоматические тесты могут просто не дать замерджить неправильное изменение. А еще можно гонять их фонов прямо в IDE - см http://www.ncrunch.net/ |
|
15.03.2017, 11:12 | #57 |
Участник
|
А еще есть книжка Эффекстивная работа с унаследованным кодом там как раз про то, как писать тесты для говнокода.
Есть такое понятие pinning tests - когда надо на существующую систему написать тесты просто чтобы зафиксировать ее поведение при определенных параметрах. Я даже временные такие делал (когда, например, тестов нет, но надо ускорить какой-то код, но не сломать суенарий в целом) - помогает. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
15.03.2017, 11:33 | #58 |
Участник
|
Надо протестировать что каждый из параметров отрабатывает правильно, то есть его изменения приводят к ожидаемому результату, а потом написать интеграционный тест где задать все параметры, убедившись что они взаимодействуют правильно. Было на тренинге по Acceptance Tests.
На самом деле тут (я имею ввиду Аксапту вообще) проблема другая, а именно что одним из параметров является состояние системы. Положим, для простоты что считывается значение из LedgerParameters, а дальше исполнение идёт по разным веткам. Тогда получается что хоть явного параметра и нет, а на самом деле он есть. Касательно метода PriceDisc.findPrice - ну это, как правильно замечено, один из важных методов системы, который считает цену. Для него 256 тестовых методов это в общем то может быть и нормально. Самое сложное, может быть, в данном случае это с помощью Reverse Engineering описать эти 256 сценариев. |
|
15.03.2017, 11:45 | #59 |
Участник
|
Цитата:
но. задать надо вручную? 256 комбинаций? тот, кто проводит ревью теста тоже должен проверить вручную? когда добавится еще один параметр (и он будет дефолтным), то бедняга добавляющий должен будет добавить еще 256 строк? а ревьюирующий должен будет проверить вручную? как уже говорилось, при таком подходе вероятность ошибки в тесте выше, чем вероятность ошибки в самом коде. Цитата:
Сообщение от VORP
На самом деле тут (я имею ввиду Аксапту вообще) проблема другая, а именно что одним из параметров является состояние системы. Положим, для простоты что считывается значение из LedgerParameters, а дальше исполнение идёт по разным веткам. Тогда получается что хоть явного параметра и нет, а на самом деле он есть.
Цитата:
Сообщение от VORP
Касательно метода PriceDisc.findPrice - ну это, как правильно замечено, один из важных методов системы, который считает цену. Для него 256 тестовых методов это в общем то может быть и нормально. Самое сложное, может быть, в данном случае это с помощью Reverse Engineering описать эти 256 сценариев.
но я понимаю людей, которые отвечают на такой довод "здравый смысл" ))))) |
|
15.03.2017, 12:13 | #60 |
Участник
|
но. задать надо вручную? 256 комбинаций? - Насчёт вручную не понял - но автоматизировать это мне кажется не получится если речь об этом. В моём понимании надо ревьюить не столько код теста, сколько описание сценариев, которое надо сделать до написание теста, иначе в таком случае недолго и запутаться.
Надо для каждого из параметров сформировать список "имеющих смысл" значений, например клиент который есть в PriceDiscTable, и которого нет или количество(Qty) больше порога или меньше, и на каждый написать тест. Когда добавится новый параметр, надо опять же протестировать этот параметр с имеющими смысл значениями а также изменить интеграционный тест, как я понимаю. Ну то есть если бы добавили параметр количество(Qty которое уже есть) новых тестов было бы два + измененный интерграционный. да. но тут ситуация хоть как-то облегчается тем, что каждый запуск каждого тестового метода выполняется в одном и том же окружении. Я говорю о том что если окружение в разном состоянии то метод может работать по разному и это тоже может быть надо тестировать, то есть, например, проставлять разные значения в LedgerParameters. Последний раз редактировалось VORP; 15.03.2017 в 12:15. |
|