3 4 Реферат Курсовой проект: 77 страниц, 22 рисунка, 3 таблицы, 6 используемых источников, 4 приложения. СИМПЛЕКС-МЕТОД, VISUAL STUDIO, C#, WINDOWS FORMS, МЕТОДЫ ОПТИМИЗАЦИИ, ИНФОРМАЦИОННЫЕ ТЕХНОЛОГИИ Целью данного курсового проекта является разработка приложения для составления стратегии вложений, минимизирующую наличную сумму, которую заказчик должен иметь в самом начале для выплаты всех денег по заключенному контракту. В результате выполнения курсовой работы разработано десктопприложение, решающее поставленную задачу. Реализованное приложение позволяет сэкономить денежные средства заказчика, направленные на приобретение земельного участка. 5 Содержание Введение ........................................................................................................... 8 Нормативные ссылки .................................................................................... 10 1Анализ технических требований и уточнение спецификаций ............... 11 1.1 Анализ задания и выбор технологии, языка и среды разработки .. 11 1.2 Анализ процесса обработки информации и построение функциональных диаграмм .................................................................................. 12 1.3 Анализ хранимой информации и выбор структур данных для ее представления ........................................................................................................ 15 1.4 Выбор методов и алгоритмов решения задачи ................................ 15 2 Проектирование структуры и компонентов программного продукта .. 18 2.1 Разработка интерфейса пользователя ............................................... 18 2.1.1 Разработка структурной схемы интерфейса ............................. 18 2.1.2 Построение графа (диаграммы) состояний интерфейса .......... 19 2.1.3 Разработка форм ввода-вывода информации ............................ 20 2.2 Разработка алгоритма основной программы и структурной схемы программного продукта ........................................................................................ 21 2.2.1 Описание структуры приложения и схема связности модулей ............................................................................................................................. 21 2.2.2 Схема движения информационных потоков ............................. 22 2.3 Разработка основных алгоритмов программного продукта ........... 23 2.4 Разработанные классы программы .................................................... 26 3 Тестирование программы .......................................................................... 32 3.1 Разработка плана тестирования ......................................................... 32 3.2 Разработка алгоритма процедуры тестирования ............................. 33 3.3 Оценка результатов тестирования ..................................................... 33 4 Сопровождение .......................................................................................... 35 6 4.1 Руководство пользователя .................................................................. 35 4.2 Обслуживание модели, алгоритма, программы и их эксплуатация ................................................................................................................................. 36 Заключение .................................................................................................... 38 Список используемой литературы .............................................................. 40 Приложение А – Планы тестирования интерфейса ................................... 41 Приложение Б – Листинг приложения ....................................................... 44 Приложение В – Листинг модульных тестов ............................................. 70 Приложение Г - Антиплагиат ...................................................................... 74 7 Введение Современное информационное общество неразрывно связано с обработкой данных и оптимизацией процессов. В этом контексте методы линейного программирования, в частности симплекс-метод, играют ключевую роль в решении различных задач оптимизации, от экономических до технических. Предметная область, к которой относится данная курсовая работа, охватывает проблематику получения максимальной прибыли от целевых фондов. Симплекс-метод является одним из наиболее эффективных инструментов для решения таких задач, благодаря своей способности находить оптимальное решение в многообразии ограничений и переменных. Цель настоящей курсовой работы заключается в исследовании и применении симплекс-метода для решения конкретной задачи оптимизации в предметной области. Задачи, поставленные перед курсовой работой, включают в себя: − Изучение теоретических основ линейного программирования и симплекс-метода. − Формализация конкретной задачи оптимизации в рамках выбранной предметной области. − Программная реализация симплекс-метода для решения данной задачи с использованием предполагаемых средств проектирования. − Анализ полученных результатов и их применение в реальной предметной области. Актуальность данной работы обусловлена постоянным развитием технологий и растущей потребностью в оптимизации процессов в различных сферах деятельности. Эффективное использование симплекс-метода позволяет существенно сократить временные и материальные затраты, повысить производительность и эффективность бизнес-процессов. Целью данной курсовой работы является исследование и применение симплекс-метода для решения конкретной задачи оптимизации в выбранной предметной области. В результате работы будет получено практическое 8 решение, демонстрирующее применимость и эффективность симплекс-метода в решении реальных задач оптимизации. Для разработки приложения, реализующего симплекс-метод для решения задачи оптимизации в выбранной предметной области, предполагается использовать следующие средства проектирования: − Язык программирования: программирования будет использован В качестве основного языка C#. Он предоставляет широкие возможности для создания приложений под платформу .NET и имеет богатые инструменты для работы с математическими вычислениями. − Интегрированная среда разработки (IDE): Для комфортной работы над проектом предполагается использовать Visual Studio 2022, которая обладает всеми необходимыми инструментами для разработки приложений на C#, включая удобный редактор кода, отладчик и инструменты для создания пользовательского интерфейса. − CASE-средство: визуализации классов Для и моделирования взаимодействия структуры между приложения, компонентами будет использовано средство моделирования Star UML. Оно позволяет создавать диаграммы классов, диаграммы последовательностей и другие типы диаграмм для проектирования программного обеспечения. Выбор этих средств проектирования обеспечит эффективную разработку приложения, сочетающего в себе удобство использования, производительность и возможности визуализации результатов. 9 высокую Нормативные ссылки В данном курсовом проекте использованы следующие нормативные ссылки: − ГОСТ Р 1.5-2004 Стандартизация в Российской Федерации. Стандарты национальные Российской Федерации. Правила построения, изложения, оформления и обозначения. − ГОСТ Р 1.12-2004 Стандартизация в Российской Федерации. Термины и определения. − ГОСТ Р 7.0.5-2008 СИБИД. Библиографическая ссылка. Общие требования и правила составления. − ГОСТ Р ИСО 9000-2008 Системы менеджмента качества. Основные положения и словарь. − ГОСТ Р ИСО/МЭК 12207-99 Информационная технология. Процессы жизненного цикла программных средств. − ГОСТ 19.103-77 ЕСПД. Обозначение программ и программных продуктов. − ГОСТ 19.105-78 ЕСПД. Обозначение программ и программных продуктов. − ГОСТ 19.401-78 ЕСПД. Текст программы. Требования к содержанию и оформлению. − ГОСТ 19.101-77 ЕСПД. Виды программ и программных документов. 10 1Анализ технических требований и уточнение спецификаций 1.1 Анализ задания и выбор технологии, языка и среды разработки Выбор использования объектно-ориентированной парадигмы программирования в реализации приложения для данной курсовой работы обосновывается следующими причинами: − Модульность и структурирование: Объектно-ориентированное программирование позволяет структурировать код приложения на основе объектов, которые соответствуют реальным или абстрактным сущностям предметной области. Это обеспечивает лучшую модульность, упрощает понимание и поддержку кода, а также способствует повторному использованию компонентов. − Инкапсуляция и сокрытие данных: ООП позволяет скрыть внутреннюю реализацию объектов и предоставить только необходимый интерфейс для работы с ними. Это повышает уровень абстракции, улучшает безопасность и облегчает сопровождение кода[6]. − Наследование и полиморфизм: Механизмы наследования и полиморфизма позволяют создавать иерархии классов и использовать общие свойства и методы, что способствует повторному использованию кода и уменьшает его объем. Теперь, учитывая преимущества объектно-ориентированного программирования, были выбраны следующие средства программирования: − C# - это мощный и современный язык программирования, который полностью поддерживает объектно-ориентированную парадигму. Он обладает богатыми возможностями для создания объектов, классов, наследования и полиморфизма, что делает его прекрасным выбором для разработки приложений, основанных на ООП. − Windows Forms - это фреймворк для создания графических пользовательских интерфейсов в приложениях под платформу Windows. Он предоставляет широкий набор элементов управления и инструментов для создания интерактивных и интуитивно понятных интерфейсов. Windows Forms 11 хорошо интегрирован с языком C# и предоставляет возможности для быстрой и удобной разработки приложений. − Visual Studio 2022 - это мощная интегрированная среда разработки, которая обладает широкими возможностями для работы с языком C#, Windows Forms и другими технологиями Microsoft. Она предоставляет удобный редактор кода, инструменты для отладки, дизайнера форм, а также интеграцию с другими инструментами разработки и системами контроля версий. 1.2 Анализ процесса обработки информации и построение функциональных диаграмм Вводимую и выводимую информацию можно представить в виде диаграммы вариантов использования, отражающей действия, которые пользователь будет предпринимать во время работы с приложением (рисунок 1). Рисунок 1 – Диаграмма вариантов использования приложения Таким образом были выявлены подлежащие реализации функции приложения, обрабатывающие получаемую информацию и выводящие результирующие значения. Процесс обработки информации для разрабатываемого приложения включает следующие основные шаги: 1. Считывание введённых пользователем данных: 12 − Пользователь вводит данные, необходимые для постановки задачи оптимизации, такие как коэффициенты целевой функции, ограничения и параметры задачи. − Приложение считывает эти данные и формирует математическую модель задачи оптимизации. 2. Пошаговое решение задачи: − Применяется симплекс-метод для решения поставленной задачи оптимизации. − На каждом переменных, шаге проверка алгоритма на происходит оптимальность выбор базисных текущего решения, вычисление и выбор направления движения и пересчёт базисных переменных. − Каждый шаг алгоритма и соответствующие изменения в состоянии задачи фиксируются для последующего анализа. 3. Запись каждого шага в ходе решения: − Приложение сохраняет каждый шаг алгоритма, включая значения переменных, оценки, базисные переменные и другие параметры. − Эти данные могут быть сохранены в виде структуры или объекта для дальнейшего использования и отображения пользователю. 4. Вывод полученных результатов и записанного хода решения задачи: − По завершении решения задачи оптимизации приложение выводит результаты оптимизации, такие как оптимальное значение целевой функции и значения переменных. − Также выводится записанный ход решения, который позволяет пользователю понять, каким образом было получено оптимальное решение. − Результаты могут быть представлены в текстовом или графическом виде, в зависимости от требований к интерфейсу приложения. Такой процесс обработки информации обеспечивает пользователю возможность ввода данных, получения подробного отчёта о ходе решения задачи и вывода результата оптимизации. 13 Данный процесс можно представить в виде функциональной диаграммы (рисунок 2) и её декомпозиции (рисунок 3). Рисунок 2 – Функциональная диаграмма приложения, обобщенная Рисунок 3 – Функциональная диаграмма приложения, декомпозиция Также была составлена концептуальная диаграмма классов, отображающая подлежащие к реализации модули приложения (рисунок 4). 14 Рисунок 4 – Концептуальная диаграмма классов приложения 1.3 Анализ хранимой информации и выбор структур данных для ее представления Основной информацией, которая необходима для написания программного кода симплекс-метода, является базис, а также сама пересчетная таблица. Информация такого рода будет хранится в течении всего времени работы программы, однако, есть необходимость хранить также промежуточные данные, использующиеся в циклах программы, пока ответ не будет найден[2]. При выборе структуры данных для представления базиса и пересчетной таблицы, стоит обратить внимание на следующее: структуры имеют четко определённые, заранее заданные размеры, имеется необходимость обращаться к элементам по индексам для использования в циклах, а также для изменения значения элемента. Для таких целей наилучшим выбором будет массив, который имеет все вышеперечисленные функции. Базис содержит лишь одно измерение, в то время как таблица содержит два, и будет хранится в многомерном массиве. Для определения новой базисной переменной, точнее указания столбца с наибольшим коэффициентом целевой функции, есть необходимость хранить только лишь индекс выбранного столбца. Для таких целей достаточно целочисленной переменной. 1.4 Выбор методов и алгоритмов решения задачи Поставленная в условиях варианта курсовой работы задача предрасполагает к использованию симплекс-метода, поскольку её очень просто перевести математическую модель, подлежащую решению данным способом. 15 Также можно отметить другие причины выбора именно этого варианта решения поставленной задачи: 1. Эффективность: Симплекс-метод является одним из наиболее эффективных методов решения линейных задач оптимизации. Он хорошо работает даже для задач с большим количеством переменных и ограничений. 2. Гарантированная сходимость: Симплекс-метод обеспечивает гарантированную сходимость к оптимальному решению в конечном числе шагов для линейных задач оптимизации. 3. Широкое применение: Симплекс-метод применим для решения разнообразных задач оптимизации, таких как задачи линейного программирования в экономике, производстве, логистике и других областях. 4. Доступность: Симплекс-метод широко изучен и реализован во многих библиотеках и программных средствах для решения задач оптимизации. Это обеспечивает доступность и удобство его использования в различных прикладных задачах. 5. Понятность и интерпретируемость: Решение задачи симплекс-методом обычно сопровождается последовательностью шагов, каждый из которых имеет понятную геометрическую или алгебраическую интерпретацию. Это позволяет легче понять процесс оптимизации и результаты его работы. Исходя из этих факторов, симплекс-метод является логичным и эффективным выбором для решения задачи оптимизации в рамках данной курсовой работы. Симплекс-метод начинается с выбора начального базисного допустимого решения, которое обеспечивает нулевые значения переменных, не являющихся базисными[3]. Затем алгоритм оценивает, как изменение базиса может улучшить целевую функцию, используя метод разрешающего столбца. После выбора разрешающего столбца определяется разрешающая строка, которая ограничивает шаг в направлении улучшения. Далее происходит пересчет базиса и переменных, чтобы найти новое допустимое решение. Алгоритм продолжает эти шаги, двигаясь от одного базисного допустимого решения к другому, пока не достигнет оптимального решения или определит, что проблема не ограничена или недопустима[1]. 16 Описанный выше алгоритм также можно представить в виде блок-схемы (рисунок 5). Рисунок 5 – Блок-схема алгоритма симплекс-метода В данном разделе был проведён анализ технических требований и уточнение спецификаций продукта. Сначала был проведён анализ задания и выбор технологий, языка программирования и среды разработки. Их результатом стал выбор объектно-ориентированной парадигмы программирования, выбор языка программирования C# и использование среды разработки Visual Studio 2022. Затем был проанализирован процесс обработки информации, на основе которого были построены функциональные диаграммы программы. Далее был проведён анализ хранимой информации и выбор структур данных для её представления – двумерные массивы. 17 2 Проектирование структуры и компонентов программного продукта 2.1 Разработка интерфейса пользователя 2.1.1 Разработка структурной схемы интерфейса При разработке интерфейса программы должны быть учтены все выполняемые ею функции: − Визуализация таблицы симплекс-метода в отчете; − Таблица для ввода решения; − Область для вывода решения; − Функция ввода экстремума; − Вспомогательная функция ввода задачи-примера; − Поддержка произвольного количества условий и переменных; Учитывая все функции приложения была построена структурная схема интерфейса (рисунок 6). Рисунок 6 – Структурная схема интерфейса главного окна Вариант приложения, состоящего из одного окна, был выбран по следующим причинам: 18 − Удобство: Пользователю не нужно переключаться между различными окнами или страницами, чтобы выполнить различные задачи. Все функции доступны в одном месте, что упрощает навигацию и улучшает пользовательский опыт. − Простота: Единая форма уменьшает сложность интерфейса, так как пользователь видит только необходимые элементы управления и информацию для выполнения задачи. Это снижает вероятность запутанности и ошибок при взаимодействии с системой. − Минимализм: Одно окно может быть спроектировано с учетом минималистичного дизайна, что делает интерфейс более эстетичным и приятным для использования. Минимизация лишних элементов также помогает сосредоточить внимание пользователя на основных функциях. В целом, единая форма интерфейса может обеспечить более эффективное взаимодействие с системой, улучшая удобство, простоту и моду на минимализм. Это особенно важно в контексте современных требований к пользовательским интерфейсам, где пользователи ценят интуитивность и эффективность. 2.1.2 Построение графа (диаграммы) состояний интерфейса При построении диаграммы состояний интерфейса следует учитывать специфику самого условия задачи. Оно состоит из всего одного базового варианта. Наличие этого условия добавляет необходимость реализации способа автоматического ввода начальных условий задачи. Также в целях создания более гибкого приложения должна быть реализована функция редактирования и очищения таблицы условий задачи. Все вышеперечисленные состояния могут быть отражены диаграммой (рисунок 7). 19 Рисунок 7 – Диаграмма состояний интерфейса 2.1.3 Разработка форм ввода-вывода информации При использовании данного приложения, большое значения имеет вводвывод информации, так как работа симплекс-метода без входных табличных значений, на основе которых выдается решение, невозможна. Тот же исход будет и при вводе некорректных данных, что тоже должно быть учтено. Приложение имеет несколько состояний: − Заполнение таблицы условий; − Заполнение таблицы целевой функции; − Изменение количества условий; − Изменение количества переменных; − Изменение искомого экстремума; − Вывод решения; − Вывод отчета; − Вывод моделирования прибыли; − Сообщение об ошибке. Основное окно приложения также является и формой ввода и вывода информации. На основании данного факта и перечисленных в данном и предыдущем пунктах требований была составлена диаграмма состояний приложения (рисунок 8). 20 Рисунок 8 – Диаграмма состояний формы интерфейса 2.2 Разработка алгоритма основной программы и структурной схемы программного продукта 2.2.1 Описание структуры приложения и схема связности модулей При проектировании структуры будущего программного продукта стоит описать основные функциональные элементы продукта, а также характер их взаимодействия. Основные функциональные элементы приложения касаются ввода информации и ее очистки, получения выходных данных, а также выведения отчета, составленного на основе этих данных. Все вышеперечисленные элементы можно отобразить на структурной схеме, которая в полной мере отражает их взаимодействие (рисунок 9). 21 Рисунок 9 – Структурная схема программного продукта 2.2.2 Схема движения информационных потоков Схема движения информационных поток, содержит несколько уровней детализации. Главный уровень содержит абстракцию, которая заключает в себе весь алгоритм программы, а также входной и выходной потоки. Вследствие того, что абстракция содержит в себе весь программный код, табличные данные – будут являться входными данными, а отчет – выходными (рисунок 10). Рисунок 10 – Диаграмма потоков данных (уровень А0) Далее главный уровень (А0) следует детализировать согласно состояниям программы и структурной схеме программного продукта. Диаграмма, приведенная на рисунке 9, отражает информационные потоки на уровне работы алгоритмов программы. Алгоритм получает входные данные в табличном формате, удобном для пользователя, затем заполняет структуры данных, заранее определенных программой, после чего управление передается алгоритму симплекс-метода. Результаты работы алгоритма передаются далее в форматируемый отчет (рисунок 11). 22 Рисунок 11 – Диаграмма потоков данных, декомпозиция уровня A0 2.3 Разработка основных алгоритмов программного продукта Основной алгоритм использования программы можно представить в виде блок-схемы. Он основан на относительной независимости функционала пользовательских возможностей друг от друга (рисунок 12). Рисунок 12 – Блок-схема основного алгоритма программы Основной алгоритм программы содержит в себе несколько подпрограмм, каждая из которых выполняет одно определенное действие. 1. Заполнить. 23 Используемые переменные: глобальная таблица типа DataGridView (constraintsGridView), глобальная таблица типа DataGridView (functionGridView). Схема алгоритма (рисунок 13). Рисунок 13 – Алгоритм подпрограммы автоматического заполнения условий задачи Функциональное назначение алгоритма: алгоритм предназначен для автоматического заполнения условий задачи из заданного варианта. Входные данные: отсутствуют. Выходные данные: заполненные таблицы условий задачи и целевой функции задачи в соответствии с заданным вариантом курсовой работы. 2. Алгоритм очистки данных таблиц. Используемые переменные: глобальная таблица типа DataGridView (constraintsGridView), глобальная (functionGridView). 24 таблица типа DataGridView Схема алгоритма (рисунок 14). Рисунок 14 – Схема алгоритма очищения данных таблиц Функциональное назначение алгоритма: алгоритм предназначен для полной очистки данных двух таблиц: constraintsGridView и functionGridView. После очищения предыдущие данные не будут доступны в течении работы программы. Входные данные: отсутствуют. Выходные данные: пустые таблицы, ранее содержавшие некоторые данные. 3. Алгоритм получения решения. Используемые переменные: глобальная таблица типа DataGridView (constraintsGridView), глобальная таблица типа DataGridView (functionGridView), массив вещественного типа данных для хранения 25 значений симплекс-таблицы, две целочисленные переменные для хранения индексов разрешающего элемента. Схема алгоритма (рисунок 15). Рисунок 15 – Алгоритм подпрограммы получения решения Функциональное назначение алгоритма: алгоритм предназначен для решения задачи симплекс-методом, полученные значения также используются для расчета других значений. Входные данные: обработанные данные из таблиц constraintsGridView и functionGridView. Выходные данные: набор промежуточных симплекс-таблиц, двумерный массив со значениями готового решения. 2.4 Разработанные классы программы Классы рабочей части программы создавались на основе изученной предметной области решения задач оптимизации симплекс-методом. В 26 результате анализа процесс решения была составлена диаграмма классов (рисунок 16), подлежащих реализации для успешного применения симплексметода в поставленных в работе задачах. Конкретно были выделены сущности: − «Simplex» - класс, в котором производятся расчеты в соответствии с методом. − «Function» - класс, содержащий целевую функцию математической модели. − «Constraint» - класс, содержащий условия математической модели. − «SimplexIndexResult» - класс, отвечающий за переходы между шагами симплекс-алгоритма. − «SimplexSnap» - класс, сохраняющий результаты работы симплексметода в удобном для работы программы формате. Рисунок 16 – Диаграмма рабочих классов программы 27 Также были разработаны классы интерфейса программы и их внутренние компоненты. Так, были созданы классы «MainForm» и «ReportForm», отвечающие за основное окно и за окно отчета соответственно. Отчет отделён от остальной части решения задачи из-за его объёма. Его внедрение в основное окно вылилось бы в большую площадь зачастую неиспользуемого места на экране и неудобство использования. Для этих классов также была составлена диаграмма (рисунок 17). Рисунок 17 – Диаграмма классов форм программы Также была составлена диаграмма последовательностей, поясняющая переход между окнами при выведении отчета ( рисунок 18). 28 Рисунок 18 – Диаграмма последовательностей перехода между окнами Итогом создания классов программы стала диаграмма компоновки программного продукта (рисунок 19), показывающая связи между файлами разработанного продукта. 29 Рисунок 19 – Диаграмма компоновки программного продукта Программный продукт создавался по восходящей методологии, так как симплекс-метод – это математический алгоритм, состоящий из повторяющихся действий. Поэтому сначала были реализованы базовые функции, на основании которых был собрана программная реализация алгоритма[4]. Также можно отметить и несколько второстепенных преимуществ выбранной методологии: − Постепенное улучшение функциональности: Восходящий подход позволяет начать с минимальной жизнеспособной версии (MVP) приложения, которая решает базовые задачи оптимизации. Затем функциональность приложения может постепенно расширяться и улучшаться на основе обратной связи от пользователей и изменений в требованиях. − Быстрое время выхода на рынок: Разработка MVP позволяет быстрее запустить продукт на рынок и начать собирать обратную связь от пользователей. Это особенно важно в области оптимизации, где компании постоянно сталкиваются с вызовами и нуждаются в эффективных решениях как можно скорее. 30 − Гибкость и адаптивность: Восходящий подход делает приложение более гибким и адаптивным к изменяющимся требованиям и условиям рынка. Вместо того чтобы пытаться предусмотреть все возможные сценарии заранее, команды разработчиков могут быстро реагировать на изменения, внедряя новые функции и оптимизируя существующие. − Минимизация рисков: Разработка поэтапно позволяет уменьшить риски, связанные с разработкой большого и сложного приложения. Путем начала с простой версии MVP можно быстро выявить проблемы и исправить их, минимизируя риски больших инвестиций в разработку. − Легкое масштабирование и оптимизация: Постепенное улучшение приложения позволяет более эффективно масштабировать и оптимизировать его. После того как базовая функциональность установлена и протестирована, можно сконцентрироваться на улучшении производительности, интерфейса пользователя и других аспектов приложения. Подводя итоги, в данном разделе было проведено проектирование структуры и компонентов программного продукта. Был разработан интерфейс – его структурная схема, диаграмма его состояний и формы ввода-вывода информации. Был разработан алгоритм основной программы и структурная схема продукта. Произведено описание структуры приложения и составлена схема связности модулей. Разработаны основные алгоритмы программного продукта. 31 3 Тестирование программы 3.1 Разработка плана тестирования Целью данного плана тестирования является обеспечение высокого качества разрабатываемого продукта путем проверки его функциональности, производительности, безопасности и удобства использования. Основные задачи включают: − Проверку корректности работы отдельных компонентов системы (модульные тесты). − Проверку работоспособности и пользовательского опыта взаимодействия с интерфейсом (тестирование интерфейса). Модульные тесты будут проводиться для каждого отдельного модуля системы с целью проверки их функциональности в изоляции от других компонентов. Этот вид тестирования выбран в первую очередь потому, что при разработке использовалась восходящая методология разработки. План модульного тестирования включает в себя: − Создание тестовых случаев для каждого модуля на основе его спецификации и требований. − Определение критериев приемки для каждого теста. − Разработку средств автоматизации для проведения модульных тестов. − Оценку покрытия кода модульными тестами. − Регулярное обновление и поддержание тестовых случаев в соответствии с изменениями в коде. Тестирование интерфейса будет проводиться для проверки удобства использования, соответствия дизайну и функциональности пользовательского интерфейса. План тестирования интерфейса включает в себя: − Создание тестовых сценариев, охватывающих различные аспекты взаимодействия пользователя с интерфейсом. − Проверку адаптивности интерфейса к различным разрешениям экранов. − Оценку времени отклика и общей производительности интерфейса. 32 3.2 Разработка алгоритма процедуры тестирования Учитывая этапы, описанные выше, можно составить следующий алгоритм проверки программы: 1. Определение функциональных требований (пункт 1). 2. Разработка модульных тестов (Приложение Г). 3. Проведение модульного тестирования (рисунок 20). 4. Тестирование интерфейса (приложение А). 5. Документирование тестирования (таблицы приложения А, рисунок 20). Модульные тесты будут спроектированы и реализованы на основе фреймворка xUnit. Он предоставляет программистам инструменты для написания и запуска тестов для различных типов приложений на разных языках программирования. Каждый фреймворк XUnit предоставляет базовые функциональности для организации тестов, а также генерации отчетов о выполненных тестах, включая информацию о количестве успешных и неудачных тестов, времени выполнения и другие статистические данные[5]. При тестировании интерфейса используется ручное тестирование за неимением возможности автоматизации данного процесса. Процесс, условия и алгоритм тестирований записан в единообразных планах в формате таблиц. 3.3 Оценка результатов тестирования Ниже приведены результаты выполнения разработанных для приложения модульных тестов (рисунок 20). 33 Рисунок 20 – Результат выполнения модульных тестов приложения Планы и результаты тестирования интерфейса представлены в приложении А. В данном разделе было проведено разноплановое тестирование программы. Были разработаны планы тестирования, разработан алгоритм процедуры тестирования и проведена оценка результатов. Основываясь на результатах тестирования приложения, можно сделать вывод о том, что оно полностью готово к эксплуатации, а ошибки, которые возможно обнаружатся в будущем будут несущественны и некритичны. 34 4 Сопровождение 4.1 Руководство пользователя Ниже представлено пошаговое описание типичного способа использования приложения с пояснениями по каждой функции приложения. 1. Вход в приложение. При запуске программы открывается главное окно приложения (рисунок 21). Рисунок 21 – Главное окно приложения 2. Заполнение таблицы. Для удобства использования в программе предоставлена возможность автоматического заполнение таблиц (кнопка «Заполнить таблицы»). 3. Получение решения. При нажатии на кнопку «Решить» решение отображается в нижнем текстовом разделе окна. 35 4. Получение отчета. При нажатии на кнопку «Отчет» появляется окно отчета, содержащее результаты решения задачи оптимизации (рисунок 22). Рисунок 22 – Окно с результатами 5. Моделирование прибыли. При нажатии на кнопку «Смоделировать» появляется в текстовом окне, где появляется решение задачи, появляется её же решение, но с условием того, что на каждой следующей итерации количество человеческого ресурса, доступного для каждого цеха, увеличивается на единицу. В случае ручного заполнения условий задачи нужно воспользоваться переключателями рядом с надписями «Условия» и «Переменные», выбрав необходимое количество переменных. После чего ввести в получившуюся пустую таблицу условия из математической модели задачи. Знак неравенства выбирается из 3 вариантов: «<= (меньше или равно)», «>=» (больше или равно), «=» (равно). После чего необходимо ввести коэффициенты целевой функции и выбрать необходимый экстремум. 4.2 Обслуживание модели, алгоритма, программы и их эксплуатация При использовании встроенной модели, заполняющейся автоматически, не стоит менять местами уже имеющиеся условия и добавлять новые только 36 снизу. Иначе могут возникнуть ошибки в дополнительной информации, выдаваемой отчетом. Алгоритм не нуждается в доработках в функциональном плане, однако может быть подвергнут процессу оптимизации его внутренней реализации. Уже имеющиеся модульные тесты будут хорошим подспорьем в процессе рефакторинга алгоритма. Обслуживание программы и её доработка должны проводиться только с целью реализации новых функций или исправления ошибок старой реализации, но не с целью удаления оригинальных функций, так как они и так являются минимально необходимыми. В результате данного раздела было составлено руководство пользователя, описывающее и разъясняющее работу с разработанным продуктом. Были определены и описаны процессы обслуживания модели, алгоритма и программы, а также их эксплуатация. 37 Заключение В результате выполнения курсовой работы было спроектировано и реализовано приложение по решению задачи оптимизации используя симплексметод. В первом разделе был проведён анализ технических требований и уточнение спецификаций продукта. Сначала был проведён анализ задания и выбор технологий, языка программирования и среды разработки. Их результатом стал выбор объектно-ориентированной парадигмы программирования, выбор языка программирования C# и использование среды разработки Visual Studio 2022. Затем был проанализирован процесс обработки информации, на основе которого были построены функциональные диаграммы программы. Далее был проведён анализ хранимой информации и выбор структур данных для её представления – двумерные массивы. Во втором разделе было проведено проектирование структуры и компонентов программного продукта. Был разработан интерфейс – его структурная схема, диаграмма его состояний и формы ввода-вывода информации. Был разработан алгоритм основной программы и структурная схема продукта. Произведено описание структуры приложения и составлена схема связности модулей. Разработаны основные алгоритмы программного продукта. В третьем разделе было проведено разноплановое тестирование программы. Были разработаны планы тестирования, разработан алгоритм процедуры тестирования и проведена оценка результатов. Основываясь на результатах тестирования приложения, можно сделать вывод о том, что оно полностью готово к эксплуатации, а ошибки, которые возможно обнаружатся в будущем будут несущественны и некритичны. В четвертом разделе было составлено руководство пользователя, описывающее и разъясняющее работу с разработанным продуктом. Были определены и описаны процессы обслуживания модели, алгоритма и программы, а также их эксплуатация. 38 После поэтапного решения задач был получен готовый продукт, соответствующий поставленным требованиям, однако он готов к дальнейшим изменениям и усовершенствованиям при необходимости, например в плане дизайна интерфейса или добавления возможности решения других типов задач оптимизации. Продукт был протестирован в соответствии с методологиями, изученными в процессе обучения. Также важно отметить полученные посредством изучения предметной области в процессе работы знания и опыт разработки. 39 Список используемой литературы 1. Аттетков А. В. Методы оптимизации: Учебное пособие / А.В. Аттетков, В.С. Зарубин, А.Н. Канатников. - М.: ИЦ РИОР: НИЦ Инфра-М, 2019. - 270 с.: ил.; - (Высшее образование: Бакалавриат). - ISBN 978-5-16-103309-8. - Текст : электронный. - URL: https://znanium.com/bookread2.php?book=1002733 2. Балдин К. В. Математическое программирование / Балдин К.В., Брызгалов Н.А., Рукосуев А.В., - 2-е изд. - Москва :Дашков и К, 2018. - 218 с.: ISBN 978-5-394-01457-4. - Текст : электронный. - URL: https://znanium.com/bookread2.php?book=415097 3. Карманов В. Г. Математическое программирование [Электронный ресурс] : Учебное пособие / В. Г. Карманов. - 6-е изд., испр. - Москва : ФИЗМАТЛИТ, 2018. - 264 с. - ISBN 978-5-9221-0983-3. - Текст : электронный. URL: https://znanium.com/bookread2.php?book=544747 4. Каштанов В. А. Исследование операций (линейное программирование и стохастические модели) : учебник / В.А. Каштанов, О.Б. Зайцева. — Москва : КУРС, 2017. - 256 с. - ISBN 978-5-906818-78-2. - Текст : электронный. - URL: https://znanium.com/bookread2.php?book=1017099 5. Морозова, Ю. В. Тестирование программного обеспечения : учебное пособие / Ю. В. Морозова. - Томск : Эль-Контент, 2019. - 120 с. - ISBN 978-54332-0279-5. - Текст : электронный. - URL: https://znanium.com/catalog/product/1845910 6. Хорев, П. Б. Объектно-ориентированное программирование с примерами на С# : учебное пособие / П.Б. Хорев. — Москва : ФОРУМ : ИНФРАМ, 2023. — 200 с. — (Среднее профессиональное образование). - ISBN 978-500091-713-8. - Текст : электронный. https://znanium.com/catalog/product/1895650 40 - URL: Приложение А – Планы тестирования интерфейса Таблица 1 – План тестирования функции решения задачи Наименование Описание Наименование проекта Приложение решения задач оптимизации Номер версии 1.0 Имя тестера Караваев Вадим Михайлович Даты тестирования 21.08.2024 Test Case # TC_RS_1 Приоритет тестирования Высокий Название тестирования/Имя Проверка корректности выведения решения Резюме испытания Корректное решение, соответствующее действительному результату Шаги тестирования 1. Запуск приложения. 2. Нажатие на кнопку «Заполнить таблицы». 3. Нажатие на кнопку «Решить». Данные тестирования Первоначальные данные полученного на выполнение варианта курсовой работы. Ожидаемый результат Вывод на экран в нижнем текстовом разделе корректного решения задачи. Фактический результат Корректное решение выведено. Предпосылки Отсутствуют. Постусловия Отсутствуют. Статус Pass Комментарии Отсутствуют. 41 Таблица 2 – План тестирования функции создания отчета по решению задачи Наименование Наименование проекта Номер версии Имя тестера Даты тестирования Test Case # Приоритет тестирования Название тестирования/Имя Резюме испытания Шаги тестирования Данные тестирования Ожидаемый результат Фактический результат Предпосылки Постусловия Статус Комментарии Описание Приложение решения задач оптимизации 1.0 Караваев Вадим Михайлович 21.08.2024 TC_RT_1 Высокий Проверка создания отчета по решению поставленной задачи Готовый правильный отчет. 1. Запуск приложения. 2. Нажатие на кнопку «Заполнить таблицы». 3. Нажатие на кнопку «Решить». 4. Нажатие на кнопку «Отчет». Первоначальные данные полученного на выполнение варианта курсовой работы. Вывод на экран нового окна с отчетом по нахождении решения задачи, содержащего ход решения задачи в виде промежуточных таблиц и их текстового описания Окно отчета выведено, промежуточные таблицы когерентны с решением вручную, качество текстового описания удовлетворительное. Отсутствуют. Отсутствуют. Pass Отсутствуют Таблица 3 – План тестирования функции моделирования повышения прибыли Наименование Описание Наименование проекта Приложение решения задач оптимизации Номер версии 1.0 42 Наименование Имя тестера Даты тестирования Test Case # Приоритет тестирования Название тестирования/Имя Резюме испытания Шаги тестирования Данные тестирования Ожидаемый результат Фактический результат Предпосылки Постусловия Статус Комментарии Описание Караваев Вадим Михайлович 21.08.2024 TC_MD_1 Высокий Проверка функции моделирования роста прибыли Адекватная работа функции моделирования прибыли 1. Запуск приложения. 2. Нажатие на кнопку «Заполнить таблицы». 3. Нажатие на кнопку «Смоделировать». Первоначальные данные полученного на выполнение варианта курсовой работы. Вывод на экран в нижнем текстовом разделе корректного решения задачи для 10 её вариантов, отличающихся постепенно увеличивающимся количеством доступной для цехов рабочей силы.. Корректное решение выведено, функция моделирования работает удовлетворительно. Отсутствуют Отсутствуют Pass Отсутствуют 43 Приложение Б – Листинг приложения Б.1 Модуль Constraint using System; namespace SimplexMethod { public class Constraint { public double[] variables; public double b; public string sign; public Constraint(double[] variables, double b, string sign) { if (sign == "=" || sign == "<=" || sign == ">=") { this.variables = variables; this.b = b; this.sign = sign; } else { throw new ArgumentException("Wrong sign"); } } } } Б.2 Модуль Function namespace SimplexMethod { public class Function { public double[] variables; public double c; public bool isExtrMax; public Function(double[] variables, double c, bool isExtrMax) { this.variables = variables; this.c = c; this.isExtrMax = isExtrMax; } } 44 } Б.3 Модуль SimplexIndexResult using System; namespace SimplexMethod { public class SimplexIndexResult { public Tuple<int, int> index; public SimplexResult result; public SimplexIndexResult(Tuple<int, SimplexResult result) { this.index = index; this.result = result; } } } int> index, Б.4 Модуль SimplexSnap using System.Linq; namespace SimplexMethod { public class SimplexSnap { public double[] b; public double[][] matrix; public double[] M; public double[] F; public int[] C; public double fValue; public double[] fVars; public bool isMDone; public bool[] m; public SimplexSnap(double[] b, double[][] matrix, double[] M, double[] F, int[] C, double[] fVars, bool isMDone, bool[] m) { this.b = Copy(b); this.matrix = Copy(matrix); this.M = Copy(M); this.F = Copy(F); this.C = Copy(C); this.isMDone = isMDone; this.m = Copy(m); this.fVars = Copy(fVars); fValue = 0; 45 for (int i = 0; i < C.Length; i++) { fValue += fVars[C[i]] * b[i]; } } T[] Copy<T>(T[] array) { T[] newArr = new T[array.Length]; for (int i = 0; i < array.Length; i++) { newArr[i] = array[i]; } return newArr; } T[][] Copy<T>(T[][] matrix) { T[][] newMatr = new T[matrix.Length][]; for (int i = 0; i < matrix.Length; i++) { newMatr[i] = new T[matrix.First().Length]; for (int j = 0; j < matrix.First().Length; j++) { newMatr[i][j] = matrix[i][j]; } } return newMatr; } } } Б.5 Модуль Simplex using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace SimplexMethod { public enum SimplexResult { Unbounded, Found, NotYetFound } public class Simplex { public List<Tuple<int,int>> List<Tuple<int,int>>(); Function function; double[] functionVariables; double[][] matrix; double[] b; 46 dots = new bool[] m; double[] M; double[] F; int[] C; bool isMDone = false; public Simplex(Function function, Constraint[] constraints) { if (function.isExtrMax) { this.function = function; } else { this.function = Canonize(function); } getMatrix(constraints); getFunctionArray(); getMandF(); for(int i = 0; i < F.Length; i++) { F[i] = -functionVariables[i]; } } public Tuple<List<SimplexSnap>, SimplexResult> GetResult() { List<SimplexSnap> snaps = new List<SimplexSnap>(); snaps.Add(new SimplexSnap(b, matrix, M, F, C, functionVariables, isMDone,m)); SimplexIndexResult result = nextStep(); int i = 0; while (result.result == SimplexResult.NotYetFound && i < 100) { calculateSimplexTableau(result.index); snaps.Add(new SimplexSnap(b, matrix, M, F, C, functionVariables, isMDone, m)); result = nextStep(); i++; } return new SimplexResult>(snaps,result.result); } Tuple<List<SimplexSnap>, void calculateSimplexTableau(Tuple<int,int> Xij) { double[][] newMatrix = double[matrix.Length][]; C[Xij.Item2] = Xij.Item1; 47 new double[] newJRow = new double[matrix.Length]; for (int i = 0; i < matrix.Length; i++) { newJRow[i] = matrix[i][Xij.Item2] matrix[Xij.Item1][Xij.Item2]; } / double[] newB = new double[b.Length]; for (int i = 0; i < b.Length; i++) { if (i == Xij.Item2) { newB[i] = b[i] matrix[Xij.Item1][Xij.Item2]; } else { newB[i] = b[i] b[Xij.Item2] matrix[Xij.Item1][Xij.Item2] * matrix[Xij.Item1][i]; } } / / b = newB; for (int i = 0; i < matrix.Length; i++) { newMatrix[i] = new double[C.Length]; for (int j = 0; j < C.Length; j++) { if(j == Xij.Item2) { newMatrix[i][j] = newJRow[i]; } else { newMatrix[i][j] = matrix[i][j] newJRow[i] * matrix[Xij.Item1][j]; } } } matrix = newMatrix; getMandF(); } void getMandF() { M = new double[matrix.Length]; F = new double[matrix.Length]; for (int i = 0; i < matrix.Length; i++) { double sumF = 0; double sumM = 0; 48 - for (int j = 0; j < matrix.First().Length; j++) { if (m[C[j]]) { sumM -= matrix[i][j]; } else { sumF += functionVariables[C[j]] * matrix[i][j]; } } M[i] = m[i] ? sumM +1 : sumM; F[i] = sumF - functionVariables[i]; } } SimplexIndexResult nextStep() { int columnM getIndexOfNegativeElementWithMaxAbsoluteValue(M); if (isMDone || columnM == -1) { //M doesn't have negative values isMDone = true; int columnF getIndexOfNegativeElementWithMaxAbsoluteValue(F); = = if (columnF != -1) //Has at least 1 negative value { int row = getIndexOfMinimalRatio(matrix[columnF], b); dots.Add(new Tuple<int, int>(row, columnF)); if (row != -1) { return new SimplexIndexResult(new Tuple<int, int>(columnF, row), SimplexResult.NotYetFound); } else { return new SimplexIndexResult(null, SimplexResult.Unbounded); } } else { return new SimplexIndexResult(null, SimplexResult.Found); } } else 49 { int row = getIndexOfMinimalRatio(matrix[columnM], b); if (row != -1) { return new SimplexIndexResult(new Tuple<int, int>(columnM, row), SimplexResult.NotYetFound); } else { return new SimplexIndexResult(null, SimplexResult.Unbounded); } } } int getIndexOfNegativeElementWithMaxAbsoluteValue(double[] array) { int index = -1; for(int i = 0; i < array.Length; i++) { if (array[i] < 0) { if (!isMDone || isMDone && !m[i]) { if (index == -1) { index = i; } else if (Math.Abs(array[i]) Math.Abs(array[index])) { index = i; } } > } } return index; } int getIndexOfMinimalRatio(double[] column, double[] b) { int index = -1; for (int i = 0; i < column.Length; i++) { if(column[i] > 0 && b[i] > 0) { if (index == -1) { index = i; } 50 else if(b[i] / column[i] < b[index] / column[index]) { index = i; } } } return index; } public void getFunctionArray() { double[] funcVars = new double[matrix.Length]; for(int i = 0; i < matrix.Length; i++) { funcVars[i] = i < function.variables.Length ? function.variables[i] : 0; } this.functionVariables = funcVars; } public Function Canonize(Function function) { double[] newFuncVars = double[function.variables.Length]; new for (int i = 0; i < function.variables.Length; i++) { newFuncVars[i] = -function.variables[i]; } return new Function(newFuncVars, -function.c, true); } double[][] appendColumn(double[][] matrix, double[] column) { double[][] newMatrix = new double[matrix.Length + 1][]; for (int i = 0; i < matrix.Length; i++) { newMatrix[i] = matrix[i]; } newMatrix[matrix.Length] = column; return newMatrix; } T[] append<T>(T[] array, T element) { T[] newArray = new T[array.Length + 1]; for(int i = 0; i<array.Length; i++) { newArray[i] = array[i]; } newArray[array.Length] = element; return newArray; 51 } double[] getColumn(double value, int place, int length) { double[] newColumn = new double[length]; for (int k = 0; k < length; k++) { newColumn[k] = k == place ? value : 0; } return newColumn; } public void getMatrix(Constraint[] constraints) { for (int i = 0; i < constraints.Length; i++) { if (constraints[i].b < 0) { double[] cVars = double[constraints[i].variables.Length]; for (int constraints[i].variables.Length; j++) { cVars[j] constraints[i].variables[j]; } j = 0; = j new < - string sign = constraints[i].sign; if (sign == ">=") { sign = "<="; } else if (sign == "<=") { sign = ">="; } Constraint cNew = new Constraint(cVars, constraints[i].b, sign); constraints[i] = cNew; } } double[][] matrix double[constraints.First().variables.Length][]; = new for(int i = 0; i < constraints.First().variables.Length; i++) { matrix[i] = new double[constraints.Length]; for(int j = 0; j < constraints.Length; j++) 52 { matrix[i][j] constraints[j].variables[i]; } } = double[][] appendixMatrix = new double[0][]; double[] Bs = new double[constraints.Length]; for (int i = 0; i < constraints.Length; i++) { Constraint current = constraints[i]; Bs[i] = current.b; if (current.sign == ">=") { appendixMatrix = appendColumn(appendixMatrix, getColumn(-1, i, constraints.Length)); } else if (current.sign == "<=") { appendixMatrix = appendColumn(appendixMatrix, getColumn(1, i, constraints.Length)); } } double[][] newMatrix double[constraints.First().variables.Length appendixMatrix.Length][]; for(int i = 0; constraints.First().variables.Length; i++) { newMatrix[i] = matrix[i]; } = new + i < for (int i = constraints.First().variables.Length; i < constraints.First().variables.Length + appendixMatrix.Length; i++) { newMatrix[i] = appendixMatrix[i constraints.First().variables.Length]; } bool[] hasBasicVar = new bool[constraints.Length]; for(int i = 0; i < constraints.Length; i++) { hasBasicVar[i] = false; } C = new int[constraints.Length]; int ci = 0; for(int i = 0; i < newMatrix.Length; i++) { 53 bool hasOnlyNulls = true; bool hasOne = false; Tuple<int,int> onePosition = new Tuple<int, int>(0,0); for(int j = 0; j < constraints.Length; j++) { if (newMatrix[i][j] == 1) { if (hasOne) { hasOnlyNulls = false; break; } else { hasOne = true; onePosition = new Tuple<int, int>(i,j); } } else if (newMatrix[i][j] != 0) { hasOnlyNulls = false; break; } } if (hasOnlyNulls && hasOne) { hasBasicVar[onePosition.Item2] = true; C[ci] = onePosition.Item1; ci++; } } m = new bool[newMatrix.Length]; for(int i = 0; i < newMatrix.Length; i++) { m[i] = false; } for(int i = 0; i < constraints.Length; i++) { if(!hasBasicVar[i]) { double[] double[constraints.Length]; basicColumn = new for(int j = 0; j < constraints.Length; j++) { basicColumn[j] = j == i ? 1 : 0; } newMatrix appendColumn(newMatrix,basicColumn); 54 = m = append(m, true); C[ci] = newMatrix.Length - 1; ci++; } } this.b = Bs; this.matrix = newMatrix; } } } Б.6 Модуль MainForm using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; namespace SimplexMethod { public partial class MainForm : Form { public MainForm() { InitializeComponent(); } int constraintsCount = 0; int variablesCount = 0; Tuple<List<SimplexSnap>, SimplexResult> result; public List<Tuple<int, int>> dots = new List<Tuple<int, int>>(); void fillConstraintsGrid() { constraintsGridView.Rows.Clear(); constraintsGridView.ColumnCount = variablesCount + 2; constraintsGridView.RowHeadersVisible = false; for (int i = 0; i < variablesCount + 2; i++) { constraintsGridView.Columns[i].Width = 50; constraintsGridView.Columns[i].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; if (i < variablesCount) { constraintsGridView.Columns[i].Name = "x" + (i + 1).ToString(); } else if (i == variablesCount) { constraintsGridView.Columns[i].Name = "Знак"; 55 } } for (int i = 0; i < constraintsCount; i++) { string[] row = new string[constraintsCount + 2]; constraintsGridView.Rows.Add(row); constraintsGridView.Rows[i].Height = 20; } } void fillFunctionGrid() { functionGridView.Rows.Clear(); functionGridView.ColumnCount = variablesCount + 1; functionGridView.RowHeadersVisible = false; for (int i = 0; i < variablesCount + 1; i++) { functionGridView.Columns[i].Width = 50; functionGridView.Columns[i].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; if (i < variablesCount) { functionGridView.Columns[i].Name = "x" + (i + 1).ToString(); } } functionGridView.Rows.Add(); } void Proceed() { string resString = ""; Constraint[] constraints = new Constraint[constraintsCount]; for (int i = 0; i < constraintsCount; i++) { double[] variables = new double[variablesCount]; double b = Convert.ToDouble(constraintsGridView.Rows[i].Cells[variablesCount + 1].Value); string sign = Convert.ToString(constraintsGridView.Rows[i].Cells[variablesCount] .Value); for (int j = 0; j < variablesCount; j++) { variables[j] = Convert.ToDouble(constraintsGridView.Rows[i].Cells[j].Value); } constraints[i] = new Constraint(variables, b, sign); } double[] functionVariables = new double[variablesCount]; 56 for (int i = 0; i < variablesCount; i++) { functionVariables[i] = Convert.ToDouble(functionGridView.Rows[0].Cells[i].Value); } double c = Convert.ToDouble(functionGridView.Rows[0].Cells[variablesCount].Va lue); bool isExtrMax = extrComboBox.SelectedIndex == 0; Function function Function(functionVariables, c, isExtrMax); Simplex simplex = new = new Simplex(function, constraints); result = simplex.GetResult(); dots = simplex.dots; switch (result.Item2) { case SimplexResult.Found: string extrStr = isExtrMax ? "max" : "min"; resString += "Найдено оптимальное решение: F" + extrStr + $" = {string.Format("{0:N2}", Math.Abs(result.Item1.Last().fValue))}\nПри:\n{Convert.ToDouble(co nstraintsGridView.Rows[2].Cells[variablesCount + 1].Value)} чел/ч на производстве в цехе А\n{Convert.ToDouble(constraintsGridView.Rows[3].Cells[variablesCo unt + 1].Value)} чел/ч на производстве в цехе Б\n"; string restext = ""; for (int i = 0; i < variablesCount; i++) { List<int> list = result.Item1.Last().C.ToList(); if (i < result.Item1.Last().b.Length) try { restext += $"x{i + 1} = {Math.Truncate(result.Item1.Last().b[list.IndexOf(i)])} "; } catch { restext += $"x{i + 1} = 0 "; } else restext += $"x{i + 1} = 0 "; } resString += restext; break; case SimplexResult.Unbounded: resString = "Оптимального решения нет"; break; case SimplexResult.NotYetFound: 57 resString = "Оптимальное решение не было найдено спустя 100 циклов"; break; } ansRichTextBox.Text = resString; ShowResultsGrid(result.Item1); } void ShowResultsGrid(List<SimplexSnap> snaps) { resultsGridView.Rows.Clear(); resultsGridView.ColumnCount = snaps.First().matrix.Length + 4; resultsGridView.RowHeadersVisible = false; resultsGridView.ColumnHeadersVisible = false; for (int i = 0; i < snaps.First().matrix.Length + 4; i++) { resultsGridView.Columns[i].Width = 50; resultsGridView.Columns[i].DefaultCellStyle.Alignment DataGridViewContentAlignment.MiddleCenter; } foreach (SimplexSnap snap in snaps) { string[] firstRow string[snaps.First().matrix.Length + 4]; = = new firstRow[0] = "i"; firstRow[1] = "Базис"; firstRow[2] = "C"; firstRow[3] = "Коэфф."; for (int i = 4; snaps.First().matrix.Length + 4; i++) { firstRow[i] = $"X{i - 3}"; } i < resultsGridView.Rows.Add(firstRow); for (int i = 0; i < snaps.First().C.Length; i++) { string[] row = string[snaps.First().matrix.Length + 4]; for (int j = 0; j snaps.First().matrix.Length + 4; j++) { if (j == 0) { row[j] = (i + 1).ToString(); } else if (j == 1) { 58 new < row[j] = $"X{snap.C[i] + 1}"; } else if (j == 2) { row[j] = snap.m[snap.C[i]] ? "-M" : $"{snap.fVars[snap.C[i]]}"; } else if (j == 3) { row[j] = } else { row[j] - Round(snap.b[i]).ToString(); = Round(snap.matrix[j 4][i]).ToString(); } } resultsGridView.Rows.Add(row); } string[] fRow = new string[snaps.First().matrix.Length + 4]; fRow[0] = "m+1"; fRow[1] = "F"; fRow[2] = "Δj"; fRow[3] = Round(snap.fValue).ToString(); for (int i = 4; i < snaps.First().matrix.Length + 4; i++) { fRow[i] = Round(snap.F[i - 4]).ToString(); } resultsGridView.Rows.Add(fRow); if (!snap.isMDone) { string[] mRow = string[snaps.First().matrix.Length + 4]; mRow[0] = "m+2"; mRow[1] = "M"; mRow[2] = "Δj"; mRow[3] = ""; for (int i = 4; i snaps.First().matrix.Length + 4; i++) { mRow[i] = Round(snap.M[i 4]).ToString(); } resultsGridView.Rows.Add(mRow); } string[] emptyRow = string[snaps.First().matrix.Length + 4]; resultsGridView.Rows.Add(emptyRow); } resultsGridView.Columns[0].Visible = false; resultsGridView.Columns[2].Visible = false; resultsGridView.Rows[6].Visible = false; 59 new < - new } double Round(double a) { return Math.Round(a, 2); } private void goBtn_Click(object sender, EventArgs e) { try { Proceed(); } catch (ArgumentException) { MessageBox.Show("Неправильный формат введённого знака"); } catch (FormatException) { MessageBox.Show("Неправильный формат введённых данных"); } catch { MessageBox.Show("Неизвестная ошибка"); } } private void clearBtn_Click(object sender, EventArgs e) { resultsGridView.Columns.Clear(); functionGridView.Columns.Clear(); constraintsGridView.Columns.Clear(); extrComboBox.SelectedIndex = -1; fillConstraintsGrid(); fillFunctionGrid(); } private void MainScreen_onLoad(object sender, EventArgs e) { try { constraintsCount = numericUpDownConstraints.Value; variablesCount (int)numericUpDownVariables.Value; fillConstraintsGrid(); fillFunctionGrid(); } catch (Exception err) { MessageBox.Show(err.Message); } "1"; (int) = //constraintsGridView.Rows[0].Cells[0].Value = constraintsGridView.Rows[0].Cells[1].Value = "0"; 60 constraintsGridView.Rows[0].Cells[2].Value = "<="; constraintsGridView.Rows[0].Cells[3].Value = "70"; //constraintsGridView.Rows[1].Cells[0].Value = "0"; constraintsGridView.Rows[1].Cells[1].Value = "1"; constraintsGridView.Rows[1].Cells[2].Value = "<="; constraintsGridView.Rows[1].Cells[3].Value = "50"; //constraintsGridView.Rows[2].Cells[0].Value = "1,8"; constraintsGridView.Rows[2].Cells[1].Value = "1,2"; constraintsGridView.Rows[2].Cells[2].Value = "<="; constraintsGridView.Rows[2].Cells[3].Value = "120"; //constraintsGridView.Rows[3].Cells[0].Value = "1"; constraintsGridView.Rows[3].Cells[1].Value = "1"; constraintsGridView.Rows[3].Cells[2].Value = "<="; constraintsGridView.Rows[3].Cells[3].Value = "90"; //functionGridView.Rows[0].Cells[0].Value "150"; functionGridView.Rows[0].Cells[1].Value = "200"; = extrComboBox.SelectedIndex = -1; } private void numericUpDownConstraints_ValueChanged(object sender, EventArgs e) { constraintsCount = (int) numericUpDownConstraints.Value; constraintsGridView.RowCount = (int) numericUpDownConstraints.Value; } private void numericUpDownVariables_ValueChanged(object sender, EventArgs e) { bool increased = (int)numericUpDownVariables.Value > variablesCount; variablesCount = (int) numericUpDownVariables.Value; functionGridView.ColumnCount = variablesCount + 1; // func grid stuff functionGridView.RowHeadersVisible = false; for (int i = 0; i < variablesCount + 1; i++) { functionGridView.Columns[i].Width = 50; functionGridView.Columns[i].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; if (i < variablesCount) { functionGridView.Columns[i].Name = "x" + (i + 1).ToString(); } else { functionGridView.Columns[i].Name = ""; } } 61 if (increased) { DataGridViewColumn temp (DataGridViewColumn)constraintsGridView.Columns[0].Clone(); temp.HeaderText = $"x{variablesCount}"; constraintsGridView.Columns.Insert(variablesCount - 1, temp); for (int i = 0; i constraintsGridView.RowCount; i++) { = < constraintsGridView.Rows[i].Cells[variablesCount - 1].Value = ""; } } else { constraintsGridView.Columns.RemoveAt(variablesCount); } } private void modelButton_Click(object sender, EventArgs e) { object prevValA = constraintsGridView.Rows[2].Cells[variablesCount + 1].Value; object prevValB = constraintsGridView.Rows[3].Cells[variablesCount + 1].Value; string resString = ""; for (int f = 0; f < 11; f++) { Constraint[] constraints = new Constraint[constraintsCount]; for (int i = 0; i < constraintsCount; i++) { double[] variables = new double[variablesCount]; double b = Convert.ToDouble(constraintsGridView.Rows[i].Cells[variablesCount + 1].Value); string sign = Convert.ToString(constraintsGridView.Rows[i].Cells[variablesCount] .Value); for (int j = 0; j < variablesCount; j++) { variables[j] = Convert.ToDouble(constraintsGridView.Rows[i].Cells[j].Value); } constraints[i] = new Constraint(variables, b, sign); } double[] functionVariables = new double[variablesCount]; for (int i = 0; i < variablesCount; i++) { 62 functionVariables[i] = Convert.ToDouble(functionGridView.Rows[0].Cells[i].Value); } double c = Convert.ToDouble(functionGridView.Rows[0].Cells[variablesCount].Va lue); bool isExtrMax = extrComboBox.SelectedIndex == 0; Function function Function(functionVariables, c, isExtrMax); Simplex simplex = new = new Simplex(function, constraints); Tuple<List<SimplexSnap>, result = simplex.GetResult(); SimplexResult> switch (result.Item2) { case SimplexResult.Found: string extrStr = isExtrMax ? "max" : "min"; resString += "Найдено оптимальное решение: F" + extrStr + $" = {string.Format("{0:N2}", Math.Abs(result.Item1.Last().fValue))}\nПри:\n{Convert.ToDouble(co nstraintsGridView.Rows[2].Cells[variablesCount + 1].Value)} чел/ч на производстве в цехе А\n{Convert.ToDouble(constraintsGridView.Rows[3].Cells[variablesCo unt + 1].Value)} чел/ч на производстве в цехе Б\n"; string restext = ""; for (int i = 0; i < variablesCount; i++) { List<int> list = result.Item1.Last().C.ToList(); if (i < result.Item1.Last().b.Length) try { restext += $"x{i + 1} = {Math.Truncate(result.Item1.Last().b[list.IndexOf(i)])} "; } catch { restext += $"x{i + 1} = 0 "; } else restext += $"x{i + 1} = 0 "; } resString += restext; break; case SimplexResult.Unbounded: 63 resString = "Оптимального решения нет"; break; case SimplexResult.NotYetFound: resString = "Оптимальное решение не было найдено спустя 100 циклов"; break; } resString += "\n------------------------\n"; constraintsGridView.Rows[2].Cells[variablesCount + 1].Value = Convert.ToDouble(constraintsGridView.Rows[2].Cells[variablesCount + 1].Value) + 1.0d; constraintsGridView.Rows[3].Cells[variablesCount + 1].Value = Convert.ToDouble(constraintsGridView.Rows[3].Cells[variablesCount + 1].Value) + 1.0d; } constraintsGridView.Rows[2].Cells[variablesCount + 1].Value = prevValA; constraintsGridView.Rows[3].Cells[variablesCount + 1].Value = prevValB; ansRichTextBox.Text = resString; } private void fillButton_Click(object sender, EventArgs e) { numericUpDownConstraints.Value = 4; numericUpDownVariables.Value = 2; try { constraintsCount = (int)numericUpDownConstraints.Value; variablesCount = (int)numericUpDownVariables.Value; fillConstraintsGrid(); fillFunctionGrid(); } catch (Exception err) { MessageBox.Show(err.Message); } constraintsGridView.Rows[0].Cells[0].Value = "1"; constraintsGridView.Rows[0].Cells[1].Value = "0"; constraintsGridView.Rows[0].Cells[2].Value = "<="; constraintsGridView.Rows[0].Cells[3].Value = "70"; constraintsGridView.Rows[1].Cells[0].Value = "0"; constraintsGridView.Rows[1].Cells[1].Value = "1"; constraintsGridView.Rows[1].Cells[2].Value = "<="; constraintsGridView.Rows[1].Cells[3].Value = "50"; constraintsGridView.Rows[2].Cells[0].Value = "1,8"; constraintsGridView.Rows[2].Cells[1].Value = "1,2"; constraintsGridView.Rows[2].Cells[2].Value = "<="; constraintsGridView.Rows[2].Cells[3].Value = "120"; 64 constraintsGridView.Rows[3].Cells[0].Value = "1"; constraintsGridView.Rows[3].Cells[1].Value = "1"; constraintsGridView.Rows[3].Cells[2].Value = "<="; constraintsGridView.Rows[3].Cells[3].Value = "90"; functionGridView.Rows[0].Cells[0].Value = "150"; functionGridView.Rows[0].Cells[1].Value = "200"; extrComboBox.SelectedIndex = 0; } private void reportButton_Click(object sender, EventArgs e) { if (result == null) { MessageBox.Show("Решение ещё не получено"); return; } ReportForm reportForm = new ReportForm(result, dots, constraintsCount, variablesCount); reportForm.ShowDialog(); } } } Б.3 Модуль ReportForm using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace SimplexMethod { public partial class ReportForm : Form { Tuple<List<SimplexSnap>, SimplexResult> result; int constraintsCount = 0; int variablesCount = 0; public ReportForm(Tuple<List<SimplexSnap>, SimplexResult> result, List<Tuple<int, int>> dots, int constraintsCount, int variablesCount) { InitializeComponent(); this.result = result; this.constraintsCount = constraintsCount; this.variablesCount = variablesCount; ShowResultsGrid(result.Item1); 65 paintResults(dots); textReults(dots); } void paintResults(List<Tuple<int, int>> dots) { int period = 3 + constraintsCount; for (int i = 0; i < dots.Count; i++) { for (int k = 0; k < resultsGridView.Columns.Count; k++) { resultsGridView.Rows[dots[i].Item1 + period * i + 1].Cells[k].Style.BackColor = Color.Green; } for (int k = 0; k < period - 1; k++) { resultsGridView.Rows[k + i * period].Cells[dots[i].Item2 + 3].Style.BackColor = Color.Green; } } } void textReults(List<Tuple<int, int>> dots) { string restext = $"Шаг {1}\n"; restext += $"Переносим в таблицу (столбец \"Базис\") базисные элементы (переменные для условий).\n"; //Transfer to the table the basic elements that we identified in the preliminary stage restext += $"Переносим в таблицу (столбец \"C\") коэффициент, соответствующий базисной переменной этой строки.\n"; //Each cell of this column is equal to the coefficient, which corresponds to the //base variable in the corresponding row. restext += $"Переносим соответствеющие значения коэффициентов из математической модели в соответствующие строки без изменений.\n"; //At this stage, no calculations are needed, just transfer the values from the preliminary stage to the corresponding table cells restext += $"Значения целевой функции \"F\" равны сумме поэлементного произведения столбцов \"Коэфф.\" и \"С\".\n"; // We calculate the value of the objective function by elementwise multiplying the column Cb by the column P, adding the results of the products restext += $"Оценки для каждой контролируемой переменной рассчитываем, поэлементно умножая значение из столбца переменных на значение из столбца C, суммируя результаты произведений и вычитая из их суммы коэффициент целевой функции с этой переменной.\n"; restext += $"Поскольку среди оценок управляемых переменных имеются отрицательные значения, то текущая таблица пока не имеет оптимального решения. Поэтому в базис вводим переменную с наименьшей отрицательной оценкой.\n"; restext += "Выводим из базиса переменную с наименьшим положительным значением \"Коэфф.\" / (Соответствующий коэфф. новой базисной переменной).\n"; 66 restext += "На пересечении строки, соответствующей переменной, выведенной из базиса, и столбца, соответствующего переменной, вводимой в базис, находится разрешающий элемент."; restext += "Этот элемент позволит нам рассчитать элементы таблицы следующей итерации.\n\n"; restext += "Шаг 2:\n"; restext += $"По результатам вычислений предыдущей итерации удалим переменную из базиса x4 и поставим на ее место x2. Все остальные клетки остаются неизмененными.\n"; restext += $"Каждая ячейка столбца \"C\" равна коэффициенту, соответствующему базовой переменной в соответствующей строке.\n"; restext += $"Переносим строку с разрешающим элементом из предыдущей таблицы в текущую таблицу, поэлементно разделив ее значения на разрешающий элемент.\n"; restext += $"Обновляем значения столбца \"Коэфф\", после чего обновляются значения во всей остальной таблице по описанным в шаге 1 правилам.\n"; restext += $"Обнуляем значения перенесённой в базис переменной в симплекс-таблице.\n"; restext += $"Переносим строку разрешающего элемента из предыдущей таблицы в новую.\n"; restext += "Этот элемент позволит нам рассчитать элементы таблицы следующей итерации.\n\n"; restext += "Шаг 3:\n"; restext += $"По результатам вычислений предыдущей итерации удалим из базиса переменную x5 и поставим на ее место x1. Все остальные клетки остаются неизмененными.\n"; restext += $"Каждая ячейка столбца \"C\" равна коэффициенту, соответствующему базовой переменной в соответствующей строке.\n"; restext += $"Переносим строку с разрешающим элементом из предыдущей таблицы в текущую таблицу, поэлементно разделив ее значения на разрешающий элемент.\n"; restext += $"Значения целевой функции \"F\" равны сумме поэлементного произведения столбцов \"Коэфф.\" и \"С\".\n"; restext += $"Оценки для каждой контролируемой переменной рассчитываем, поэлементно умножая значение из столбца переменных на значение из столбца C, суммируя результаты произведений и вычитая из их суммы коэффициент целевой функции с этой переменной.\n"; restext += $"Ни один из коэффициентов контроллируемых переменных не ниже нуля, следовательно полученный результат оптимален.\n"; restext += $""; //We transfer the row with the resolving element from the previous table into the current table, //elementwise dividing its values into the resolving element richTextBox1.Text += restext; } void ShowResultsGrid(List<SimplexSnap> snaps) { resultsGridView.Rows.Clear(); 67 resultsGridView.ColumnCount snaps.First().matrix.Length + 4; resultsGridView.RowHeadersVisible = false; resultsGridView.ColumnHeadersVisible = false; = for (int i = 0; i < snaps.First().matrix.Length + 4; i++) { resultsGridView.Columns[i].Width = 50; resultsGridView.Columns[i].DefaultCellStyle.Alignment DataGridViewContentAlignment.MiddleCenter; } foreach (SimplexSnap snap in snaps) { string[] firstRow string[snaps.First().matrix.Length + 4]; = = new firstRow[0] = "i"; firstRow[1] = "Базис"; firstRow[2] = "C"; firstRow[3] = "Коэфф."; for (int i = 4; snaps.First().matrix.Length + 4; i++) { firstRow[i] = $"X{i - 3}"; } i < resultsGridView.Rows.Add(firstRow); for (int i = 0; i < snaps.First().C.Length; i++) { string[] row = new string[snaps.First().matrix.Length + 4]; for (int j = 0; j < snaps.First().matrix.Length + 4; j++) { if (j == 0) { row[j] = (i + 1).ToString(); } else if (j == 1) { row[j] = $"X{snap.C[i] + 1}"; } else if (j == 2) { row[j] = snap.m[snap.C[i]] ? "-M" : $"{snap.fVars[snap.C[i]]}"; } else if (j == 3) { 68 row[j] = Round(snap.b[i]).ToString(); } else { row[j] = Round(snap.matrix[j - 4][i]).ToString(); } } resultsGridView.Rows.Add(row); } string[] fRow = new string[snaps.First().matrix.Length + 4]; fRow[0] = "m+1"; fRow[1] = "F"; fRow[2] = "Δj"; fRow[3] = Round(snap.fValue).ToString(); for (int i = 4; i < snaps.First().matrix.Length + 4; i++) { fRow[i] = Round(snap.F[i - 4]).ToString(); } resultsGridView.Rows.Add(fRow); if (!snap.isMDone) { string[] mRow = string[snaps.First().matrix.Length + 4]; mRow[0] = "m+2"; mRow[1] = "M"; mRow[2] = "Δj"; mRow[3] = ""; for (int i = 4; i snaps.First().matrix.Length + 4; i++) { mRow[i] = Round(snap.M[i 4]).ToString(); } resultsGridView.Rows.Add(mRow); } string[] emptyRow = string[snaps.First().matrix.Length + 4]; resultsGridView.Rows.Add(emptyRow); } resultsGridView.Columns.RemoveAt(0); //resultsGridView.Columns.RemoveAt(2); resultsGridView.Rows.RemoveAt(6); } double Round(double a) { return Math.Round(a, 2); } } } 69 new < - new Приложение В – Листинг модульных тестов В.1 Модуль UnitTest1 using FluentAssertions; using Ref_LR9; using Ref_LR9.Bills; using Ref_LR9.FileSource; namespace SimplexTests { public class ResultTests { public Customer TestCustNoBonuses = new("Jej", 0); public new("TestGoodRegNoDisc", PercentForQuantity()); public new("TestGoodRegNoDisc", PercentForQuantity()); public new("TestGoodRegNoDisc", PercentForQuantity()); RegularGoods TestGoodRegular new AmountForQuantity(), = new SaleGoods TestGoodSale new AmountForQuantity(), = new SpecialGoods TestGoodSpecial new AmountForQuantity(), = new IView view = new TxtView(); [Fact] public void SimplexTableTest() { Bill bill = new Bill(TestCustNoBonuses); Item item = new Item(TestGoodRegular, 2, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] {' ', '\t', '\n' }); Assert.Equal(1, Convert.ToInt32(str[^3])); // Assert.Equal(20, Convert.ToDouble(str[^6])); // bonus check price check } [Fact] public void SimplexTablesTransitionTests() { Bill bill = new Bill(TestCustNoBonuses); Item item = new Item(TestGoodRegular, 20, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] { ' ', '\t', '\n' }); 70 Assert.Equal(10, Convert.ToInt32(str[^3])); // Assert.Equal(194, Convert.ToDouble(str[^6])); // bonus check price check } [Fact] public void SimplexCalculationTest() { Bill bill = new Bill(TestCustNoBonuses); Item item = new Item(TestGoodSpecial, 2, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] { ' ', '\t', '\n' }); Assert.Equal(0, Convert.ToInt32(str[^3])); // Assert.Equal(20, Convert.ToDouble(str[^6])); // bonus check price check } } public class SimplexSnapsTests { public Customer TestCustWithBonuses = new("Jej", 100); public new("TestGoodRegNoDisc", PercentForQuantity()); public new("TestGoodRegNoDisc", PercentForQuantity()); public new("TestGoodRegNoDisc", PercentForQuantity()); RegularGoods TestGoodRegular new AmountForQuantity(), = new SaleGoods TestGoodSale new AmountForQuantity(), = new SpecialGoods TestGoodSpecial new AmountForQuantity(), = new public IView view = new TxtView(); [Fact] public void SnapCopyTest() { Bill bill = new Bill(TestCustWithBonuses); Item item = new Item(TestGoodRegular, 2, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] { ' ', '\t', '\n' }); Assert.Equal(1, Convert.ToInt32(str[^3])); // Assert.Equal(20, Convert.ToDouble(str[^6])); // bonus check price check } [Fact] public void SnapArrayTest() 71 { Bill bill = new Bill(TestCustWithBonuses); Item item = new Item(TestGoodRegular, 20, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] { ' ', '\t', '\n' }); Assert.Equal(10, Convert.ToInt32(str[^3])); // Assert.Equal(94, Convert.ToDouble(str[^6])); // bonus check price check } [Fact] public void SimplexIndexTest() { Bill bill = new Bill(TestCustWithBonuses); Item item = new Item(TestGoodSpecial, 2, 10); bill.addGoods(item); BillGenerator billGenerator = new(view, bill); string[] str = billGenerator.GetBill().Split(new char[3] { ' ', '\t', '\n' }); Assert.Equal(0, Convert.ToInt32(str[^3])); // Assert.Equal(0, Convert.ToDouble(str[^6])); // bonus check price check } } public class ParsingTests { [Fact] public void ConditionParsingTest() { string str = "CustomerName: Test\nCustomerBonus: 10\nGoodsTotalCount: 0\n#\nItemsTotalCount: 0\n#"; StringReader sr = new(str); IFileSource fs = new YamlFileSource(sr); Bill bill = BillFactory.CreateBill(fs, new DateTime(2024,12,01)); bill._customer.getName().Should().Be("Test"); bill._customer.getBonus().Should().Be(10); } [Fact] public void FunctionParsingTest() { string str = "CustomerName: Test\nCustomerBonus: 10\nGoodsTotalCount: 0\n#\nItemsTotalCount: 0\n#"; StringReader sr = new(str); IFileSource fs = new YamlFileSource(sr); Bill bill = BillFactory.CreateBill(fs, new DateTime(2024, 12, 01)); bill._items.Count.Should().Be(0); 72 } [Fact] public void ExtremumParsingTest() { string str = "CustomerName: Test\nCustomerBonus: 10\nGoodsTotalCount: 1\n#\n1: Cola REG\nItemsTotalCount: 1\n#\n1: 1 65 6"; StringReader sr = new(str); IFileSource fs = new YamlFileSource(sr); Bill bill = BillFactory.CreateBill(fs, new DateTime(2024, 12, 01)); bill._items[0].getPrice().Should().Be(65); bill._items[0].getQuantity().Should().Be(6); bill._items[0].getGoods().getTitle().Should().Be("Cola"); } } } 73 Приложение Г – Антиплагиат 74