865 lines
52 KiB
TeX
865 lines
52 KiB
TeX
\documentclass[a4paper, final]{article}
|
||
%\usepackage{literat} % Нормальные шрифты
|
||
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
|
||
\usepackage{tabularx}
|
||
\usepackage[T2A]{fontenc}
|
||
\usepackage[utf8]{inputenc}
|
||
\usepackage[russian]{babel}
|
||
\usepackage{amsmath}
|
||
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
||
\usepackage{ragged2e} %для растягивания по ширине
|
||
\usepackage{setspace} %для межстрочно го интервала
|
||
\usepackage{moreverb} %для работы с листингами
|
||
\usepackage{indentfirst} % для абзацного отступа
|
||
\usepackage{moreverb} %для печати в листинге исходного кода программ
|
||
\usepackage{pdfpages} %для вставки других pdf файлов
|
||
\usepackage{tikz}
|
||
\usepackage{graphicx}
|
||
\usepackage{afterpage}
|
||
\usepackage{longtable}
|
||
\usepackage{float}
|
||
|
||
|
||
|
||
% \usepackage[paper=A4,DIV=12]{typearea}
|
||
\usepackage{pdflscape}
|
||
% \usepackage{lscape}
|
||
|
||
\usepackage{array}
|
||
\usepackage{multirow}
|
||
|
||
\renewcommand\verbatimtabsize{4\relax}
|
||
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
|
||
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
|
||
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
|
||
\usepackage{listings} %листинги
|
||
\usepackage{xcolor} % цвета
|
||
\usepackage{hyperref}% для гиперссылок
|
||
\usepackage{enumitem} %для перечислений
|
||
|
||
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
|
||
|
||
|
||
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
|
||
|
||
\hypersetup{colorlinks,
|
||
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
|
||
|
||
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
|
||
\lstloadlanguages{ SQL}
|
||
% включаем кириллицу и добавляем кое−какие опции
|
||
\lstset{tabsize=2,
|
||
breaklines,
|
||
basicstyle=\footnotesize,
|
||
columns=fullflexible,
|
||
flexiblecolumns,
|
||
numbers=left,
|
||
numberstyle={\footnotesize},
|
||
keywordstyle=\color{blue},
|
||
inputencoding=cp1251,
|
||
extendedchars=true
|
||
}
|
||
\lstdefinelanguage{MyC}{
|
||
language=SQL,
|
||
% ndkeywordstyle=\color{darkgray}\bfseries,
|
||
% identifierstyle=\color{black},
|
||
% morecomment=[n]{/**}{*/},
|
||
% commentstyle=\color{blue}\ttfamily,
|
||
% stringstyle=\color{red}\ttfamily,
|
||
% morestring=[b]",
|
||
% showstringspaces=false,
|
||
% morecomment=[l][\color{gray}]{//},
|
||
keepspaces=true,
|
||
escapechar=\%,
|
||
texcl=true
|
||
}
|
||
|
||
\textheight=24cm % высота текста
|
||
\textwidth=16cm % ширина текста
|
||
\oddsidemargin=0pt % отступ от левого края
|
||
\topmargin=-1.5cm % отступ от верхнего края
|
||
\parindent=24pt % абзацный отступ
|
||
\parskip=5pt % интервал между абзацами
|
||
\tolerance=2000 % терпимость к "жидким" строкам
|
||
\flushbottom % выравнивание высоты страниц
|
||
|
||
|
||
% Настройка листингов
|
||
\lstset{
|
||
language=python,
|
||
extendedchars=\true,
|
||
inputencoding=utf8,
|
||
keepspaces=true,
|
||
% captionpos=b, % подписи листингов снизу
|
||
}
|
||
|
||
\begin{document} % начало документа
|
||
|
||
|
||
|
||
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
|
||
\begin{center}
|
||
\hfill \break
|
||
\hfill \break
|
||
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
|
||
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
|
||
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
|
||
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
|
||
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
|
||
|
||
\hfill \break
|
||
\hfill \break
|
||
\hfill \break
|
||
\hfill \break
|
||
\large{Лабораторная работа №3}\\
|
||
\large{<<Статический анализ кода приложений>>}\\
|
||
\large{по дисциплине}\\
|
||
\large{<<Методы тестирования программного обеспечения>>}\\
|
||
\hfill \break
|
||
|
||
% \hfill \break
|
||
\hfill \break
|
||
\end{center}
|
||
|
||
\small{
|
||
\begin{tabular}{lrrl}
|
||
\!\!\!Студент, & \hspace{2cm} & & \\
|
||
\!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
|
||
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Курочкин М. А. \\\\
|
||
&&\hspace{4cm}
|
||
\end{tabular}
|
||
\begin{flushright}
|
||
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г.
|
||
\end{flushright}
|
||
}
|
||
|
||
\hfill \break
|
||
% \hfill \break
|
||
\begin{center} \small{Санкт-Петербург, 2025} \end{center}
|
||
\thispagestyle{empty} % выключаем отображение номера для этой страницы
|
||
|
||
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
|
||
\newpage
|
||
|
||
\tableofcontents
|
||
|
||
|
||
\newpage
|
||
|
||
\section{Постановка задачи}
|
||
\subsection{Задачи лабораторной работы}
|
||
Задачи лабораторной работы:
|
||
\begin{itemize}
|
||
\item изучить методы статического тестирование;
|
||
\item провести статическое тестирование программы;
|
||
\item проанализировать полученный результат;
|
||
\item рассмотреть рекомендации статического анализатора и при необходимости внести изменения в программу.
|
||
\end{itemize}
|
||
|
||
|
||
\newpage
|
||
\section {Статическое тестирование}
|
||
Статическое тестирование — это оценка элемента тестирования, при которой не происходит выполнения кода, и которая может быть проведена вручную или с помощью инструментариев. Объектом тестирования может быть документация или исходный код, а сам процесс возможен на любом этапе жизненного цикла ПО (Согласно ГОСТ Р 56920-2024 (раздел 5.5.2)).
|
||
|
||
Статическое тестирование включает в себя:
|
||
\begin{itemize}
|
||
\item Проверку документации: требования, спецификации, архитектурные решения.
|
||
\item Анализ исходного кода: поиск синтаксических ошибок, нарушений стандартов
|
||
кодирования и потенциальных уязвимостей.
|
||
\end{itemize}
|
||
|
||
Цель статического тестирования — выявление дефектов на ранних стадиях разработки, что снижает затраты на их исправление. Как отмечает Гленфорд Майерс
|
||
в книге «Искусство тестирования», до 60\% ошибок можно обнаружить до запуска
|
||
программы. Это делает статическое тестирование критически важным инструментом для повышения качества ПО.
|
||
|
||
\subsection{Основные формы статического тестирования}
|
||
Используются специальные инструменты -- «статические анализаторы», которые автоматически:
|
||
|
||
\begin{itemize}
|
||
\item Проверяют соответствие кода стандартам кодирования (Code Style, Coding
|
||
Guidelines).
|
||
\item Ищут потенциальные ошибки (например, неиспользуемые переменные, некорректные приведения типов, опасные конструкции).
|
||
\item Указывают на потенциальные уязвимости в безопасности (например, возможности для SQL-инъекций, потенциальные переполнения буфера).
|
||
\item Анализируют потоки данных, чтобы понять, где значения могут принимать
|
||
нежелательные (NullPointerException и др.) значения.
|
||
\end{itemize}
|
||
|
||
|
||
\subsection{Разница между статическим анализатором и инспекцией кода за столом}
|
||
\subsubsection*{Способ выполнения}
|
||
\begin{itemize}
|
||
\item Статический анализатор: запускается автоматически на исходном коде и выдает отчёт об обнаруженных проблемах. Анализатор следует набору заранее
|
||
заданных правил (линейный и/или межпроцедурный анализ, анализ потока
|
||
данных и т. д.).
|
||
\item Инспекция кода за столом: проводится людьми (разработчиками, тестировщиками). Участники встречи просматривают код построчно (или анализируют его логические куски) и обсуждают архитектурные, логические, стилевые и
|
||
другие аспекты.
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Область охвата}
|
||
\begin{itemize}
|
||
\item Статический анализатор:
|
||
\begin{itemize}
|
||
\item Ориентирован в основном на типичные ошибки и «сигнализирует» о
|
||
потенциальных проблемах, отклонениях от правил кодирования, уязвимостях в безопасности.
|
||
\item Хорошо справляется с рутинным поиском большого количества распространённых проблем (например, неиспользуемые переменные, неочевидные «if» без «else», выход за границы массива и т. п.).
|
||
\end{itemize}
|
||
|
||
\item Инспекция кода:
|
||
\begin{itemize}
|
||
\item Позволяет вскрыть более сложные логические ошибки, несоответствие
|
||
требованиям, некорректную бизнес-логику.
|
||
\item Во время обсуждения могут выявиться проблемы, которые невозможно уловить статическим анализатором: «Почему этот алгоритм выбран
|
||
именно так?», «Соответствует ли это бизнес-требованиям?», «Оптимальна ли структура данных?», «Легко ли будет поддерживать этот код?».
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Глубина и виды обнаруживаемых дефектов}
|
||
\begin{itemize}
|
||
\item Статический анализатор: находит скорее «структурные» и «синтаксические»
|
||
дефекты и уязвимости (неиспользуемые переменные, неправильные операции
|
||
с памятью, отсутствие проверок). Может не понимать, «хорош ли» сам алгоритм.
|
||
\item Инспекция кода: ориентирована на логику, архитектуру, читаемость, потенциальные проблемы взаимодействия модулей. Тут важны не только дефекты
|
||
самого кода, но и соответствует ли он требованиям или лучшим практикам
|
||
проектирования.
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Скорость и автоматизация}
|
||
\begin{itemize}
|
||
\item Статический анализатор: работает быстро (особенно если хорошо интегрирован в CI/CD); выдаёт отчёты сразу после запуска.
|
||
\item Инспекция кода: процесс требует участия людей и времени на обсуждение.
|
||
Однако именно в этом процессе выявляются «глубинные» проблемы, которые
|
||
не найдёт автоматизированный инструмент.
|
||
\end{itemize}
|
||
|
||
\subsubsection*{Результаты и интерпретация}
|
||
\begin{itemize}
|
||
\item Статический анализатор:
|
||
\begin{itemize}
|
||
\item Даёт отчёты (логи, списки ошибок/предупреждений) -- но они нуждаются в
|
||
интерпретации человеком, поскольку есть ложные срабатывания (false
|
||
positives).
|
||
\item Для принятия решения о серьёзности проблемы часто всё равно приходится просматривать код.
|
||
\end{itemize}
|
||
\item Инспекция кода:
|
||
\begin{itemize}
|
||
\item Часто приводит не только к обнаружению ошибок, но и к улучшению
|
||
совместной экспертизы в команде.
|
||
\item Может завершиться рекомендациями по рефакторингу, изменению архитектуры, или даже пересмотром требований.
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
|
||
\newpage
|
||
\section {Описание приложения и среды разработки}
|
||
Название программы: Генератор паролей.
|
||
|
||
Задача программы: Сгенерировать пароль с параметрами, заданными пользователем.
|
||
|
||
Дано:
|
||
\begin{itemize}
|
||
\item число -- длина генерируемого пароля;
|
||
\item строка <<yes>> или <<no>>, если пользователь введёт строку <<yes>, то в пароле будут использоваться строчные буквы;
|
||
\item строка <<yes>> или <<no>>, если пользователь введёт строку <<yes>, то в пароле будут использоваться заглавные буквы;
|
||
\item строка <<yes>> или <<no>>, если пользователь введёт строку <<yes>, то в пароле будут использоваться цифры;
|
||
\item строка <<yes>> или <<no>>, если пользователь введёт строку <<yes>, то в пароле будут использоваться спецсимволы;
|
||
\item число -- минимальное количество строчных букв в пароле;
|
||
\item число -- минимальное количество заглавных букв в пароле;
|
||
\item число -- минимальное количество цифр в пароле;
|
||
\item число -- минимальное количество спецсимволов;
|
||
\end{itemize}
|
||
|
||
Ограничения:
|
||
\begin{itemize}
|
||
\item допустимая длина пароля -- от 1 до 100 символов;
|
||
\item минимальное количество строчных букв -- 0;
|
||
\item минимальное количество заглавных букв -- 0;
|
||
\item минимальное количество цифр -- 0;
|
||
\item минимальное количество спецсимволов -- 0;
|
||
\item сумма минимального количества строчных букв, заглавных букв, цифр и спецсимволов не может превышать длину пароля.
|
||
\end{itemize}
|
||
|
||
Программа была написана на языке Python. В качестве среды разработки использовалось лицензионное программное обеспечение для редактирования текста -- Microsoft Visual Studio Code.
|
||
|
||
|
||
\newpage
|
||
\subsection{Спецификация тестируемой программы}
|
||
|
||
Спецификация программы представлена в таблице~\ref{tbl:spec}.
|
||
|
||
\begin{table}[h!]
|
||
\centering
|
||
\caption{Спецификация.}
|
||
\footnotesize
|
||
\begin{tabularx}{\textwidth}{|X|X|X|}
|
||
\hline
|
||
\textbf{Входные данные} & \textbf{Выходные данные} & \textbf{Комментарий} \\
|
||
\hline
|
||
10 y y y y 1 1 1 1 & 6KoL4Tfn*M & Программа сгенерировала и вывела на экран пароль с заданными параметрами. \\
|
||
\hline
|
||
-10 y y y y 1 1 1 1 & Ошибка: значение должно быть не меньше 1. Попробуйте снова. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\
|
||
\hline
|
||
10 abc y y y 1 1 1 1 & Ошибка: введите 'yes' (или 'y') или 'no' (или 'n'). & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\
|
||
\hline
|
||
10 abc y y y -1 1 1 1 & Ошибка: значение должно быть не меньше 0. Попробуйте снова. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\
|
||
\hline
|
||
10 abc y y y 5 5 5 5 & Ошибка: сумма минимальных значений (103) превышает длину пароля (10). Пожалуйста, введите параметры заново. & Программа вывела на экран сообщение о некорректном пользовательском вводе. \\
|
||
\hline
|
||
\end{tabularx}
|
||
\label{tbl:spec}
|
||
\end{table}
|
||
|
||
\subsection{Исходный код программы тестируемой программы}
|
||
|
||
Исходный код программы представлен в листинге~\ref{lst:code}.
|
||
|
||
\begin{lstlisting}[caption={Исходный код.}, label={lst:code}]
|
||
import random
|
||
import string
|
||
|
||
max_password_length = 100 # Максимальная длина пароля
|
||
|
||
|
||
def get_valid_int(prompt, min_value=0, max_value=None):
|
||
while True:
|
||
user_input = input(prompt).strip()
|
||
if not user_input:
|
||
print("Ошибка: ввод не должен быть пустым. Попробуйте снова.")
|
||
continue
|
||
try:
|
||
value = int(user_input)
|
||
if value < min_value:
|
||
print(
|
||
f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова."
|
||
)
|
||
continue
|
||
if max_value and value > max_value:
|
||
print(
|
||
f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова."
|
||
)
|
||
continue
|
||
return value
|
||
except ValueError:
|
||
print("Ошибка: введите корректное целое число.")
|
||
|
||
|
||
def get_yes_no(prompt):
|
||
while True:
|
||
user_input = input(prompt).strip().lower()
|
||
if user_input in ["yes", "y"]:
|
||
return True
|
||
if user_input in ["no", "n"]:
|
||
return False
|
||
print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').")
|
||
|
||
|
||
|
||
def get_user_input():
|
||
"""Запрашивает у пользователя параметры генерации пароля с проверкой ввода."""
|
||
global max_password_length
|
||
|
||
length = get_valid_int(
|
||
f"Введите длину пароля (1-{max_password_length}): ",
|
||
min_value=1,
|
||
max_value=max_password_length,
|
||
)
|
||
|
||
use_lower = get_yes_no("Использовать строчные буквы? (yes/y, no/n): ")
|
||
use_upper = get_yes_no("Использовать заглавные буквы? (yes/y, no/n): ")
|
||
use_digits = get_yes_no("Использовать цифры? (yes/y, no/n): ")
|
||
use_special = get_yes_no("Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): ")
|
||
|
||
# Проверяем, что хотя бы один тип символов выбран
|
||
if not (use_lower or use_upper or use_digits or use_special):
|
||
print("Ошибка: необходимо выбрать хотя бы один тип символов.")
|
||
return get_user_input() # Повторный ввод всех данных
|
||
|
||
# Запрашиваем минимальное количество каждого типа символов
|
||
min_lower = (
|
||
get_valid_int("Минимальное количество строчных букв: ", 0) if use_lower else 0
|
||
)
|
||
min_upper = (
|
||
get_valid_int("Минимальное количество заглавных букв: ", 0) if use_upper else 0
|
||
)
|
||
min_digits = get_valid_int("Минимальное количество цифр: ", 0) if use_digits else 0
|
||
min_special = (
|
||
get_valid_int("Минимальное количество спецсимволов: ", 0) if use_special else 0
|
||
)
|
||
|
||
return (
|
||
length,
|
||
use_lower,
|
||
use_upper,
|
||
use_digits,
|
||
use_special,
|
||
min_lower,
|
||
min_upper,
|
||
min_digits,
|
||
min_special,
|
||
)
|
||
|
||
|
||
def validate_input(length, min_lower, min_upper, min_digits, min_special):
|
||
"""Проверяет, что длина пароля больше суммы минимальных значений."""
|
||
total_required = min_lower + min_upper + min_digits + min_special
|
||
if total_required > length:
|
||
print(
|
||
f"Ошибка: сумма минимальных значений ({total_required}) превышает длину пароля ({length})."
|
||
)
|
||
return False
|
||
return True
|
||
|
||
|
||
def generate_mandatory_chars(
|
||
min_lower,
|
||
min_upper,
|
||
min_digits,
|
||
min_special,
|
||
lower_chars,
|
||
upper_chars,
|
||
digit_chars,
|
||
special_chars,
|
||
):
|
||
"""Генерирует обязательные символы пароля."""
|
||
password = (
|
||
random.choices(lower_chars, k=min_lower)
|
||
+ random.choices(upper_chars, k=min_upper)
|
||
+ random.choices(digit_chars, k=min_digits)
|
||
+ random.choices(special_chars, k=min_special)
|
||
)
|
||
return password
|
||
|
||
|
||
def fill_password(password, length, all_chars):
|
||
"""Дополняет пароль случайными символами до нужной длины."""
|
||
remaining_length = length - len(password)
|
||
password += random.choices(all_chars, k=remaining_length)
|
||
return password
|
||
|
||
|
||
def shuffle_password(password):
|
||
"""Перемешивает символы пароля случайным образом."""
|
||
random.shuffle(password)
|
||
return "".join(password)
|
||
|
||
|
||
def generate_password(
|
||
length,
|
||
use_lower,
|
||
use_upper,
|
||
use_digits,
|
||
use_special,
|
||
min_lower,
|
||
min_upper,
|
||
min_digits,
|
||
min_special,
|
||
):
|
||
"""Генерирует пароль с учётом заданных параметров."""
|
||
lower_chars = string.ascii_lowercase if use_lower else ""
|
||
upper_chars = string.ascii_uppercase if use_upper else ""
|
||
digit_chars = string.digits if use_digits else ""
|
||
special_chars = "!@#$%^&*" if use_special else ""
|
||
|
||
all_chars = lower_chars + upper_chars + digit_chars + special_chars
|
||
|
||
while not validate_input(length, min_lower, min_upper, min_digits, min_special):
|
||
print("Пожалуйста, введите параметры заново.")
|
||
return generate_password(*get_user_input())
|
||
|
||
password = generate_mandatory_chars(
|
||
min_lower,
|
||
min_upper,
|
||
min_digits,
|
||
min_special,
|
||
lower_chars,
|
||
upper_chars,
|
||
digit_chars,
|
||
special_chars,
|
||
)
|
||
password = fill_password(password, length, all_chars)
|
||
password = shuffle_password(password)
|
||
|
||
return password
|
||
|
||
|
||
def main():
|
||
"""Основная функция программы."""
|
||
max_password_length = 100
|
||
user_data = get_user_input()
|
||
password = generate_password(*user_data)
|
||
print("Сгенерированный пароль:", password)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
\end{lstlisting}
|
||
|
||
|
||
\section{Статический анализ кода приложения}
|
||
\subsection{Описание выбранного инструмента для статического анализа кода}
|
||
|
||
В качестве инструмента статического анализа кода проекта был выбран Pylint
|
||
версии 3.3.6. Pylint - это программа для проверки исходного кода, ошибок и качества
|
||
для языка программирования Python. Он назван в соответствии с общепринятым в
|
||
Python соглашением о префиксе «py» и отсылкой к программе lint для программирования на C. Он следует стилю, рекомендованному PEP 8, руководством по стилю
|
||
Python. Он похож на Pychecker и Pyflakes, но включает в себя следующие функции:
|
||
\begin{itemize}
|
||
\item Проверка длины каждой строки;
|
||
\item Проверка правильности формирования имен переменных в соответствии со
|
||
стандартом кодирования проекта;
|
||
\item Проверка того, что заявленные интерфейсы действительно реализованы.
|
||
\end{itemize}
|
||
|
||
Pylint классифицирует свои сообщения об ошибках и предупреждениях по категориям, каждая из которых обозначается соответствующим префиксом. Основные
|
||
виды сообщений следующие
|
||
|
||
\begin{itemize}
|
||
\item C (Convention): Сообщения, связанные со стилем оформления кода (например,
|
||
нарушение соглашений PEP8), именованием переменных, форматированием и
|
||
т.д.
|
||
\item R (Refactor): Рекомендации по рефакторингу кода для улучшения читаемости,
|
||
структуры и поддерживаемости. Эти сообщения помогают улучшить архитектуру кода.
|
||
\item W (Warning): Предупреждения о потенциальных проблемах, которые могут
|
||
привести к ошибкам или неожиданному поведению во время выполнения. Например, возможное использование необъявленной переменной.
|
||
\item E (Error): Ошибки, которые скорее всего приведут к сбоям выполнения программы, такие как неправильное использование синтаксиса, отсутствие необходимых атрибутов или функций.
|
||
\item F (Fatal): Критические ошибки, при обнаружении которых анализ кода прерывается. Обычно это ошибки синтаксиса или другие проблемы, которые делают
|
||
дальнейший анализ невозможным.
|
||
\end{itemize}
|
||
|
||
\subsection{Разбор Pylint правил из группы «Convention»}
|
||
Эти проверки нацелены на то, чтобы код соответствовал принятым соглашениям о стиле (например, PEP8) и был легко читаемым.
|
||
\begin{itemize}
|
||
\item Именование:
|
||
\begin{itemize}
|
||
\item Проверяется, чтобы имена классов, функций, переменных, модулей и
|
||
констант соответствовали принятым стандартам.
|
||
\item Например, классы должны именоваться в стиле CamelCase (например, MyClass), а функции и переменные – в snake\_case (например,
|
||
my\_function, my\_variable).
|
||
\item Также проверяются длина имен и их осмысленность, чтобы они точно
|
||
отражали назначение объекта в коде.
|
||
\end{itemize}
|
||
\item Стиль оформления:
|
||
\begin{itemize}
|
||
\item Длина строк: Pylint следит за тем, чтобы строки не превышали установленную длину (обычно 79 или 99 символов в зависимости от конфигурации).
|
||
\item Отступы и пробелы: Контролируется корректное использование отступов (обычно 4 пробела), пробелов вокруг операторов, после запятых и
|
||
т.д.
|
||
\item Разбиение на строки: Рекомендуется правильно разбивать длинные выражения или вызовы функций на несколько строк для лучшей читаемости.
|
||
\item Пустые строки: Проверяется количество пустых строк между функциями и классами для поддержания визуальной структуры кода.
|
||
\end{itemize}
|
||
\item Документация:
|
||
\begin{itemize}
|
||
\item Docstrings: Pylint обращает внимание на наличие строк документации
|
||
(docstrings) в модулях, классах, функциях и методах.
|
||
\item Формат документации: Документация должна быть оформлена согласно принятым стандартам (например, в формате reStructuredText или
|
||
Google style), чтобы обеспечить понятное описание функционала и параметров.
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
\subsection{ Разбор Pylint правил из группы «Refactor»}
|
||
Эта группа сообщений направлена на улучшение структуры кода, его упрощение
|
||
и повышение поддерживаемости
|
||
|
||
\begin{itemize}
|
||
\item Сложность функций:
|
||
\begin{itemize}
|
||
\item Цикломатическая сложность: Анализируется количество ветвлений,
|
||
циклов и условных операторов. Функции с высокой сложностью могут
|
||
быть трудными для тестирования и отладки.
|
||
\item Слишком длинные функции: Если функция слишком большая или содержит множество аргументов, Pylint может рекомендовать её разбить
|
||
на более мелкие части.
|
||
\end{itemize}
|
||
\item Дублирование кода:
|
||
\begin{itemize}
|
||
\item Проверка на повторяющиеся участки кода, что может указывать на возможность объединения логики в одну функцию или класс.
|
||
\item Цель – уменьшить количество повторений, чтобы изменение в одной
|
||
части кода не требовало повторения исправлений в нескольких местах.
|
||
\end{itemize}
|
||
\item Структурные проблемы:
|
||
\begin{itemize}
|
||
\item Обнаружение слишком больших классов или методов, которые выполняют сразу несколько задач
|
||
\item Рекомендации по разделению ответственности (например, принцип
|
||
единственной ответственности из SOLID) для улучшения модульности
|
||
и тестируемости кода.
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
\subsection{Разбор Pylint правил из группы «Warning»}
|
||
Эта категория охватывает сообщения, которые указывают на потенциальные
|
||
проблемы, не являющиеся критическими ошибками, но способными привести к
|
||
неожиданному поведению.
|
||
|
||
\begin{itemize}
|
||
\item Неиспользуемые элементы:
|
||
\begin{itemize}
|
||
\item Переменные: Если переменная объявлена, но не используется, Pylint сообщает об этом, что помогает избежать загромождения кода.
|
||
\item Импорты: Неиспользуемые модули или функции, импортированные в
|
||
начале файла, могут быть отмечены для удаления.
|
||
\item Аргументы функций: Иногда функция принимает аргументы, которые
|
||
не используются в теле, что может быть сигналом к тому, что интерфейс
|
||
функции следует пересмотреть.
|
||
\end{itemize}
|
||
\item Подозрительные конструкции:
|
||
\begin{itemize}
|
||
\item Использование переменных до объявления: Если переменная используется до того, как ей было присвоено значение, это может привести к
|
||
ошибкам.
|
||
\item Использование изменяемых значений по умолчанию: Применение изменяемых объектов (например, списков или словарей) в качестве значений
|
||
по умолчанию в параметрах функций может привести к неожиданным эффектам.
|
||
\end{itemize}
|
||
\item Ошибки логики:
|
||
\begin{itemize}
|
||
\item Порой конструкция кода может быть синтаксически корректной, но её
|
||
поведение может быть неочевидным или потенциально приводить к логическим ошибкам (например, некорректное сравнение или неверное использование операторов).
|
||
\end{itemize}
|
||
\end{itemize}
|
||
|
||
\newpage
|
||
\section{Процесс тестирования}
|
||
\subsection{Подготовка}
|
||
|
||
Перед использованием PyLint необходимо установить Python и PIP с официального сайта. Затем создать виртуальное окружение с помощью команды \texttt{virtualenv venv}.
|
||
|
||
Чтобы установить PyLint, достаточно выполнить команду \texttt{pip install pylint} внутри виртуального окружения.
|
||
|
||
Для запуска статического анализа достаточно выполнить команду \texttt{pylint file.py}, где \texttt{file.py} -- это название файла с исходным кодом.
|
||
|
||
|
||
\subsection{Результат работы анализатора}
|
||
|
||
Полный вывод команды \texttt{pylint passgen.py} представлен в листинге~\ref{lst:res}. Статический анализатор вывел 12 сообщений о различных проблемах в коде. Они связаны с несоответствием стиля именования, отсутствием документации в модуле и функциях, а также слишком длинными строками. Кроме того, есть предупреждения о неинициализированной глобальной переменной, переопределении имени переменной во внутренней области видимости, а также о слишком большом количестве аргументов в функциях. PyLint также даёт общую оценку кода. Для моей программы он вывел оценку 8.48 из 10, что означает, что код в целом неплох, но его можно улучшить.
|
||
|
||
\begin{lstlisting}[caption={Результат выполнения команды \texttt{pylint passgen.py}.}, label={lst:res}]
|
||
************* Module passgen
|
||
passgen.py:91:0: C0301: Line too long (103/100) (line-too-long)
|
||
passgen.py:1:0: C0114: Missing module docstring (missing-module-docstring)
|
||
passgen.py:4:0: C0103: Constant name "max_password_length" doesn't conform to UPPER_CASE naming style (invalid-name)
|
||
passgen.py:7:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
passgen.py:30:0: C0116: Missing function or method docstring (missing-function-docstring)
|
||
passgen.py:43:4: W0602: Using global for 'max_password_length' but no assignment is done (global-variable-not-assigned)
|
||
passgen.py:97:0: R0913: Too many arguments (8/5) (too-many-arguments)
|
||
passgen.py:97:0: R0917: Too many positional arguments (8/5) (too-many-positional-arguments)
|
||
passgen.py:130:0: R0913: Too many arguments (9/5) (too-many-arguments)
|
||
passgen.py:130:0: R0917: Too many positional arguments (9/5) (too-many-positional-arguments)
|
||
passgen.py:171:4: W0621: Redefining name 'max_password_length' from outer scope (line 4) (redefined-outer-name)
|
||
passgen.py:171:4: W0612: Unused variable 'max_password_length' (unused-variable)
|
||
|
||
-------------------------------------------
|
||
Your code has been rated at 8.48/10 (previous run: 8.48/10, +0.00)
|
||
\end{lstlisting}
|
||
|
||
\subsection{Результат улучшения кода}
|
||
|
||
В соответствии с рекомендациями PyLint в исходный код были добавлены комментарии с документацией для всего модуля и функций, слишком длинные строки были разбиты на более короткие и удобочитаемые, неинициализированная глобальная переменная была удалена, была удалена одна переменная из локальной области видимости, перекрывавшая глобальную переменную, имена всех переменных были приведены в соответствие с принятыми соглашениями языка Python.
|
||
|
||
После внесения перечисленных выше изменений статический анализатор PyLint был запущен ещё раз на обновлённом файле с исходным кодом. Результат запуска представлен в листинге~\ref{lst:res-new}. В этот раз PyLint не вывел никаких сообщений.
|
||
|
||
\begin{lstlisting}[caption={Результат работы PyLint на обновлённом файле с исходным кодом.}, label={lst:res-new}]
|
||
-------------------------------------
|
||
Your code has been rated at 10.00/10
|
||
\end{lstlisting}
|
||
|
||
Обновлённый код программы представлен в листинге~\ref{lst:code-new}.
|
||
|
||
\begin{lstlisting}[caption={Обновлённый код программы.}, label={lst:code-new}]
|
||
"""
|
||
Модуль для генерации безопасных паролей с разными настройками.
|
||
Пользователь может задавать длину, типы символов и минимальное количество каждого типа.
|
||
"""
|
||
|
||
import random
|
||
import string
|
||
|
||
MAX_PASSWORD_LENGTH = 100 # Максимальная длина пароля
|
||
|
||
|
||
def get_valid_int(prompt, min_value=0, max_value=None):
|
||
"""Запрашивает у пользователя целое число, проверяя корректность ввода."""
|
||
while True:
|
||
user_input = input(prompt).strip()
|
||
if not user_input:
|
||
print("Ошибка: ввод не должен быть пустым. Попробуйте снова.")
|
||
continue
|
||
try:
|
||
value = int(user_input)
|
||
if value < min_value:
|
||
print(
|
||
f"Ошибка: значение должно быть не меньше {min_value}. Попробуйте снова."
|
||
)
|
||
continue
|
||
if max_value and value > max_value:
|
||
print(
|
||
f"Ошибка: значение должно быть не больше {max_value}. Попробуйте снова."
|
||
)
|
||
continue
|
||
return value
|
||
except ValueError:
|
||
print("Ошибка: введите корректное целое число.")
|
||
|
||
|
||
def get_yes_no(prompt):
|
||
"""Запрашивает у пользователя 'yes'/'y' или 'no'/'n', проверяя корректность ввода."""
|
||
while True:
|
||
user_input = input(prompt).strip().lower()
|
||
if user_input in ["yes", "y"]:
|
||
return True
|
||
if user_input in ["no", "n"]:
|
||
return False
|
||
print("Ошибка: введите 'yes' (или 'y') или 'no' (или 'n').")
|
||
|
||
|
||
def get_user_input():
|
||
"""Запрашивает у пользователя параметры генерации пароля с проверкой ввода."""
|
||
settings = {
|
||
"length": get_valid_int(
|
||
f"Введите длину пароля (1-{MAX_PASSWORD_LENGTH}): ",
|
||
min_value=1,
|
||
max_value=MAX_PASSWORD_LENGTH,
|
||
),
|
||
"use_lower": get_yes_no("Использовать строчные буквы? (yes/y, no/n): "),
|
||
"use_upper": get_yes_no("Использовать заглавные буквы? (yes/y, no/n): "),
|
||
"use_digits": get_yes_no("Использовать цифры? (yes/y, no/n): "),
|
||
"use_special": get_yes_no(
|
||
"Использовать спецсимволы (!@#$%^&*)? (yes/y, no/n): "
|
||
),
|
||
}
|
||
|
||
# Проверяем, что хотя бы один тип символов выбран
|
||
if not any(
|
||
[
|
||
settings["use_lower"],
|
||
settings["use_upper"],
|
||
settings["use_digits"],
|
||
settings["use_special"],
|
||
]
|
||
):
|
||
print("Ошибка: необходимо выбрать хотя бы один тип символов.")
|
||
return get_user_input() # Повторный ввод всех данных
|
||
|
||
# Запрашиваем минимальное количество каждого типа символов
|
||
settings["min_lower"] = (
|
||
get_valid_int("Минимальное количество строчных букв: ", 0)
|
||
if settings["use_lower"]
|
||
else 0
|
||
)
|
||
settings["min_upper"] = (
|
||
get_valid_int("Минимальное количество заглавных букв: ", 0)
|
||
if settings["use_upper"]
|
||
else 0
|
||
)
|
||
settings["min_digits"] = (
|
||
get_valid_int("Минимальное количество цифр: ", 0)
|
||
if settings["use_digits"]
|
||
else 0
|
||
)
|
||
settings["min_special"] = (
|
||
get_valid_int("Минимальное количество спецсимволов: ", 0)
|
||
if settings["use_special"]
|
||
else 0
|
||
)
|
||
|
||
return settings
|
||
|
||
|
||
def validate_input(settings):
|
||
"""Проверяет, что длина пароля больше суммы минимальных значений."""
|
||
total_required = sum(
|
||
[
|
||
settings["min_lower"],
|
||
settings["min_upper"],
|
||
settings["min_digits"],
|
||
settings["min_special"],
|
||
]
|
||
)
|
||
if total_required > settings["length"]:
|
||
print(
|
||
f"Ошибка: сумма минимальных значений ({total_required})"
|
||
f" превышает длину пароля ({settings['length']})."
|
||
)
|
||
return False
|
||
return True
|
||
|
||
|
||
def generate_mandatory_chars(settings, char_sets):
|
||
"""Генерирует обязательные символы пароля."""
|
||
password = (
|
||
random.choices(char_sets["lower"], k=settings["min_lower"])
|
||
+ random.choices(char_sets["upper"], k=settings["min_upper"])
|
||
+ random.choices(char_sets["digits"], k=settings["min_digits"])
|
||
+ random.choices(char_sets["special"], k=settings["min_special"])
|
||
)
|
||
return password
|
||
|
||
|
||
def fill_password(password, length, all_chars):
|
||
"""Дополняет пароль случайными символами до нужной длины."""
|
||
remaining_length = length - len(password)
|
||
password += random.choices(all_chars, k=remaining_length)
|
||
return password
|
||
|
||
|
||
def shuffle_password(password):
|
||
"""Перемешивает символы пароля случайным образом."""
|
||
random.shuffle(password)
|
||
return "".join(password)
|
||
|
||
|
||
def generate_password(settings):
|
||
"""Генерирует пароль с учётом заданных параметров."""
|
||
char_sets = {
|
||
"lower": string.ascii_lowercase if settings["use_lower"] else "",
|
||
"upper": string.ascii_uppercase if settings["use_upper"] else "",
|
||
"digits": string.digits if settings["use_digits"] else "",
|
||
"special": "!@#$%^&*" if settings["use_special"] else "",
|
||
}
|
||
|
||
all_chars = "".join(char_sets.values())
|
||
|
||
while not validate_input(settings):
|
||
print("Пожалуйста, введите параметры заново.")
|
||
return generate_password(get_user_input())
|
||
|
||
password = generate_mandatory_chars(settings, char_sets)
|
||
password = fill_password(password, settings["length"], all_chars)
|
||
password = shuffle_password(password)
|
||
|
||
return password
|
||
|
||
|
||
def main():
|
||
"""Основная функция программы."""
|
||
user_settings = get_user_input()
|
||
password = generate_password(user_settings)
|
||
print("Сгенерированный пароль:", password)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
\end{lstlisting}
|
||
|
||
\newpage
|
||
\section*{Заключение}
|
||
\addcontentsline{toc}{section}{Заключение}
|
||
|
||
В ходе выполнения данной лабораторной работы было проведено статистическое тестирование для программы: <<Генератор паролей>>.
|
||
|
||
Были найдены следующие проблемы:
|
||
\begin{itemize}
|
||
\item 5 нарушений правил оформления исходного кода;
|
||
\item 3 предупреждения о возможных ошибках в исходном коде;
|
||
\item 4 рекомендации по рефакторингу исходного кода.
|
||
\end{itemize}
|
||
|
||
В результате проделанной работы программный код был исправлен в соответствии с рекомендациями статистического анализатора. На примере небольшой программы была наглядно продемонстрирована польза от использования статических анализаторов кода. Были сделаны выводы о том, что статистическое тестирование позволяет выявить некорректность как в логике работы программы, так и в стиле её оформления. На примере PyLint был получен первый опыт использования статических анализаторов кода.
|
||
|
||
В процессе статистического тестирования были выявлены некоторые недостатки, которых не удалось обнаружить во время инспекции за столом. Поэтому статистическое тестирование является хорошим дополнением к инспекции за столом.
|
||
К тому же статическое тестирование не столь трудозатрано и его можно автоматизировать.
|
||
|
||
\newpage
|
||
\section*{Список литературы}
|
||
\addcontentsline{toc}{section}{Список литературы}
|
||
|
||
\vspace{-1.5cm}
|
||
\begin{thebibliography}{0}
|
||
\bibitem{mayers}
|
||
Майерс, Г. Искусство тестирования программ. -- Санкт-Петербург: Диалектика, 2012 г.
|
||
\end{thebibliography}
|
||
|
||
\end{document} |