Статическое тестирование кода
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