Примеры C#. DataGenerator. Тесты и рефакторинг

Поделиться
HTML-код
  • Опубликовано: 30 сен 2024

Комментарии • 23

  • @PsyMaza
    @PsyMaza 6 лет назад +3

    К сожалению автор перестал выпускать свои видео. Надеюсь он жив здоров, т.к. со всех ресурсов пропал в одно время.
    Если кому то нужно, то я нашел его проект на github.
    github.com/defazze/DataGenerator/tree/master/DataGenerator.Data.Test

  • @BoxaShu
    @BoxaShu 8 лет назад +11

    А вот у меня, к началу этой лекции прошло 5 тестов из 8. Был очень удивлен тем, что тест пароля так же прошел. Стал разбираться.
    Оказалось что в NUnit проверка Assert.That(null, Is.Not.Empty); спокойно проходит и тест не падает.
    Как итог, конструкцию Assert.That(password, Is.Not.Empty); заменил на: Assert.That(string.IsNullOrEmpty(password), Is.False); и тест стал падать, как ему и положено в начале этого урока.
    На всякий случай, параметры среды. VS 2013 CEd; .NET 4.0; nunit.framework 3.2.0.0

    • @Defazze
      @Defazze  8 лет назад +1

      +Boxa Shu Хорошее замечание!

    • @androidtv5628
      @androidtv5628 7 лет назад

      Огромное спасибо за курс видео уроков. Пожалуйста, подскажите вкратце, можно ли как-то написать для библиотеки интерфейс (то есть, как мне использовать эту библиотеку в целом)? Делаю исключительно для себя в целях изучения языка.

  • @umkamaks3296
    @umkamaks3296 6 лет назад +3

    Все чётко и по делу, воды нет, инфа с первого раза понятна! Спасибо за объяснение! А то другие льют воду, одна сплошная книжная терминология! Либо сами не понимают излогаемый материал, либо не могут подобрать язык, либо выпендриваются! Ещё раз спасибо за доступность

  • @sergepikovsky3385
    @sergepikovsky3385 8 лет назад +4

    52. Закончим метод GenerateUser для прохождения тестов GenerateUser_PasswordRequired() и GenerateUser_RegistrationDataPeriod().
    53. Пароль - это случайное 4-значное число. Создаем закрытое поле "_random" типа System.Random для его инкапсуляции,
    (для генерации случайных чисел надо иметь экземпляр типа Random) и с его помощью добавляем сроку:
    entity.Password = _random.Next(1000, 10000).ToString();
    54. Дописывая метод GenerateUser для теста GenerateUser_RegistrationDataPeriod() в задание уже не смотрим, то что было в
    задании перекочевало в тест. Дата должна быть в диапазоне 01.01.2010 - 29.02.2016.
    55. Тесты на проверку пароля и даты проходят, но нюанс в следующем: Поскольку мы используем генератор случайных чисел,
    нам надо проверить попадание в диапазон одного случайного числа, но как можно большего количества случайных чисел.
    Основываясь, на нашей выборке можно прикинуть, что наш "тест на прохождение даты" хорошо бы было запустить примерно
    10 000 раз(? почему столько и, вообще, важно ли это ?). Для этого под атрибутом метода [Test] добавляем декорацию
    атрибутом [Repeat(10000)]. Таким образом мы с большей вероятностью утверждаем, что задаваемые нами случайные даты
    попадут в тестовый диапазон.
    56. После этого имеем не пройденный тест по дате регистрации с датой 20.04.2016, т.к. и апрель и 2016 у нас разрешены.
    57. Вводим стоку для разрешения коллизии: if (year == 2016 && month > 2) month = 2;. Тест по дате теперь пройден.
    58. Реализуем метод формирования строки скрипта string GetValueLine(UserEntity entity), собирающей воедино все
    поля плоского класса UserEntity и дату регистрации. В VS 2015 для этого можно использовать из string.Format
    механизм интерполяции строк. Запись получается более компактной и понятной.
    После тестируем и итерационно исправляем ошибки.
    59. Реализуем GetInsertLine() возвращающий строку (берем ее нагло из соответствующего тестового метода)
    @"INSERT INTO BlogUser (Name, Surname, Patronymic, Email, UserLogin, Password, RegistrationDate)"
    Теперь все тесты проходят.
    60. Можно сказать, что класс ScriptGenerator в первом приближении (т.к. остался метод CreateScript) спроектирован.
    Время заняться им. Почему с ним возникли сложности? Это произошло потому, что по логике этого метода он должен
    обращаться для сборки скрипта к остальным методам класса. Но, по логике unit-testing тест пишется и проверяет только
    на базовую логику метода. Проверяя метод CreateScript мы будем проверять уже проверенные нами методы с их логикой.
    Unit-test получиться избыточным и будет проверять не совсем то, что нужно. Как мы выходим из этой ситуацией?
    Нам необходимо разделить метод CreateScript на часть, которая будет обращаться к остальным методам класса и
    на часть, которая будет реализовывать базовую логику сборки скрипта, может иметь сигнатуру:
    public string CreateScript(int entityCount) т.е. метод получает количество необходимых сущностей, генерит их
    при помощи GenerateUsers это количество сущностей, преобразовывает их при помощи метода GetValueLine в строке,
    а потом отдает все эти строки со значениями и строку, полученных при помощи GetInsertLine в новый метод MergeLines:
    public string MergeLines(IEnumerable valueLines, string insertLine ) и этот метод собирает скрипт по
    определенному нами формату. А именно: заголовки - строка содержащая инструкцию "INSERT", потом строки, содержащие
    значения, перечисленные через запятую. И вот уже этот метод MergeLines мы можем покрыть unet-test-ами.
    61. Однако тут возникает вторая сложность: поскольку наш метод MergeLines инкапсулирует внутреннюю логику класса он
    должен быть объявлен как privat. Нет нужды вытаскивать его наружу, т.к. внешнему потребителю этот метод не интересен.
    Unit-test-ами мы можем покрывать только открытые методы. Что же нам делать? Мы можем объявить данный метод
    с модификатором internal. Тогда метод будет виден только в пределах своей сборки,
    те в пределах библиотеки TestDataGenerator.BL . A внешний потребитель находится в библиотеке TestDataGenerator
    и он не видит данного метода, но, аналогично, потребителю не будет виден и нашей тестовой сборке,
    т.к. она в отдельной библиотеке.
    62. Более того, чтобы тесты увидели метод такой как этот (с модификатором доступа internal) необходимо внести
    определенные изменения в свойства нашей сборки: в проекте TestDataGenerator.BL заходим в Properties и заходим
    в файл AssemblyInfo.cs и видим разные атрибуты, которые применяются к сборке. Добавляем сюда еще один атрибут:
    [assembly: InternalsVisibleTo("TestDataGenerator.BL.Test")] - данный атрибут расширяет область видимости internal
    на те сборки, которые мы указываем в параметре. Теперь мы сможем написать на наш метод unit-test.
    63. Переходим в тест и первое, что делаем - меняем тип возвращаемого генератора с интерфейсного на конкретный:
    public IScriptGenerator _generator; --> public ScriptGenerator _generator;
    т.к. метод MergeLines не входи в интерфейс IScriptGenerator, т.к. он инкапсулирует внутреннюю логику класса,
    но для тестов он должен быть доступен. По этому мы отходим от интерфейса к конкретному классу.
    64. Теперь пишем метод для тестировки метода MergeLines. Вопрос: Почему мы вместо похожих на реальные строки в методе
    используем строки INSERT LINE, VALUE LINE 1 и т.д.? А просто они нам здесь не нужны, ведь задача метода - только
    слить передаваемые строки в определенном порядке. Это мы и проверяем. Запускаем метод и убеждаемся, что он
    не прошел (т.к. мы его еще не реализовали). Совсем не обязательно писать все тесты перед началом разработки приложения
    абсолютно на все классы, интерфейсы. Можно написать один класс без реализации, написать на него тесты, потом реализовать класс,
    потом сделать второй класс... и т.д. Разработка может быть итеративной, но тест - сначала, потом - реализация. Так и здесь:
    сначала покрыли тестами и реализовали базовые методы ScriptGenerator, а теперь дошли до MergeLines.
    65. Реализовываем метод MergeLines после провального теста. Записываем в созданый экземпляр StringBuilder заголовочную строку,
    а затем в цикле дописываем строки со значениями и если строка не первая, то перед строкой добавляем запятую.
    66. Запущенный тест не проходит из-за того, что в тесте MergeLines не корректно сформирован ожидаемый результат.
    Отказываемся от константы, переходим на переменную и используе string.Format. и команду конца строки. Тест пройден.
    Логика сборки скрипта у нас есть и мы можем приступить к реализации самого метода CreateScript(int entityCount).
    Мы не будем покрывать его тестами, т.к. он просто обращается к остальным методам нашего класса.
    Мы получаем количество записей на вход, и пре помощи метода Repeat класса Enumerable формируем необходимое колличество
    наших сущностей-пользователей. Потом эти сущности пользователи переводим в набор строк при помощи метода GetValueLine,
    затем получаем заголовочную строку через GetInsertLine, затем собираем их с помощью только что написанного метод MergeLines.
    67. Теперь, с вероятностью 99% можем сказать, что наш код класса ScriptGenerator полностью покрыт unit-test-ами.
    68. По хорошему, конечно, надо бы покрыть тестами и метод CreateScript. Мы могли его вынести CreateScript в отдельный класс,
    тогда код нашего класса был бы покрыт на 100%, а в нашем новом классе мы бы заменили обращения к нашему классу ScriptGenerator
    очередным mock-ом для unit-testing (как в случае репозитария). Он бы точно так обращался бы к методам
    GenarateUser, GetValueLine, InsertLine в продакшн версии, и эти методы бы пренадлежали к классу ScriptGenerator, а в тестовой
    версии они бы принадлежали какому-то mock-у. И тогда, в этом нашем новом классе мы бы могли покрыть тестами так же и
    метод CreateScript.
    69. Здесь возникает тонкая грань и вопрос "Когда остановиться?". Жесткого правила нет о том, что unit-test-ами должно быть
    покрыто 100% бизнес логики. Есть определенные метрики покрытия кода (хорошие и плохие). Принято считать, что если
    Unit-test-ами покрыто хотя бы 70-80%% нашего кода то это уже хорошо. Конечно, акцентировать внимание нудно на боле ли менее
    сложных методах. Нет смысла покрывать unit-test-ами какие-то тривиальные вещи (а CreateScript - достаточно тривиален).
    70. Что же у нас получилось при применении Test Driven Development и при проектировании класса?
    Теперь, вместо того, чтобы получать скрипт на основании количества требуемых записей мы можем создавать такое же
    количество плоских классов UserEntity, содержащих данные, мы можем переводить этих пользователей в строки,
    мы можем получать заголовочную строку. При этом метод MergeLines наиболее интересен, т.к. он по сути может применяться
    для соединения любых строк, не только в нашем конкретном скрипте, но и в целом. Его логика заключается в том, что
    оно просто форматирует итоговую строку на основании входных строк. По сути он даже не завязан на класс ScriptGenerator,
    потому, что ему все равно что получать на вход. Если бы наш класс был бы один из многих генераторов скриптов,
    то этот метод можно было бы размещать в базовом классе генераторов. Просто наш подход Test Driven Development
    позволил создать достаточно гибкую архитектуру класса ScriptGenerator, которую достаточно легко модифицировать.
    Гибкость нашего кода - ценное свойство, т.к. при необходимости получить эту же логику в похожем проекте вместо
    копи-паста мы сможем эту логику легко получить, так как логика структурирована и разделена.

  • @MrMaksimsergeevich
    @MrMaksimsergeevich 6 лет назад +3

    Все 4 урока на одном дыхании, посмотрел и будто бы прочитал книгу. Жаль, пару дней назад это не видел - пригодилось бы на собеседовании. Спасибо! Не останавливайтесь на достигнутом, Ваш опыт очень ценен.

  • @КонстантинЧерышев
    @КонстантинЧерышев 8 лет назад +8

    А когда следующая часть?

  • @Bah1918
    @Bah1918 2 года назад

    Великолепно. ОГРОМНОЕ СПАСИБО. Ну так как же сгенерировать текстовый документ для SQL.

  • @sergeydmitriev5281
    @sergeydmitriev5281 8 лет назад +2

    спасибо за 4 урока. освежил память и узнал что-то новое для себя.

  • @Milording
    @Milording 8 лет назад +2

    Любимая серия! Очень круто. Особенно, если Фаулера читать параллельно. Спасибо большое. Единственное, капсом писать наименование констант, это сишный подход, в шарпе по-другому, о чем решарпер сообщает.
    Еще очень жаль, что от интерфейса в тестах мы вернулись к реализации. Ведь если теперь мы захотим добавить другой класс, реализующий интерфейс IScriptGenerator, то как быть?

    • @Defazze
      @Defazze  8 лет назад +1

      +Milording Решарпер можно настроить ) А тесты можно разделить - на тест интерфейса и тест конкретного класса.

  • @ag4191
    @ag4191 5 лет назад

    А зачем мы создали TestDataGenerator.Data.Test, если вы сами сказали, что тестируются только бизнес логика приложении??

  • @alexanderkostyuk755
    @alexanderkostyuk755 6 лет назад

    Чтобы протестировать метод MergeLines можно было бы его сделать protected и создать мок-класс декоратор производный от ScriptGenerator и в нем создать public метод для тестирования который будет дергать MergeLines базового класса

  • @delchewwwbas
    @delchewwwbas 4 года назад

    Вот за эту группу уроков реально спасибо! Прям побежал рефакторить свой небольшой стартовый проектик.

  • @vladimirezh
    @vladimirezh 7 лет назад

    Недостаток архитектуры. Нельзя делать видимым метод из-за юнит тестов. Даже внутри сборки. Если бы метод CreateScript принимал poco, а не count, то протестировать было бы очень просто. Не надо нарушать принцип открытости/закрытости. Разделение интерфейса на две части: генерация скрипта в определенном формате и генерация данных решила бы эту задачу.

  • @adilet123
    @adilet123 6 лет назад

    Извиняюсь за вопрос, но данная архитектура называется ТРЕХуровневая???

  • @2s1234m
    @2s1234m 7 лет назад +1

    а можно ссылку на солюшн ?

  • @АртемЯковлєв-р2м
    @АртемЯковлєв-р2м 7 лет назад

    Благодарю, вас!
    Вы великолепно преподносите материал, вот бы все преподаватели также относились к своим урокам.
    Вот читаю параллельно книгу Марка Симана "Dependency Injections in .Net", многое было не ясно, но ваши уроки помогли мне понять аспекты книги.
    Благодарю за ценный практический опыт, хотелось бы что бы, вы продолжали выпускать новые видео.

  • @yakovvorontsov62
    @yakovvorontsov62 7 лет назад

    как сделать, чтобы решарпер показываю управляющие символы в тесте ? %)

  • @syso300
    @syso300 5 лет назад

    Спасибо за видео! Скажите в этом примере используется паттер Фабрика?

    • @DmytroLynda
      @DmytroLynda 4 года назад

      Нет, тут использовался Dependency Injection ( Внедрение зависимостей) через конструктор.

  • @dragsdovakin7805
    @dragsdovakin7805 7 лет назад

    снимай C++