LXF171:Обеспечение качества ПО
|
|
|
Содержание |
Тестирование: Даешь качество ПО
Джоно Бэкон исследует мир автоматического и ручного тестирования, чтобы помочь вам сделать свое приложение прочным, как скала. [[Файл: |left | thumb|100px|Наш эксперт Джоно Бэкон — менеджер сообщества Ubuntu, автор The Art Of Community [Искусство Сообщества] и основатель ежегодной встречи Community Leadership Summit.]] Качество — ключевой фактор для любой программы. Неважно, насколько современны ваши функции, насколько впечатляет ваш интерфейс или насколько уникально ваше приложение: если оно ненадежно работает, им перестанут пользоваться.
Однако качество важно не только для обеспечения пользователю приятной работы — оно важно также для того, чтобы ваше сообщество чувствовало себя счастливым и преданным вашему приложению. Плохое качество отражается на всех, кто отстаивает интересы сообщества, а вам ведь не надо, чтобы ваше сообщество постоянно получало жалобы на плохое качество программы и на ошибки в ней.
Создание высококачественного ПО означает понимание процессов Quality Assurance (QA, Обеспечение качества). Некоторые из вас, возможно, уже знакомы с QA, и ассоциируют его с отслеживанием ошибок и тестированием программ. В целом это правильно, но эффективное QA в меньшей степени относится к сообщениям о дефектах и отслеживанию их, а в большей степени — к обеспечению качества вашего ПО. Иными словами, лучше постараться обеспечить высокое качество выпускаемого ПО, чтобы в нем вообще не было ошибок, а не отличные процессы для сообщения об обнаруженных ошибках.
Чтобы обеспечить качество, требуется эффективное тестирование кода, изгоняющее из кода как можно больше ошибок. И эта задача намного сложнее, чем можно ожидать. С одной стороны, всегда можно протестировать разные части кода и проверить, работают ли они, как положено; но как быть в тех случаях, когда код работает в необычных условиях или с необычными данными? При этом возникают хуже всего отслеживаемые проблемы, а также гонки, когда разные ветки кода исполняются в разное время.
Чтобы добиться нашей цели, обеспечения качества, лучшим методом будет применить многогранный подход к тестированию. В идеале надо протестировать наш код поблочно, а также протестировать все функции с точки зрения пользователя. Ограничившись одной из этих задач, должный уровень качества вы не обеспечите; а выполнив обе, станете гораздо ближе к нирване стабильности ПО.
Поблочное тестирование
Самая важная форма тестирования, которую нужно встроить в ваше приложение, это блочное тестирование. Огромное количество программ написано на функциональных объектно-ориентированных языках программирования, в которых код разбивается на функции многократного использования, которые служат специальным целям. Например, у вас, возможно, есть функция, которая пишет файл на диск, или конвертирует данные в другой тип, или возвращает web-страницу с web-сервера. Поблочное тестирование разработано для того, чтобы все эти разнородные функции работали как полагается и добивались желаемого результата.
Общая философия поблочного тестирования заключается в том, что у вас есть тест для каждой функции в вашем приложении. Во многих программных проектах новую функцию не примут в кодовую базу проекта, пока не будет протестирован соответствующий блок. Аналогично, новые функции и поправки не попадут в программу, пока не пройдут блочного тестирования. Это обеспечивает надежный способ проверки, что новые куски кода нечаянно не нарушат уже отлаженные функции вашей программы.
Есть множество разных сред для написания блочных тестов, и неудивительно, что многие из них относятся именно к тому языку, на котором написан код. В этой статье я покажу вам, как использовать модуль unittest, являющийся частью Python; однако структура создания теста может использоваться в большинстве других тестовых сред.
Прежде чем приступать к созданию теста, предположим, что у нас есть простой класс со следующими функциями:
class MyClass():
def return_true(self):
return True
def get_version(self):
version = “1.0”
return version
def get_file(self, fileloc):
try:
with open(fileloc) as f: return True
except IOError as e:
return False
Функций здесь три:
» return_true() В таком виде это самая бесполезная из всех когда-либо написанных функций; но вы представьте, что она делает нечто осмысленное и затем возвращает True.
» get_version() Эта функция просто возвращает текущую версию ПО в качестве строковой переменной. Она может быть полезна при написании API, чтобы клиент взял правильную версию API.
» get_file() Эта функция проверяет, существует ли указанный файл.
Все эти функции возвращают данные, и нам надо написать тест блока, проверяющий, что вернулись правильные данные. Если возвращаются другие данные (например, False вместо True), значит, блочный тест не проходит.
Давайте сначала добавим утверждения import, которые мы будем использовать в этой статье, а потом добавим приведенный выше класс:
import unittest
import tempfile
import os, sys, shutil
class MyClass():
def return_true(self):
return True
def get_version(self):
version = “1.0”
return version
def get_file(self, fileloc):
try:
with open(fileloc) as f: return True
except IOError as e:
return False
Теперь создайте свой первый тест, в данном случае — для функции return_true(). Под кодом, который мы только что добавили (код см. выше), вставьте следующее:
class Tests(unittest.TestCase):
def test_return_true(self):
myclass = MyClass()
value = myclass.return_true()
self.assertTrue(value)
if __name__ == ‘__main__’:
unittest.main()
Здесь мы создаем новый класс unittest под названием Tests. Внутри этого класса мы создаем блочный тест для каждой функции. Как видите, мы добавили наш первый блочный тест, под названием test_return_true().
Для начала мы создаем экземпляр класса MyClass, который тестируется, а затем запускаем функцию и пишем результат в value.
Следующий наш шаг — проверка, являются ли данные в value тем, что мы ожидали. С этой целью мы выполняем утверждение. Утверждение проверяет, соответствует ли передаваемая ему величина тому, что оно ожидает. В этом тесте мы используем assertTrue(), являющееся частью библиотеки unittest, чтобы проверить, является ли value истинной (True), как мы рассчитываем.
Затем в конце исходника мы запускаем функцию main() модуля unittest, чтобы провести тесты.
Чтобы запустить свои тесты и узнать, пройдут ли они, просто выполните скрипт, и вы должны увидеть следующее:
jono@forge:~/Desktop$ python tests.py .
Ran 1 test in 0.000s
OK
Здесь мы запустили один тест, и он прошел отлично. ‘.’ над линией показывает, что тест пройден. Чтобы проверить, не провалился ли тест, измените return True на return False в функции return_true() и снова запустите скрипт. Теперь на экране должно появиться
jono@forge:~/Desktop$ python tests.py
F
======================================
=======
FAIL: test_return_true (__main__.Tests)
Traceback (most recent call last):
File “tests.py”, line 35, in test_return_true
self.assertTrue(value)
AssertionError: False is not true
Ran 1 test in 0.000s
FAILED (failures=1)
Здесь тест не пройден, и указана причина провала.
Давайте напишем второй тест, чтобы проверить, возвращает ли get_version() правильную версию. В этом случае нам не нужно тестировать конкретно версию 1.0, поскольку версии регулярно изменяются. Вместо этого следует убедиться, что функция возвращает переменную, например, 1.0 или 1.5, и мы будем считать эту переменную соответствующим номером версии (поскольку никакой другой код не вносит данные в эту функцию).
Чтобы добавить данный тест, вставьте такую функцию в класс Tests:
def test_get_version(self):
myclass = MyClass()
version = myclass.get_version()
self.assertTrue(isinstance(version, basestring))
Здесь мы снова создали экземпляр класса MyClass, запустили get_version() и вывели результат в version.
Теперь нам надо протестировать, является ли version переменной. Для этого мы используем isinstance(), чтобы проверить, относится ли version к формату Basestring, и, если это так, то вернется True; затем мы проверим это в функции assertTrue(), чтобы вернуть результат утверждения. Снова запустив скрипт, мы увидим:
jono@forge:~/Desktop$ python tests.py
..
Ran 2 tests in 0.000s
OK
Здесь видно, что проведено два теста, и над линией поставлено по точке за каждый удачно пройденный тест.
И снова, если вы хотите, чтобы тест не прошел, отредактируйте исходную функцию, на сей раз изменив “1.0” на 1.0 (удалите кавычки, превратив единицу в число вместо переменной) и перезапустите скрипт, чтобы увидеть провал.
Для нашего финального теста я хочу поговорить о важных частях написания блочных тестов — подготовке и закрытии.
Вы, возможно, заметили, что наши функции не работают с реальными данными. Например, если нужно провести блочный тест, чтобы увидеть, содержит ли определенного типа файл определенный тип данных, как провести этот тест и узнать, что данные, которые мы вводим в функцию — это данные, соответствующие тому типу, на который мы рассчитываем? Конечно, данные могут быть другими, что может послужить причиной провала теста, даже если данные были приемлемы.
Чтобы решить эту проблему, модуль unittest (и многие другие среды тестирования) позволяет запускать эквивалент создателя классов, применимый для создания пробных данных для теста. Точно так же есть эквивалент деструктора классов, который можно использовать для последующего удаления этой тестовой среды. Рассмотрим нашу последнюю функцию, для которой мы хотим написать тест:
def get_file(self, fileloc):
try:
with open(fileloc) as f: return True
except IOError as e:
return False
В данном случае функция проверяет, существует ли файл, и если да, то возвращает True; в противном случае появляется ошибка IOError, и функция возвращает False. Для эффективного тестирования этой функции нам нужно знать, действительно ли существует файл, который мы ей передаем.
Вот тут пригодится папка /tmp в вашем компьютере. Мы автоматически создадим несколько файлов в /tmp, чтобы точно знать те файлы, которые наш блочный тест будет использовать в качестве исходных данных.
Для этого добавьте следующую функцию в ваш класс Tests:
def setUp(self):
self.temp_path = ‘/tmp/testingtemp/’
if not os.path.exists(self.temp_path): os.mkdir(self.temp_path)
for i in range(1, 6):
file = open(os.path.join(self.temp_path, str(i) + “.txt”), ‘w’)
file.write(‘foo’)
file.close()
Здесь мы создаем нашу функцию setup (эквивалент создателя классов). Для этого создается функция под названием setUp(), определяется местоположение в /tmp для сохранения наших файлов, проверяется, существует ли эта директория, и затем создается пять небольших текстовых файлов под названием 1.txt, 2.txt, и т. д.
Когда мы запускаем наши тесты, функция setUp() запускается перед выполнением любого теста. По завершении этой функции у нас будет пять текстовых файлов в /tmp/Testingtemp, которые мы используем в тестах. Это обеспечит готовность нашей тестовой среды перед запуском тестов.
Давайте теперь создадим тест:
def test_get_file(self):
myclass = MyClass()
value = myclass.get_file(“/tmp/testingtemp/1.txt”)
self.assertTrue(value)
Здесь мы создаем экземпляр класса MyClass, запускаем функцию get_file() и передаем ей один из файлов, созданных с помощью setUp(). Технически нам нужно создать один текстовый файл, но мне показалось веселее создать пять. Затем тест проверит, является ли величина, возвращаемая из get_file(), истинной (True). Если это так, тест пройден. Запустив скрипт, мы увидим:
jono@forge:~/Desktop$ python tests.py
...
Ran 3 tests in 0.001s
OK
Как видите, все три теста пройдены успешно.
Удаление тестовых данных
Хотя /tmp периодически вычищается системой, и наши тестовые данные будут удалены, хорошим тоном считается предусмотреть функцию, очищающую от тестовых данных. Для этого создадим функцию tearDown() в классе Tests:
def tearDown(self):
temp_path = ‘/tmp/testingtemp/’
shutil.rmtree(self.temp_path)
Эта функция просто удаляет директорию из /tmp. Теперь, запустив тесты и заглянув в /tmp после их завершения, вы увидите, что тестовых данных там нет.
Конечно, есть много других функций и возможностей в модуле unittest, но я советую заглянуть в руководство пользователя на http://docs.python.org/release/2.6.6/library/unittest.html, где вы найдете более подробную информацию, или в документацию тестовой среды, которой вы пользуетесь.
Тестирование функций
Блочные тесты являются важной частью разработки ПО, и я настоятельно рекомендую вам обзавестись пакетом блочных тестов для ваших приложений, желательно с тестом для каждой функции. Однако блочные тесты — это только проверка функциональных возможностей кода. И они совершенно не выявляют неожиданных результатов при работе приложения.
Среди примеров таких неожиданных результатов могут быть:
» рендеринг ошибок в графических приложениях;
» сломанные или не отвечающие графические виджеты;
» проблема недоступности сетевого соединения;
» текст в графическом приложении занимает слишком много места на экране.
» Приложения работают со сбоями или с ошибками.
» Проблемы интеграции приложения с другими компонентами системы (например, с темами).
Любой из этих сценариев может возникать при благополучно пройденном наборе блочных тестов. И это оставляет нам две возможности тестирования функций: тесты рабочего стола и ручные тесты.
Тесты рабочего стола — это автоматические тесты, имитирующие щелчки мышью в приложении и оценивающие результаты этих щелчков на предмет их соответствия ожиданиям. Есть два основных подхода к проведению этих тестов:
» Уровень доступности — эти тесты создаются инициирующими событиями с помощью среды доступности на рабочем столе (той же среды, которая используется инструментами доступа, например, программами для чтения с экрана).
» Снимки с экрана — эти тесты основаны на том, что делается серия скриншотов, и затем сравниваются функции приложения с частями скриншота этого приложения (например, панель инструментов приложения соответствует панели инструментов на скриншоте).
И хотя обе эти техники бывают полезны, они предполагают наличие неких технологий (например, среды доступности или настроенного набора скриншотов, которые соответствуют теме рабочего стола). Поэтому отсылаю вас к инструментам, используемым для опробования этих подходов (в наборе инструментов Desktop Testing Tools), а вместо этого мы сконцентрируемся на ручных тестах, приложимых ко всему.
Ручное тестирование
Ручные тесты — это подборка небольших предписывающих инструкций, которые мы просим выполнить пользователя для создания ожидаемого результата, а затем просим пользователя сравнить этот результат с должным.
Инструменты для предоставления пользователю ручных тестов (например, Ubuntu test tracker) в первую очередь предназначены для перечисления тестов и предоставления места для сохранения результатов тестирования. Однако в реальности вы можете использовать для этого другие инструменты, например, wiki или электронную таблицу для хранения результатов.
Создание ручных тестов может показаться не сложнее, чем написание нескольких инструкций, однако вам надо подойти к этому более методично, чтобы вы точно смогли протестировать все необходимые части вашего приложения, и чтобы каждый тест работал как надо и выдавал те результаты, которых вы и ожидали. Мы не хотим, чтобы тест провалился из-за того, что ваши инструкции неточны и пользователь их недопонял и нажал не там.
Первый шаг в написании отличного ручного теста — это определение точного списка того, что надо тестировать. Например, для текстового редактора рабочего стола нужно будет протестировать:
» операции с файлами (загрузка/сохранение/перезапись);
» добавление, редактирование, удаление и перемещение текста;
» такие функции, как проверка правописания, поиск, замена, статистика по словам, и т. д.
Составив список требований, можете приступать к написанию тестов, которые будут выполнять пользователи.
Но прежде чем забарабанить по клавиатуре, обдумайте, какие основные тесты вам нужны — те, что отсутствуют в виде блочных тестов или в иной форме тестирования. Когда мы просим пользователей провести ручное тестирование, мы не рассчитываем, что они просидят за тестами четыре часа; это весьма скоро им надоест. Куда практичнее попросить их уделить 20 минут и протестировать самые проблемные или рискованные области вашего приложения, чтобы и нужное тестирование обеспечить, и не ввергнуть пользователя в тоску.
Помня о том, какие вам нужны тесты, создайте в текстовом редакторе новый документ, чтобы написать их, и присвойте каждому тесту номер и идентификатор. Например:
ED-001 file-loading
ED-002 file-editing
ED-003 file-saving
Для каждого теста добавьте описание того, что он делает. Описание должно быть высокоуровневым, но подробным настолько, чтобы быть понятным людям, незнакомым с тестом и приложением. Например:
ED-001 file-loading Загружает текстовый файл в редактор, готовый к редактированию.
ED-002 file-editing Редактирует текстовый файл посредством ввода, удаления и перемещения текста.
ED-003 file-saving Сохраняет текст в новый файл.
Затем задокументируйте все настройки, которые должен сделать пользователь перед запуском теста. Например, для ED-001 — должен ли файл, который он загружает, быть в определенной кодировке и должен ли он загружаться с жесткого диска или с устройства или из сетевого ресурса? Это должно быть ясно указано. Например:
Подготовка: используйте TextEditor 1.0 и загрузите текст в формате UTF-8 с локального жесткого диска.
Теперь для каждого теста напишите набор действий, объясняющих, как проводить тест. Например, для ED-001:
1 Щелкните по пункту меню File.
2 Щелкните пункт Открыть... внутри меню File.
3 Используя выбор файла, выберите текстовый файл (текстовый файл показан небольшим значком с блокнотом и должен иметь расширение .txt).
Каждый тест должен включать не более 10 – 15 действий; если их будет больше, это просто убьет пользователя.
Теперь внятно и четко опишите ожидаемый результат теста. Например, для ED-001:
Результат: Текстовый файл загружается, и весь текст отображается в текстовом режиме со всеми переводами строки и возвратами каретки.
Получившийся у вас готовый тест должен выглядеть приблизительно так:
ED-001 file-loading Загрузка текста в редактор, готовый к редактированию.
Подготовка: используйте TextEditor 1.0 и загрузите текст в формате UTF-8 с локального жесткого диска.
1 Щелкните по пункту в меню File.
2 Щелкните пункт Открыть… в меню File.
3 Используя выбор файла, выберите текстовый файл (текстовый файл показан небольшим значком с блокнотом и должен иметь расширение .txt).
Результат: Текстовый файл загружается и весь текст отображается в текстовом режиме со всеми новыми строками, и носитель возвращается.
Затем пользователь должен сообщить о том пройден ли тест (PASS) или не пройден (FAIL), когда он получит результат, следуя всем перечисленным инструкциям. Поздравляем, теперь у вас есть ручной тест! |
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить