[БЕЗ_ЗВУКА] Итак, начнем последовательно улучшать наш фреймворк для создания юнит-тестов. Первый недостаток, с которым мы начнем бороться, заключается в том, что когда срабатывает функция assert, то мы в консоли не видим, чему равен каждый из операндов. Мы постараемся что-то такое сделать с нашим кодом, чтобы он работал как раньше, но при этом, если мы в assert сравниваем между собой два аргумента, то в консоли было видно, чему равен каждый из них. Для начала давайте поймем, что же мы хотим. Хотим мы следующее. Вот у меня есть пример, где в переменную x записывается результат сложения 2 и 2. Мы сделаем assert, что x должен быть равен 4. Что бы мы хотели? Мы бы хотели, что если этот assert сработал, то в консоли мы бы увидели вот такое сообщение. Например, x у нас оказался не 4, а 5, тогда мы в консоли видим сообщение Assertion failed, то есть assert сработал, и мы видим, что x оказался равен 5. Соответственно, мы, сразу видя, чему оказался равен x, можем построить какую-то гипотезу, из-за чего у нас не работает функция Add. Может быть, мы где-то случайно добавили +1. Кроме того, мы хотим, чтобы этот вывод в консоль работал для всех типов, а не только для целых чисел. Посмотрим второй пример. У нас есть вектор sorted, которому мы присваиваем результат функции Sort, передавая туда последовательность 1, 4, 3. И мы пишем assert, что sorted должен оказаться равен вектору {1, 3, 4}, то есть он должен отсортироваться. Если этот assert сработает, то мы хотим в консоли увидеть вот такое сообщение. В правой части у нас [1, 3, 4], это то, что мы ожидали. А слева мы сразу видим, что на самом деле после фукнции Sort вектор оказался равен [1, 4, 3]. Ровно таким, который мы передали в функцию Sort. Значит, сразу мы можем сделать гипотезу, что, возможно, функцию Sort просто забыли реализовать, она пустая и ничего не делает. Или там есть какие-то странные условия, которые приводят к тому, что последовательность не сортируется. То есть тот факт, что мы выводим что-то в консоль при срабатывании assert, он нам помогает отладить программу, он нам помогает догадаться, где же на самом деле ошибка. Хорошо. Давайте теперь подумаем, а как мы можем этого добиться. Смотрите, мы хотим написать какую-то функцию, которая принимает два значения произвольного типа, сравнивает их между собой и как-то реагирует на то, что они оказались не равны. Мы с вами уже знаем средство языка C++, которое позволяет в функцию принимать аргументы произвольных типов. Это шаблоны функций. Давайте этим воспользуемся. Давайте напишем шаблон AssertEqual, который будет принимать два аргумента произвольного типа и сравнивать их между собой. Давайте это сделаем. Переключимся в Eclipse и вот здесь сразу перед первым тестом напишем шаблон. Пишем template <class T, class U>, void AssertEqual (const T& t, const U& u). Проверим, если t != u, то что надо сделать? Если значения оказались не равны, нам нужно как-то отреагировать на это, как-то сделать так, чтобы мы узнали, что вот этот assert не сработал. Давайте для этого бросим исключения. Давайте тогда подключим exception и здесь сделаем throw runtime_error. И сюда, в конструктор runtime_error мы хотим передать сообщение, в которое мы хотим пометить значение в данном случае переменных t и u. Как это сделать? Давайте сформируем строчку, которая это делает. Для этого мы подключим строковые потоки, объявим переменную ostringstream и запишем в нее то сообщение, которое мы хотели: Assertion failed: " << t не равно u. И передадим в конструктор runtime_error то сообщение, которое у нас получилось. Давайте скомпилируем то, что у нас пока есть. Отлично, оно компилируется. У вас мог возникнуть вопрос, а почему я здесь для двух аргументов использую разные типы: типы t и u. Вроде как мы же сравниваем их между собой, и логично было бы, чтобы они были одного типа. На самом деле, так шаблон AssertEqual более удобен в использовании, особенно когда дело доходит до целочисленных типов. Потому что мы с вами уже знаем, что в C++ много различных целочисленных типов, и вот для того, чтобы сравнивать, например, int и int64_t, которые являются разными типами, удобно передавать их с помощью разных шаблонных аргументов, и мы посмотрим, как мы этим будем пользоваться, совсем скоро. Давайте этот наш шаблон AssertEqual встроим в один из тестов. И начнем мы с теста TestCount, который проверяет, что функция GetSynonymCount, возвращающая количество синонимов для данного слова, работает правильно. Что мы сделаем? Мы заменим вызов assert на наш AssertEqual, только внутри этой функции. И так как у нас AssertEqual принимает два параметра, то первым параметром является результат вызова GetSynonymCount, а вторым то число, которое мы ожидаем. Скомпилируем наш код. Мы с вами видим, что у нас есть Warning, который [БЕЗ_ЗВУКА] нам говорит о сравнении между знаковым и беззнаковым целыми числами. Если мы два раза по нему нажмем, то мы попадем внутрь нашего AssertEqual. А вот здесь, если мы нажмем ниже, то мы видим, что это происходит при вызове AssertEqual. В чем тут дело? Как раз в том, что мы передаем это значение и вот этот нолик через разные типы, и компилятор понимает, что тип T — это тип функции GetSynonymCount, вот она у нас, это size_t, это беззнаковый тип. А нолик, который мы там передаем, вот этот вот, он, как мы с вами уже знаем, имеет тип int, это тип всех числовых констант в C++ по умолчанию. Поэтому у нас получается сравнение size_t с int, и компилятор ругается. Как нам этого избежать, мы уже знаем. Мы здесь просто напишем u и сделаем тем самым константы беззнаковыми. Снова скомпилируем наш код. Отлично, он компилируется. Теперь мы поступим следующим образом. У нас с вами в этом решении допущен баг, который мы с вами уже исправляли несколько видео назад. Мы его исправим еще раз, чтобы не стрелял тест на функцию AddSynonyms. Выполним наш код. Отлично, он выполнился. Мы видим, что все юнит-тесты успешно отработали. Прекрасно. Но мы на самом деле ведь вот этот AssertEqual писали не на случай, когда все тесты успешно работают, а когда тесты стреляют. Давайте мы с вами осознанно допустим ошибку в GetSynonymCount и убедимся, что AssertEqual действительно выводит в консоль аргументы, когда он срабатывает. Запустим наш код, вот у нас явно выстрелил какой-то тест. Смотрим в консоль, и мы видим сообщение Assertion failed: 1 != 0. При этом вы видите, что у нас пропала та удобная вещь, которая была в оригинальной функции Assert. Мы не видим, какой именно assert сработал. Мы сейчас эту проблему решим достаточно быстро. Пока же мы видим, что мы действительно выводим в консоль значения аргументов, которые были переданы в функцию AssertEqual. Вот здесь мы осознанно прибавили к результату 1, поэтому у нас появилась 1, хотя мы ожидали 0. Хорошо. Давайте вернем в AssertEqual то преимущество, которое было в функции Assert, пусть она нам тоже будет говорить, какой именно assert сработал. Поступим мы здесь таким образом. Мы в AssertEqual будем передавать строчку hint. Эта строчка будет выводиться в конце сообщения теста. Hint: "<<hint. Чтобы в экран влезало, я это отформатирую. Тогда мы идем в функцию TestCount и вот здесь вот передаем уникальную строчку, которая нам позволит однозначно идентифицировать тот assert, который сработал. Допустим, здесь мы напишем count for empty и здесь тоже укажем какие-то уникальные строчки. [БЕЗ_ЗВУКА] Компилируем то, что у нас получилось. Запускаем, видим, как у нас сработал assert. Мы видим, что он сработал и вывел в консоль count for empty. Теперь мы можем взять эту строчку и поискать ее в исходниках. И понять, какой именно assert у нас сработал. Конечно, это не настолько удобно, как было в оригинальном Asssert. Там не надо было ничего в него добавлять, чтобы он нам сказал, в какой именно строчке сработала проверка. На самом деле, нам на данный момент не хватает знаний C++, чтобы повторить эту функциональность. Поэтому мы воспользуемся теми знаниями, которые у нас уже есть, и вот таким, пусть не самым элегантным и не самым удобным образом, но все-таки мы решим проблему и научим наши assert'ы сообщать, в каком именно месте сработала проверка. Прекрасно. Мы в этом видео смогли — давайте я сейчас его открою — написать шаблонную функцию AssertEqual, которая позволяет увидеть в консоли значения аргументов при срабатывании соответствующей проверки. В следующих видео мы внедрим этот шаблон во все наши тесты, которые есть в нашей программе.