[БЕЗ_ЗВУКА] Итак, в предыдущем видео мы с вами познакомились с тем, как писать юнит-тесты, и давайте вернемся к нашей задаче «Синонимы» и покроем юнит-тестами каждую из выделенных нами ранее функций. Давайте, например, начнем с функции AddSynonyms, которая добавляет два слова в словарь. Напишем функцию TestAddSynonyms и рассмотрим различные случаи, потому что как мы выбираем те входные данные, на которых мы будем тестировать наши функции? Точно так же, как мы это делаем, когда формируем тесты для решений целиком. Мы изучаем какие-то различные классы входных данных и для каждого из них подбираем тест. Давайте напишем первый тест. Будет он такой. Мы представим, что у нас словарь пустой, и когда мы добавляем туда какие-то два слова, то эти слова в словаре должны появиться. Давайте начнем писать. Поставим операторные скобки, чтобы переменные, которые мы объявим внутри, за пределами этого маленького теста не мешались. И смотрите, что нам надо сделать. Нам нужно объявить словарь синонимов так, чтобы он был пустой. Объявили. И тут я хочу сразу заметить, что, смотрите, нам много где приходится дублировать вот этот тип map из строки во множество строк: в каждой из функций, внутри функций, main теперь в тестах. Это неудобно. Код становится громоздким, и если вдруг вы передумаете и захотите поменять этот тип на какой-то другой, то вам придется сделать это во многих местах. Чтобы избавиться от этого недостатка, давайте в одном месте зафиксируем этот тип. Делается это так. Мы пишем using, это ключевое слово, придумываем нашему типу имя и говорим, что теперь Synonyms это map из строки в set<string>, множество строк. И теперь мы можем везде, где мы явно писали map set<string>, заменить это на имя Synonyms. И мы вот везде в нашем коде выполняем эту замену. Давайте скомпилируем нашу программу. Она скомпилировалась. Отлично. Мы выполнили замену явного типа на его другое имя, и по забавной иронии это другое имя можем назвать тоже синонимом, и теперь можем писать, использовать более короткое имя. Итак, мы пишем наш тест и делаем следующее. Мы объявили пустой словарь синонимов и вызываем функцию. И добавляем в словарь синонимов два слова, a и b, говорим, что a и b теперь синонимы. Что мы ожидаем от функции AddSynonyms? Мы ожидаем, что у нас появится в словаре две записи. Первая будет то, что у строки a появился синоним b. И наоборот, то, что у строки b появился синоним a. И это наше ожидание мы формируем в виде assert'а. Мы говорим assert (emtpy = = expected). Чтобы у нас assert работал, давайте его подключим. И это будет наш первый тестовый сценарий. Давайте добавим еще один тест к функции AddSynonyms. Возьмем какой-то уже наполненный словарь и проверим, что когда в словаре уже есть какие-то данные, то когда мы к нему их добавляем, то все происходит корректно. Объявим словарь и скажем, например, что у строки a уже есть синоним b, у строки b есть синонимы a и c. И у строки c есть синоним b. Смотрите, что мы делаем. Мы же говорили, что юнит-тесты тестируют компоненты программы в изоляции. Вот и сейчас мы подаем на вход нашей функции корректно сформированные словари синонимов и проверяем, что после выполнения этой функции они остаются корректными. То есть если корректность этого словаря нарушена в другом каком-то компоненте программы, то юнит-тесты на эту функцию не сработают, на функцию AddSynonyms, а сработают на другое. Итак, мы говорим, что, например, мы добавляем в наш словарь два новых слова, два новых синонима a и c. Каким должен получиться словарь, после того как мы добавим в него что-нибудь? Сформулируем это точно так же, expected. У нас должен у слова a появиться синоним c, а у слова c появиться синоним a. И точно так же добавим assert, что synonyms == expected. Прекрасно. Мы с вами сейчас написали юнит-тесты для функции AddSynonyms. Давайте сделаем то же самое для функции GetSynonymCount и AreSynonyms. Я не буду их писать в целях экономии времени, они у меня здесь припасены рядышком, я их просто скопирую. [БЕЗ_ЗВУКА] Давайте быстренько посмотрим, как устроены тесты для этих функций. Мы здесь сделаем то же самое. Мы сейчас тестируем функцию GetSynonymCount, которая возвращает количество синонимов для данного слова. И то же самое, мы говорим, что если у нас словарь пустой, то для любого слова, которое мы туда подадим, GetSynonymCount должна вернуть ноль, потому что словарь пустой. И мы это оформили тем, что мы объявили пустой словарь, вызвали GetSynonymCount от строчки a и проверили, что результат равен нулю. Другой пример, когда у нас уже есть корректно сформированный словарь, и мы проверяем, что действительно функция GetSynonymCount возвращает значения множеств, которые у нас есть в словаре. То есть мы видим, что для строки a у нас два синонима и поэтому GetSynonymCount от a равен 2. Для b у нас один только синоним, мы проверили, что он один. Строки z у нас вообще нет в словаре, поэтому для нее должен вернуться ноль. И аналогично для функции AreSynonyms, которая проверяет, являются ли два слова синонимами. Мы проверили, что если словарь пустой, то для любых двух слов он должен возвращать false, при этом вот здесь у нас есть дублирование, хотя на самом деле хотелось проверить вот так. И для корректно сформированного словаря мы вот здесь проверили, что, например, у нас для a есть синонимы b и c. И мы вот здесь проверяем, что AreSynonyms для a и b должно вернуть true, для b и a тоже true, для a и c true и так далее. А вот, например, для строки b у нас есть только синоним a, поэтому b и c не должны быть синонимами. Все вот это мы проверили с помощью assert'ов. Хорошо. У нас получился набор юнит-тестов. Давайте, во-первых, скомпилируем программу, убедимся, что все хорошо... и увидим, что не все хорошо. А именно: у нас здесь неудачное копирование случилось. Тут неправильно был назван тип для словаря синонимов. Поэтому давайте снова скомпилируем нашу программу и увидим, что теперь она компилируется. Итак, у нас есть набор юнит-тестов. И я напомню, потому что мы уже много об этом говорим, возможно, мы забыли, что, вообще говоря, наша задача найти ошибку в нашем решении задачи «Синонимы», которая не принимается тестирующей системой. И мы хотим сделать это с помощью юнит-тестов. Как мы это сделаем? Нам надо эти тесты вызвать. При этом хочется разом взять и вызвать все тесты. Поэтому давайте напишем еще одну функцию, назовем ее TestAll и вызовем в ней все наши тесты. TestCount, TestAreSynonyms и TestAddSynonyms. И вот эту функцию мы вызовем в функции main. При этом мы хотим просто сейчас вызвать тесты, мы хотим, чтобы работало решение нашей программы, решение задачи, чтобы оно считывало что-то со стандартного ввода. Поэтому мы просто здесь поставим return 0 на случай, если все наши тесты отработают и у нас не будет найдено ошибок. Давайте скомпилируем программу, компилируется. Запустим ее. Мы видим, что у нас появился красный вывод, значит, какой-то из тестов не сработал. Мы в предыдущем видео видели, что если assert не срабатывает, то вот здесь появляется что-то красное. Ждем, пока Windows одумается, и смотрим, что у нас есть в выводе. В выводе у нас есть следующее. У нас есть сообщение TestCount OK. Это значит, что наши тесты функции GetSynonymCount отработали корректно. Еще у нас написано TestAreSynonyms OK. То есть и функция AreSynonyms в ней не было найдено ошибок. А вот мы видим, что сработал assert, причем он сработал в 38-й строке нашего файла, и сработала проверка empty == expected. Что делаем? Идем в 38-ю строчку. И понимаем, что это тест функции AddSynonyms, это он у нас сработал и нашел ошибку. Когда у нас пустой словарь и мы добавляем в него два слова, у нас получается не такой словарь, какой мы ожидаем. Значит, в нашей программе, в нашем решении задачи «Синонимы» изначально была ошибка при добавлении синонимов. А при получении количества слов вроде как не было. По крайне мере мы не нашли на это тесты. Зайдем в функцию AddSynonyms и посмотрим на нее внимательно. Мы видим, что мы получаем словарь синонимов и два слова. Что мы делаем? Мы идем словарь синонимов, берем второе слово и добавляем сюда первое. А дальше мы берем словарь синонимов, берем первое слово и добавляем в его множество синонимов себя же. Это явно неправильно. Скорее всего, когда написали вот эту строчку, мы ее скопипастили вниз, но не поправили. Поэтому вот она ошибка. Мы должны в map для первого слова добавить второе, а не первое. Мы только что с помощью юнит-теста нашли ошибку. Давайте скомпилируем наше решение, после того как мы его исправили, и запустим. У нас ничего не упало. При этом мы в TestAddSynonyms не вывели OK, давайте это исправим, чтоб у нас все было красиво. Перекомпилируем, запустим. Вот мы видим в консоли, что все наши тесты отработали успешно и не нашли ошибки в нашем коде. Что мы можем сделать? Мы можем попробовать снова отправить решение нашей задачи в тестирующую систему, потому что мы смогли с помощью юнит-тестов найти в нем ошибку. Правда, предварительно мы должны не забыть закомментировать вызов тестов нашего решения, потому что они что-то выводят в стандартный вывод, и этот вывод смешается с тем, что выводит наше решение, и оно точно не пройдет в тестирующей системе. Хорошо. Давайте проверим, смогли ли мы исправить все ошибки. Идем в Coursera. Меняем файл. [БЕЗ_ЗВУКА] Отправляем его на проверку. Дожидаемся, когда выполнится тестирование. Мы видим, что тестирование завершилось, и наше решение принято тестирующей системой. Отлично. Давайте подведем итоги. Мы с вами узнали, что если идеи для тестов у вас закончились, для решения тестов целиком, то нужно выполнить декомпозицию вашего решения и покрыть каждый из получившихся блоков юнит-тестами. Это хорошо, потому что юнит-тесты придумать проще, чем тесты для задачи целиком. У вас есть маленькие компоненты, которые решают более маленькие задачи, и тесты поэтому придумываются проще. При этом мы только что посмотрели, что юнит-тесты — это эффективный способ определить, в какой части программы есть ошибка. Потому что только что в нашем примере что было? Мы видели, что в функциях GetSynonymCount и AreSynonyms тесты не нашли ошибок, но при этом они нам явно показали, что ошибка есть в функции AddSynonyms. И именно в этом и заключается сила юнит-тестов. Если ошибка есть, они указывают, в каком именно месте она у вас возникает.