[БЕЗ_ЗВУКА] В предыдущем видео мы с вами сделали так, чтобы наши юнит-тесты выводили стандартный поток ошибок. И это позволило нам не комментировать запуск тестов при отправке решения в тестирующую систему. И это, на самом деле, очень хорошая вещь, что мы можем не комментировать запуск тестов, потому что если мы их закомментируем для отправки, мы можем потом забыть их раскомментировать. И мы можем изменять как-то нашу программу и забывать выполнять юнит-тесты. На самом деле у нас может появиться в программе какая-то ошибка, но мы перестали их вызывать, они у нас там закомментированы, и, соответственно, мы перестали ими пользоваться. Это неудобно. Вообще нам хочется, чтобы юнит-тесты всегда выполнялись как-то автоматически. И с каждым изменением нашей программы они выполнялись и контролировали, что мы не сломали то, что работало. При этом смотрите, сейчас в нашем фреймворке есть вот какой недостаток: давайте возьмём ну вот, например, TestCount и в очередной раз в функции GetSynonymCount допустим намеренную ошибку. Скомпилируем нашу программу, запустим её и посмотрим внимательно в консоль. Я даже её увеличу. Смотрите: мы видим, что TestCount нашёл ошибку, при этом вот этот вот красный квадратик говорит о том, что наша программа продолжает выполняться. То есть она ждёт ввода с клавиатуры. Это странно, потому что наш юнит-тест сказал, что в нашей программе есть ошибка, а мы продолжаем молча, мы переходим молча к основной программе и выполняем её, выполняем решение нашей задачи. Это как-то неправильно, потому что если юнит-тест нашёл ошибку, он должен громко, звонко сообщить об этом пользователю, что «Эй, разработчик! У тебя в программе ошибка! Вот она, иди её исправь». Сейчас же наши юнит-тесты просто молча пишут в консоли, и если мы туда особо не смотрим, то мы будем выполнять наше решение и думать, что в нём нет ошибок, что всё работает правильно. Поэтому текущее поведение наших юнит-тестов нежелательное, неправильное. Юнит-тесты должны сообщать о том, что они нашли ошибку, громко. Поэтому давайте мы поменяем поведение нашего фреймворка следующим образом: сделаем так, что если хотя бы один из юнит-тестов упал, то есть нашёл какую-то ошибку, то наша программа будет завершаться аварийно. Вернее, с ненулевым кодом возврата. Если же все юнит-тесты прошли успешно, то начинает выполняться основная программа, начинает выполняться решение нашей задачи. Таким образом мы обеспечим, что если юнит-тесты нашли ошибку, то программа заканчивает выполнение и мы видим, что у нас действительно есть ошибка. Мы этот момент не пропустим. Поэтому, во-первых, давайте всегда запускать юнит-тесты при старте программы. У нас больше нет необходимости их комментировать, потому что даже если мы вместе с ними отправим в тестирующую систему наше решение, то никаких проблем не возникнет. Поэтому мы будем выполнять тесты всегда. И смотрите, что нам надо сделать. Я сейчас переключусь обратно в Eclipse. Смотрите, что нам надо сделать: вот здесь у нас есть вызовы RunTest, нам надо выполнить RunTest сколько-то раз. И когда все вызовы RunTest закончатся, нам нужно выполнить какие-то действия. Нам нужно посчитать количество упавших тестов, и если оно больше нуля, то завершить работу программы, а иначе — ничего не делать. Как нам сделать так, как нам этого добиться? Как нам где-то хранить какое-то состояние, которое существует между вызовами тестов? Давайте обернём вот эту вот функцию RunTest в класс. Поступим мы таким образом: мы напишем класс, назовём его TestRunner, RunTest сделаем его методом. Кстати, обратите внимание, у нас это раньше не встречалось: RunTest становится шаблонным методом класса. Ну так как это обычная функция, по сути, то тут как бы никаких премудростей. Дальше. В классе TestRunner заведём счётчик числа упавших тестов. И когда у нас тест стреляет, в блоке catch мы это количество тестов увеличиваем на один. Дальше объявим функцию TestAll и перенесём в неё запуск наших тестов. Соответственно, наша функция main будет начинаться с функции TestAll. В TestAll объявим объект класса TestRunner; назовём его tr. И выполним наши тесты, вызвав у него метод RunTest. Давайте скомпилируем. Запустим. Всё выполнено, всё работает, но пока что поведение не поменялось. Тест нашёл ошибку, но программа продолжает исполняться. Что нам надо? Нам нужно в каком-то месте написать код, который будет выполняться точно тогда, когда все юнит-тесты выполнились. В этом месте нам нужно проанализировать вот это вот поле fail_count. В каком месте функции TestAll мы точно знаем, что все тесты выполнились? В самом конце, вот здесь вот, перед этой скобочкой. Когда объект tr, объект класса TestRunner, будет разрушаться; раз он начал разрушаться, наша функция закончилась, и никакие юнит-тесты больше мы запускать не будем. Поэтому анализ переменной fail_count мы поставим в деструктор класса TestRunner. Напишем деструктор. И здесь напишем такое: если fail_count больше нуля, то выведем сообщение: fail_count << " tests failed. Terminate". И завершим нашу программу с ненулевым кодом возврата, в данном случае — с единичкой. Exit — это стандартная функция в C++, которая досталась ещё от C, которая завершает выполнение программы с переданным в неё кодом возврата. Давайте снова скомпилируем наш код. Мы запустим его. И видите: TestCount упал, у нас появилось в консоли дополнительное сообщение о том, что один тест упал и вот здесь вот нет красного квадратика, программа не выполняется. Она закончила своё исполнение. Теперь пойдём в TestCount. Вернём правильное поведение для функции GetSynonymCount. Скомпилируем нашу программу. Запустим. И видим, что тесты отработали, все вывели OK, и программа выполняется. Мы можем вводить в неё запросы, можем добавить синонимы a и b и ввести команду COUNT b. И получить один. Таким образом за счёт создания класса TestRunner, подсчёта внутри него количества упавших тестов и проверки этого количества упавших тестов в деструкторе мы получили желаемое поведение наших тестов. Если хоть один из них стреляет, то наша программа завершает своё выполнение. Если все выполняются, то мы переходим к основному решению. Тут, конечно же, ещё важно, что мы обернули это всё в функцию TestAll, чтобы деструктор класса TestRunner выполнялся вот здесь, грубо говоря, а не в конце функции main. Именно поэтому мы TestRunner объявили не в функции main, а внутри TestAll. Так что функция TestAll здесь важна. И при этом за счёт того, что мы выполняли это в деструкторе, мы вот здесь вот можем добавлять запуски каких угодно тестов, и наша функциональность по проверке количества упавших тестов в конце выполнения всех тестов, она никуда не денется, она автоматически будет выполняться. Вот даже давайте я тут запущу вот такой большой набор тестов. Вот они все выполнились, и программа продолжила свою работу. На этом мы закончим улучшение и разработку нашего юнит-тест фреймворка, и, на самом деле, мы получили весьма удобный для использования фреймворк. И в следующем видео мы ещё раз сделаем обзор того, что у нас в итоге получилось.