Статическое тестирование кода
This commit is contained in:
7
lab3/.gitignore
vendored
Normal file
7
lab3/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
178
lab3/programm/passgen.py
Normal file
178
lab3/programm/passgen.py
Normal file
@@ -0,0 +1,178 @@
|
||||
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()
|
||||
173
lab3/programm/passgen_refactor.py
Normal file
173
lab3/programm/passgen_refactor.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Модуль для генерации безопасных паролей с разными настройками.
|
||||
Пользователь может задавать длину, типы символов и минимальное количество каждого типа.
|
||||
"""
|
||||
|
||||
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()
|
||||
865
lab3/report.tex
Normal file
865
lab3/report.tex
Normal file
@@ -0,0 +1,865 @@
|
||||
\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}
|
||||
Reference in New Issue
Block a user