Comments 26
С Новым Годом?
0
На мой взгляд, решение чуть менее, чем полностью ужасное. Это выглядит как костыль, код становится очень плохо читаемым.
В целом я согласен, что бывает не удобно, когда первый ассерт упал, а остальные не проверились. Я бы на вашем месте взял исходники NUnit, взял оттуда класс Assert, на его основе сделал, класс, который не выполняет, а только запоминает все ассерты. А в конце тест метода, нужно вызвать что-то вроде Assert.Run(). И в этом методе класс уже будет проверять все условия и выдаст общий результат.
Конечно, это потребует чуть больше, чем 20 строк кода, но тесты внешне будут точно такими же как раньше, только придётся в конце добавить соответствующий вызов.
И конечно, если поиграться с кодогенерацией, профилированием и т.п. Можно вообще ничего не менять в коде, а всё применять уже к бинарнику, но это более магический путь, не люблю такие заморочки. Лучше, когда работа кода очевидна, по самому коду.
В целом я согласен, что бывает не удобно, когда первый ассерт упал, а остальные не проверились. Я бы на вашем месте взял исходники NUnit, взял оттуда класс Assert, на его основе сделал, класс, который не выполняет, а только запоминает все ассерты. А в конце тест метода, нужно вызвать что-то вроде Assert.Run(). И в этом методе класс уже будет проверять все условия и выдаст общий результат.
Конечно, это потребует чуть больше, чем 20 строк кода, но тесты внешне будут точно такими же как раньше, только придётся в конце добавить соответствующий вызов.
И конечно, если поиграться с кодогенерацией, профилированием и т.п. Можно вообще ничего не менять в коде, а всё применять уже к бинарнику, но это более магический путь, не люблю такие заморочки. Лучше, когда работа кода очевидна, по самому коду.
0
Модификация исходников NUnit мне представляется еще более костыльным решением. Прежде всего потому, что это ведь будет не нормальный reuse, а по сути copy-paste со всеми его вытекающими в виде убитой поддерживаемости и прочего: обновится NUnit, придется вручную обновлять свой код, чтобы оставаться up-to-date.
Кроме того, если допустить сколько-нибудь удобную и лаконичную возможность добиться подобного с помощью наследования, делегирования или экстеншенов, то использование для запоминания результатов сам Assert или его модификацию это тоже ничего хорошего. Assert статический и соответственно запоминать тогда тоже нужно будет в статике, что кошмарно. Черт знает что будет происходить при параллельном выполнении тестов и т.п.
По поводу кодогенерации вы и сами признали, что не вариант. Так и получается, что решения удобнее, чем такой класс-аккумулятор мне не видится. Был бы рад послушать еще идеи и советы.
И спасибо за фидбэк по поводу читаемости, взгляд со стороны это всегда полезно :)
Кроме того, если допустить сколько-нибудь удобную и лаконичную возможность добиться подобного с помощью наследования, делегирования или экстеншенов, то использование для запоминания результатов сам Assert или его модификацию это тоже ничего хорошего. Assert статический и соответственно запоминать тогда тоже нужно будет в статике, что кошмарно. Черт знает что будет происходить при параллельном выполнении тестов и т.п.
По поводу кодогенерации вы и сами признали, что не вариант. Так и получается, что решения удобнее, чем такой класс-аккумулятор мне не видится. Был бы рад послушать еще идеи и советы.
И спасибо за фидбэк по поводу читаемости, взгляд со стороны это всегда полезно :)
0
Я не говорил о прямой модификации. Нет, никакой проблемы, создать отдельный класс, который скопирует интерфейс Assert-а, но внутри будет накапливать данные, а потом вызовет обычный класс. Чтобы не было проблем с многопоточностью я вижу два подхода:
1. Простой. Делаем наш класс не статическим, так что в каждом тест методе придётся создавать его экземпляр (либо в SetUp), это нормально и декларативно и всё такое. Но мне лично, всё таки не очень нравится каждый раз писать стандартный код.
2. Магический. Внутри нашего статического класса использовать привязку к вызывающему методу и потоку и хранить данные по этим ключам. Таким образом, данные для каждого теста будут независмы. Также, раз у нас будет метод проверки всех ассертов, в нём же можно очищать кэш, чтобы тесты не жрали память. Магический метод внешне будет удобнее, но опять же большой минус, его магичности. Для какого-нить домашнего проекта (либо проекта с моей небольшой командой), я бы использовал этот метод. Для публичных проектов, лучше вообще не использовать всяких домашних заготовок, т.к. важно, чтобы большинство сходу понимало код.
1. Простой. Делаем наш класс не статическим, так что в каждом тест методе придётся создавать его экземпляр (либо в SetUp), это нормально и декларативно и всё такое. Но мне лично, всё таки не очень нравится каждый раз писать стандартный код.
2. Магический. Внутри нашего статического класса использовать привязку к вызывающему методу и потоку и хранить данные по этим ключам. Таким образом, данные для каждого теста будут независмы. Также, раз у нас будет метод проверки всех ассертов, в нём же можно очищать кэш, чтобы тесты не жрали память. Магический метод внешне будет удобнее, но опять же большой минус, его магичности. Для какого-нить домашнего проекта (либо проекта с моей небольшой командой), я бы использовал этот метод. Для публичных проектов, лучше вообще не использовать всяких домашних заготовок, т.к. важно, чтобы большинство сходу понимало код.
0
С описанного вами простого способа у меня как раз началась цепочка рассуждений, которая привела к решению описанному в посте, отдельный не статический класс. В SetUp кстати записывать мне показалось некрасивым, т.к. это нужно далеко не во всех тест-методах, но это и не суть.
Магический способ очень интересный. Как раз недавно использовали CallContext для управления Lifetime некоторых DAL-объектов, понравилось. Но здесь это пожалуй оверкилл и слишком комплексно, как вы и говорите.
А почему в целом от копивания интерфейса Assert я пришел к классу-аккумулятору:
1. Это все же отчасти содержит копирование;
2. Ну очень много методов там придется оборачивать, если основательно делать, а время не резиновое;
3. Самое неприятное: внутри каждого метода-обертки будет одинаковый абсолютно код вида
По поводу стандартного кода каждый раз: много ассертов в одном тесте все же не столь часто нужно, а когда нужно, то по-моему с описанным классом как раз получается декларативно и понятно, что делается, хоть читабельность внешнего кода возможно и страдает несколько из-за лямбд.
Магический способ очень интересный. Как раз недавно использовали CallContext для управления Lifetime некоторых DAL-объектов, понравилось. Но здесь это пожалуй оверкилл и слишком комплексно, как вы и говорите.
А почему в целом от копивания интерфейса Assert я пришел к классу-аккумулятору:
1. Это все же отчасти содержит копирование;
2. Ну очень много методов там придется оборачивать, если основательно делать, а время не резиновое;
3. Самое неприятное: внутри каждого метода-обертки будет одинаковый абсолютно код вида
try { Assert.Foo(args); } catch { /* запомнить */ }
, что ужасно однообразно и громоздко на мой взгляд.По поводу стандартного кода каждый раз: много ассертов в одном тесте все же не столь часто нужно, а когда нужно, то по-моему с описанным классом как раз получается декларативно и понятно, что делается, хоть читабельность внешнего кода возможно и страдает несколько из-за лямбд.
0
Методам Accumulate и Release полезно будет сделать возвращаемое значение типа AssertsAccumulator и возвращать тот же объект.
В вашем примере текст проверки будет выглядеть читабельнее.
В вашем примере текст проверки будет выглядеть читабельнее.
new AssertsAccumulator()
.Accumulate(() => Assert.That(signInResult.IsSuccess));
.Accumulate(() => Assert.That(signInResult.Value, Is.Not.Null));
.Accumulate(() => Assert.That(signInResult.Value.Username, Is.EqualTo(TestUsername)));
.Accumulate(() => Assert.That(signInResult.Value.Password, Is.EqualTo(HashedTestPassword)));
.Release();
+1
Я не очень люблю метод чейнинг вообще, и в частности здесь он по-моему как раз бьет по читабельности, вот хороший и полный топик по этому поводу: stackoverflow.com/questions/1103985/method-chaining-why-is-it-a-good-practice-or-not
0
Asserts.Accumulate(new []{
() => Assert.That(signInResult.IsSuccess),
() => Assert.That(signInResult.Value, Is.Not.Null),
() => Assert.That(signInResult.Value.Username, Is.EqualTo(TestUsername)),
() => Assert.That(signInResult.Value.Password, Is.EqualTo(HashedTestPassword))
});
0
var expectedResult = new SignInResult{
IsSuccess = true,
Value = new SignInResultValue
{
UserName = TestUserName,
Password = HasghedTestPassword
, }
}
Assert.Equals(expectedResult, signInResult);
0
Пример, который вы приводите все таки слишком прост.
Он легко разбивается на 4 изолированных теста.
Думаю, многим была бы интересна именно первоначально сложная проблема, которая и требует подобного решения.
Он легко разбивается на 4 изолированных теста.
Думаю, многим была бы интересна именно первоначально сложная проблема, которая и требует подобного решения.
0
Еще один минус подобного решения — не видно в каким месте произошла ошибка.
Т.е. определять сломанный тест придется только по его наименованию.
Т.е. определять сломанный тест придется только по его наименованию.
0
Это не так, всегда ведь есть параметр message у любого метода Assert'a для вывода развернутого сообщения. Эти сообщения в итоговом выводе аккумулятора будут сохранены. Даже в топике, например, в тесте конструктора, каждое из падений будет ясно видно по сообщению об ошибке. В примере с логином я просто поленился писать сообщения, что вообще говоря нехорошо делать :).
0
Я имел в виду, что при возникновении исключительной ситуации можно в один клик перейти на строку кода, в которой данное исключение произошло.
В предлженом же варианте, насколько я понимаю, это не возможно.
В предлженом же варианте, насколько я понимаю, это не возможно.
0
А-а, вы дебаг теста имели ввиду. Неправильно вас понял в первый раз. Тогда да, вы правы, отладка возможно будет менее удобной. Но зато, опять же, если будет несколько ошибок, то увидите все сразу в конце, а не вторую лишь после исправления первой и ребилда. Это особенно важно, если по тем или иным причинам проект у вас целиком билдится и отлаживается не локально, а в CI системе, например.
0
Я конечно понимаю, что проекты бывают разные, и некоторые тесты могут работать довольно продолжительное время, но я придерживаюсь правила, что сборка из репозитария должна всегда работать и в команде сломанные билды не поощряем.
С другой стороны, по моему личному мнению, у тестов может быть два состояния: красный или зеленый. Или пришли все проверки внутри теста либо нет. Я считаю, что анализировать по текстовым сообщениям какая конкретно часть теста упала будет не удобно.
Если посмотреть на первый пример теста, в котором предположили, что будет удобно использовать предложенный вами алгоритм, то на мой взгляд его намного проще написать в виде параметизированного теста.
С другой стороны, по моему личному мнению, у тестов может быть два состояния: красный или зеленый. Или пришли все проверки внутри теста либо нет. Я считаю, что анализировать по текстовым сообщениям какая конкретно часть теста упала будет не удобно.
Если посмотреть на первый пример теста, в котором предположили, что будет удобно использовать предложенный вами алгоритм, то на мой взгляд его намного проще написать в виде параметизированного теста.
[TestCase(0, 0, 0)]
[TestCase(2, 0, 2)]
[TestCase(15, 1, 3)]
[TestCase(36, 3, 0)]
public void ConstructorSuccess(int ctorParam, int expectedFeet, int expectedRemainderInches)
{
var testBox = new Size(ctorParam);
Assert.That(testBox.Feet, Is.EqualTo(expectedFeet));
Assert.That(testBox.RemainderInches, Is.EqualTo(expectedRemainderInches));
}
+2
Про параметризированный тест для данного примера вы правы, действительно рациональнее и компактнее получается. Спасибо, перепишу в проекте место, по которому этот пример сделан :). Такие случаи, соответственно, отпадают из области использования класса.
А в целом, по поводу тестов на удаленной машине: их в любом случае нужно прогонять в окружении, где развернут проект, и их результат от локального может там отличаться из-за разных факторов вроде часовых поясов и прочих особенностей конкретного окружения. Поэтому анализировать результаты по текстовым сообщениям приходится в любом случае время от времени, а чем больше информации получается из них, тем лучше, лишний билд и запуск тестов может съесть кучу времени. То, что состояния у теста два — это так, но когда оно красное, то лучше, когда ошибки выведены все. На мой взгляд, это как с компиляцией, она тоже может быть или успешная, или нет, но нехорошо же, если вам при неуспешной ошибку вам выводят только одну, а остальные только при следующей.
А в целом, по поводу тестов на удаленной машине: их в любом случае нужно прогонять в окружении, где развернут проект, и их результат от локального может там отличаться из-за разных факторов вроде часовых поясов и прочих особенностей конкретного окружения. Поэтому анализировать результаты по текстовым сообщениям приходится в любом случае время от времени, а чем больше информации получается из них, тем лучше, лишний билд и запуск тестов может съесть кучу времени. То, что состояния у теста два — это так, но когда оно красное, то лучше, когда ошибки выведены все. На мой взгляд, это как с компиляцией, она тоже может быть или успешная, или нет, но нехорошо же, если вам при неуспешной ошибку вам выводят только одну, а остальные только при следующей.
0
Спасибо за пост и с Новым Годом!
Я тоже копал на эту тему:
Не знаю, насколько Ваш подход будет полезен в модульных тестах, но вот в системно-интеграционных, аля Selenium через UI – будет очень полезен.
Я тоже копал на эту тему:
[Test]
public void TestSearchGoogleForThisIsATestWorkaround04()
{
// Actual: "this is a test - Поиск в Google"
// * Arrange
IWebDriver selenium = new InternetExplorerDriver();
var googlePage = new GoogleSearchPage(selenium);
string googleSearchPhrase = "This is a test";
// * Act
googlePage.Search(googleSearchPhrase);
// * Assert
// 1. Should contain the search phrase
WorkaroundFor(BOOGLE_01,
() => StringAssert.Contains(googleSearchPhrase, selenium.Title) // <==== тут
);
// 2. Should contain our Company name
StringAssert.Contains("Google", selenium.Title);
}
Не знаю, насколько Ваш подход будет полезен в модульных тестах, но вот в системно-интеграционных, аля Selenium через UI – будет очень полезен.
0
И вас с Новым годом, и вам спасибо :).
Забавно, насколько я понимаю, подход у вас получился похожий очень? Это интересно.
Почитал вашу статью, мы в подобных случаях для устранения эффекта разбитых окон такие тесты в CI-системе карантинили. В таком случае тест все еще выполняется, его результат логируется и доступен для просмотра, но падение теста в карантине не вызывает паденеия всего билда. Очень удобно получалось. Не знаю, есть ли у вас в системе подобная возможность. Мы используем Bamboo.
Забавно, насколько я понимаю, подход у вас получился похожий очень? Это интересно.
Почитал вашу статью, мы в подобных случаях для устранения эффекта разбитых окон такие тесты в CI-системе карантинили. В таком случае тест все еще выполняется, его результат логируется и доступен для просмотра, но падение теста в карантине не вызывает паденеия всего билда. Очень удобно получалось. Не знаю, есть ли у вас в системе подобная возможность. Мы используем Bamboo.
0
У нас проект просто такой, в который CI система никак не клеится. Сборка и тестирование билда у нас отдельно. Билд тестировать можно только из инсталляции. Ну, а релизы у нас раз в год. В итоге, авто-тесты нужны только тестировщикам :). Некоторые разработчики тоже пишут юнит и интеграционные тесты, но всё это децентрализированно, не для всей команды.
А билды собираются посредством JScript и bat-файлов.
Также (через bat-файлы) выглядит и последующий запуск тестов:
В такой системе нет карантина. И каждый упавший тест каждый раз отвлекает очень сильно. Так что их можно либо пофиксить либо подавить ассерты с известными багами.
А билды собираются посредством JScript и bat-файлов.
Также (через bat-файлы) выглядит и последующий запуск тестов:
- Выкачивается новый билд,
- Готовится виртуалка
- На виртуалку копируются тесты.
- Запускаются тесты при помощи Gallio/MbUnit
- В последствии отправляется отчет по почте, который можно потом благодаря интеграции Gallio + Visual Studio на своей машине и проанализировать/пофиксить.
В такой системе нет карантина. И каждый упавший тест каждый раз отвлекает очень сильно. Так что их можно либо пофиксить либо подавить ассерты с известными багами.
0
Очень рекомендую попробовать MSpec. Скажем так, описаное в этой статье там доступно «из коробки» и необходимости докручивать это что-то к велосипеду нет :)
0
В свое время писал что-то подобное. В итоге выложил вот сюда: Assertion.NUnit.
Код. Примеры использования. Из плюшек: сравнение «тестовых наборов» по содержимому и результатам. Написал эту штуку, когда необходимо было сравнить результаты выполнения тестов для небольшого количества комбинаций входных данных, но при большом количестве проверяемых условий для каждого тестового набора.
Код. Примеры использования. Из плюшек: сравнение «тестовых наборов» по содержимому и результатам. Написал эту штуку, когда необходимо было сравнить результаты выполнения тестов для небольшого количества комбинаций входных данных, но при большом количестве проверяемых условий для каждого тестового набора.
0
Публикация 31.12.2012 в 23:15 это сильно :)
+1
Only those users with full accounts are able to leave comments. Log in, please.
Множественные Assertion’ы без прерываний в одном юнит-тесте на примере NUnit