Разделы презентаций


Модульное тестирование ПО

Содержание

Почему Ваш код – отстой?Ваш код – отстой, если он не работаетВы уверены, что ваш код работает?Вы уверены, что после рефакторинга ваш код будет продолжать работать?Ваш код – отстой, если он

Слайды и текст этой презентации

Слайд 1Модульное тестирование ПО

Модульное тестирование ПО

Слайд 2Почему Ваш код – отстой?
Ваш код – отстой, если он

не работает
Вы уверены, что ваш код работает?
Вы уверены, что после

рефакторинга ваш код будет продолжать работать?
Ваш код – отстой, если он не поддаётся тестированию
Требуются изменения для проведения автоматизированных тестов?
Вы уверены, что после этих изменений код будет продолжать работать?
Почему Ваш код – отстой?Ваш код – отстой, если он не работаетВы уверены, что ваш код работает?Вы

Слайд 3Способы улучшения ситуации
Ручное тестирование кода
Автоматическое тестирование кода
Модульное тестирование
Интеграционное тестирование
В любом

случае:
«Тестирование программ может использоваться для демонстрации наличия ошибок, но оно

никогда не покажет их отсутствие.» - Дейкстра, 1970
Способы улучшения ситуацииРучное тестирование кодаАвтоматическое тестирование кодаМодульное тестированиеИнтеграционное тестированиеВ любом случае:«Тестирование программ может использоваться для демонстрации наличия

Слайд 4Недостатки ручного тестирования ПО
Глубокое ручное тестирование сложного ПО – очень

трудоемкий и дорогостоящий процесс
А вам легко протестировать 300 000 строк

кода?
Результаты тестов не сохраняются и их сложно повторить заново
Недостатки ручного тестирования ПОГлубокое ручное тестирование сложного ПО – очень трудоемкий и дорогостоящий процессА вам легко протестировать

Слайд 5Что такое Модульное тестирование?
Модульное тестирование (Unit testing) – процесс проверки

корректности отдельных модулей (классов) программы
Основная идея - разработка тестов, проверяющих

работу каждой нетривиальной функции или метода модуля
Для облегчения разработки модульных тестов используются различные фреймворки
Что такое Модульное тестирование?Модульное тестирование (Unit testing) – процесс проверки корректности отдельных модулей (классов) программыОсновная идея -

Слайд 6Разработка через тестирование
Разработка через тестирование (test-driven development, TDD) – техника

программирования, при которой модульные тесты пишутся до модулей программы, тем

самым управляя их разработкой
Разработка состоит из коротких циклов (шагов), продолжительность которых обычно составляет несколько минут
TDD – одна из практик Экстремального программирования
Разработка через тестированиеРазработка через тестирование (test-driven development, TDD) – техника программирования, при которой модульные тесты пишутся до

Слайд 7Этапы разработки в стиле Test Driven Development

Этапы разработки в стиле Test Driven Development

Слайд 8Шаг 1. Извлечение кода из репозитория
Из репозитория разработчик извлекает исходный

код программной системы, находящейся в согласованном состоянии, когда весь набор

существующих модульных тестов выполняется успешно.
Этот шаг гарантирует, что разработчик имеет дело с последней рабочей версией исходного кода проекта
Шаг 1. Извлечение кода из репозиторияИз репозитория разработчик извлекает исходный код программной системы, находящейся в согласованном состоянии,

Слайд 9Шаг 2. Добавление теста
К существующему набору тестов добавляется новый тест
Этот

тест может состоять в проверке, реализует ли система некоторое новое

поведение или содержит ли некоторую ошибку, о которой недавно стало известно
Важно написать тест до внесения изменений в код модуля, тем самым предъявляя к модулю новое требование
Шаг 2. Добавление тестаК существующему набору тестов добавляется новый тестЭтот тест может состоять в проверке, реализует ли

Слайд 10Пример
#include “SerialNumberGenerator.h"

// набор тестов для генератора серийных номеров
class CSerialNumberGeneratorTestSuite :

public CxxTest::TestSuite
{
public:
// ранее разработанные тесты
// ...

// команда тестирования сообщила об

ошибке:
// если в качестве серийного номера передана пустая строка, то
// серийный номер ошибочно принимается за правильный
void TestEmptySerialNumber()
{
CSerialNumberGenerator sg;

// серийный номер в виде пустой строки должен считаться невалидным
TS_ASSERT(!sg.VerifySerialNumber("UserName", ""));
}
};
Пример#include “SerialNumberGenerator.h

Слайд 11Шаг 3. Запуск теста
Успешно выполняется весь набор тестов, кроме нового

теста, который выполняется неуспешно
Этот шаг необходим для проверки самого теста


включен ли тест в общую систему тестирования (запускается ли)?
правильно ли тест отражает новое требование к системе, которому она, естественно, еще не удовлетворяет?

In CSerialNumberGeneratorTestSuite::TestEmptySerialNumber:
.\SerialNumberGeneratorTestSuite.h(44): Error: Assertion failed: !sg.VerifySerialNumber("UserName", "")
1>Failed 1 of 4 tests


Слайд 12Шаг 4. Исправление ошибки с минимумом усилий
Программа изменяется с тем,

чтобы как можно скорее выполнялись все тесты.
Нужно добавить самое простое

решение, удовлетворяющее новому тесту, и одновременно с этим не испортить существующие тесты
Большая часть нежелательных побочных и отдаленных эффектов от вносимых в программу изменений отслеживается именно на этом этапе, с помощью достаточно полного набора тестов
Шаг 4. Исправление ошибки с минимумом усилийПрограмма изменяется с тем, чтобы как можно скорее выполнялись все тесты.Нужно

Слайд 13Пример
class CSerialNumberGenerator
{
public:
// серийный номер ошибочно принимается за правильный
bool VerifySerialNumber(string const&

userName, string const& serialNumber)
{
if (serialNumer.empty())
{
return false;
}
//...
}
};

Примерclass CSerialNumberGenerator{public:	// серийный номер ошибочно принимается за правильный	bool VerifySerialNumber(string const& userName, string const& serialNumber)	{		if (serialNumer.empty())		{			return false;		}		//...	}};

Слайд 14Шаг 5 – запуск тестов
После внесения исправлений в код модуля

весь набор тестов должен выполняться успешно
Что, если это не так?
Проверить

корректность новых требований теста
Ошибки или опечатки в тесте
Совместимость с предыдущими требованиями
Проверить корректность внесенных изменений в код модуля
Исполнить «танец с бубном»
Шаг 5 – запуск тестовПосле внесения исправлений в код модуля весь набор тестов должен выполняться успешноЧто, если

Слайд 15Шаг 6 - рефакторинг
Когда требуемая в этом цикле функциональность достигнута

самым простым способом, производится рефакторинг
Процесс изменения внутренней структуры программы, не

затрагивающий ее внешнего поведения и имеющий целью:
облегчить понимание ее работы;
устранить дублирование кода;
облегчить внесение изменений в ближайшем будущем
Шаг 6 - рефакторингКогда требуемая в этом цикле функциональность достигнута самым простым способом, производится рефакторингПроцесс изменения внутренней

Слайд 16Шаг 7 – запуск тестов
Рефакторинг связан с внесением изменений, которые

могут случайно нарушить работу кода
Ошибки, вносимые при рефакторинге так же

могут быть выявлены в результате автоматического тестирования
Добиваемся успешного выполнения всего набора тестов на данном этапе
Шаг 7 – запуск тестовРефакторинг связан с внесением изменений, которые могут случайно нарушить работу кодаОшибки, вносимые при

Слайд 17Шаг 8 – фиксирование изменений
Комплект изменений, сделанных в этом цикле

в тестах и программе заносится в репозиторий (операция commit)
Теперь программа

снова находится в согласованном состоянии и содержит четко осязаемое улучшение по сравнению с предыдущим состоянием
Шаг 8 – фиксирование измененийКомплект изменений, сделанных в этом цикле в тестах и программе заносится в репозиторий

Слайд 18Разработка в стиле TDD

Разработка в стиле TDD

Слайд 19Пример
Разработка класса, моделирующего самолет

ПримерРазработка класса, моделирующего самолет

Слайд 20Требования
Реализовать класс Airplane, обладающий следующими свойствами
Количество топлива
Количество пассажиров на борту
Наличие

пилотов и стюардесс на борту
Состояние (на земле, взлет, полет, посадка)
Количество

посадочных мест
Класс должен поддерживать следующие методы:
Заправка топливом
Подъем и спуск пассажиров
Подъем и спуск членов экипажа
Взлет
Посадка
ТребованияРеализовать класс Airplane, обладающий следующими свойствамиКоличество топливаКоличество пассажиров на бортуНаличие пилотов и стюардесс на бортуСостояние (на земле,

Слайд 21Шаг 1. Создание теста с использование библиотеки CxxTest
class AirplaneTestSuite :

public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
Airplane airplane;
}
};
Данный код не будет компилироваться, т.к. отсутствует

объявление класса Airplane
Тем не менее, это позволяет нам убедиться, что наш тест был добавлен в проект
Шаг 1. Создание теста с использование библиотеки CxxTestclass AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		Airplane airplane;	}};Данный код не будет

Слайд 22Создание каркаса класса Airplane и модификация теста
class Airplane
{
public:
Airplane()
{
}
};
#include “Airplane.h”

class AirplaneTestSuite

: public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
Airplane airplane;
}
};
Убеждаемся, что набор тестов теперь проходит

нормально
Создание каркаса класса Airplane и модификация тестаclass Airplane{public:	Airplane()	{	}};#include “Airplane.h”class AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		Airplane airplane;	}};Убеждаемся, что набор

Слайд 23Шаг 2 – тестируем начальное состояние самолета
Требования
Для создания самолета необходимо

указать количество мест в нем
Количество пассажиров, пилотов, стюардесс и топлива

в самолете после создания равно нулю
Дорабатываем тест TestAirplaneConstruction, добавляя в него проверку начального состояния самолета
Шаг 2 – тестируем начальное состояние самолетаТребованияДля создания самолета необходимо указать количество мест в немКоличество пассажиров, пилотов,

Слайд 24Добавление проверки нового функционала в тест
class AirplaneTestSuite : public CxxTest::TestSuite
{
public:
void

TestAirplaneConstruction()
{
// 100 - number of seats
Airplane airplane(100);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfPassengers(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfSeats(), 100);
TS_ASSERT_EQUALS(airplane.GetNumberOfPilots(),

0);
TS_ASSERT_EQUALS(airplane.GetNumberOfStewardess(), 0);

// 50 - number of seats
Airplane airplane1(50);
TS_ASSERT_EQUALS(airplane1.GetNumberOfSeats(), 50);
}
};
Добавление проверки нового функционала в тестclass AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		// 100 - number of seats		Airplane airplane(100);		TS_ASSERT_EQUALS(airplane.GetFuel(),

Слайд 25Доработка класса Airplane для успешного прохождения тестов
class Airplane
{
public:
Airplane(size_t numberOfSeats):m_numberOfSeats(numberOfSeats)
{
}

double GetFuel()const{return

0;}
size_t GetNumberOfSeats()const{return m_numberOfSeats;}
size_t GetNumberOfPassengers()const{return 0;}
size_t GetNumberOfPilots()const{return 0;}
size_t GetNumberOfStewardess()const{return 0;}
private:
size_t m_numberOfSeats;
};
В

глаза бросается тот факт, что многие Get-функции возвращают hardcoded-значения. На данном этапе – это минимальное решение, удовлетворяющее успешному прохождению тестов. В дальнейшем мы это исправим
Доработка класса Airplane для успешного прохождения тестовclass Airplane{public:	Airplane(size_t numberOfSeats):m_numberOfSeats(numberOfSeats)	{	}	double GetFuel()const{return 0;}	size_t GetNumberOfSeats()const{return m_numberOfSeats;}	size_t GetNumberOfPassengers()const{return 0;}	size_t GetNumberOfPilots()const{return 0;}	size_t

Слайд 26Пришло время добавить возможность заправки самолета
Выясняется, что самолет должен иметь

еще одно дополнительное состояние – вместимость топливного бака
Это новое требование

мы должны также отразить в модульных тестах
Пришло время добавить возможность заправки самолетаВыясняется, что самолет должен иметь еще одно дополнительное состояние – вместимость топливного

Слайд 27Дорабатываем тест
class AirplaneTestSuite : public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
// 100 - number

of seats, 500.0 – fuel tank capacity
Airplane airplane(100, 500.0);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfPassengers(),

0);
TS_ASSERT_EQUALS(airplane.GetNumberOfSeats(), 100);
TS_ASSERT_EQUALS(airplane.GetNumberOfPilots(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfStewardess(), 0);
TS_ASSERT_EQUALS(airplane.GetFuelTankCapacity(), 500.0);

// 50 - number of seats, 400.3 – fuel tank capacity
Airplane airplane1(50, 400.3);
TS_ASSERT_EQUALS(airplane1.GetNumberOfSeats(), 50);
TS_ASSERT_EQUALS(airplane.GetFuelTankCapacity(), 400.3);
}
};
Дорабатываем тестclass AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		// 100 - number of seats, 500.0 – fuel tank capacity		Airplane

Слайд 28Дорабатываем класс Airplane
class Airplane
{
public:
Airplane(size_t numberOfSeats, double fuelTankCapacity)
:m_numberOfSeats(numberOfSeats)
{
}

double GetFuel()const{return 0;}
double GetFuelTankCapacity()const{return

m_fuelTankCapacity;}

size_t GetNumberOfSeats()const{return m_numberOfSeats;}
size_t GetNumberOfPassengers()const{return 0;}
size_t GetNumberOfPilots()const{return 0;}
size_t GetNumberOfStewardess()const{return 0;}
private:
size_t m_numberOfSeats;
double

m_fuelTankCapacity;
};

Пристальный анализ исходного кода показывает, что мы забыли выполнить инициализацию поля m_fuelTankCapacity в конструкторе класса

Дорабатываем класс Airplaneclass Airplane{public:	Airplane(size_t numberOfSeats, double fuelTankCapacity)		:m_numberOfSeats(numberOfSeats)	{	}	double GetFuel()const{return 0;}	double GetFuelTankCapacity()const{return m_fuelTankCapacity;}		size_t GetNumberOfSeats()const{return m_numberOfSeats;}	size_t GetNumberOfPassengers()const{return 0;}	size_t GetNumberOfPilots()const{return 0;}	size_t

Слайд 29Запускаем тест
Running 1 test
In AirplaneTestSuite::TestAirplaneConstruction:
d:\vivid\unit_testing\unit_testing\airplane\airplanetestsuite.h:18: Error: Expec
ted (airplane.GetFuelTankCapacity() == 500.0),

found (-9.2559E62 != 500.0000)
Failed 1 of 1 test
Success rate: 0%
Ошибка,

допущенная нами при внесении изменений в класс была обнаружена на этапе модульного тестирования

Поскольку изменения, вносимые в класс на каждой итерации, являются минимальными, это упрощает локализацию и исправление ошибки.

В данном случае ошибка является тривиальной и легко исправляется
Запускаем тестRunning 1 testIn AirplaneTestSuite::TestAirplaneConstruction:d:\vivid\unit_testing\unit_testing\airplane\airplanetestsuite.h:18: Error: Expected (airplane.GetFuelTankCapacity() == 500.0), found (-9.2559E62 != 500.0000)Failed 1 of 1

Слайд 30Исправляем класс Airplane
class Airplane
{
public:
Airplane(size_t numberOfSeats, double fuelTankCapacity)
:m_numberOfSeats(numberOfSeats)
,m_fuelTankCapacity(fuelTankCapacity)
{
}

double GetFuel()const{return 0;}
double GetFuelTankCapacity()const{return

m_fuelTankCapacity;}

size_t GetNumberOfSeats()const{return m_numberOfSeats;}
size_t GetNumberOfPassengers()const{return 0;}
size_t GetNumberOfPilots()const{return 0;}
size_t GetNumberOfStewardess()const{return 0;}
private:
size_t m_numberOfSeats;
double

m_fuelTankCapacity;
};
Исправляем класс Airplaneclass Airplane{public:	Airplane(size_t numberOfSeats, double fuelTankCapacity)		:m_numberOfSeats(numberOfSeats)		,m_fuelTankCapacity(fuelTankCapacity)	{	}	double GetFuel()const{return 0;}	double GetFuelTankCapacity()const{return m_fuelTankCapacity;}		size_t GetNumberOfSeats()const{return m_numberOfSeats;}	size_t GetNumberOfPassengers()const{return 0;}	size_t GetNumberOfPilots()const{return 0;}	size_t

Слайд 31Запускаем тесты
Running 1 test
In AirplaneTestSuite::TestAirplaneConstruction:
d:\vivid\unit_testing\unit_testing\airplane\airplanetestsuite.h:23: Error: Expec
ted (airplane.GetFuelTankCapacity() == 400.3),

found (500.0000 != 400.3000)
Failed 1 of 1 test
Success rate: 0%
class

AirplaneTestSuite : public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
// 100 - number of seats, 500.0 – fuel tank capacity
Airplane airplane(100, 500.0);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfPassengers(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfSeats(), 100);
TS_ASSERT_EQUALS(airplane.GetNumberOfPilots(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfStewardess(), 0);
TS_ASSERT_EQUALS(airplane.GetFuelTankCapacity(), 500.0);

// 50 - number of seats, 400.3 – fuel tank capacity
Airplane airplane1(50, 400.3);
TS_ASSERT_EQUALS(airplane1.GetNumberOfSeats(), 50);
TS_ASSERT_EQUALS(airplane.GetFuelTankCapacity(), 400.3);
}
};

Анализ кода показывает, что ошибка была допущена в самом тесте.
Что ж, такое тоже бывает. Нужно просто внести исправление в сам тест. 

class AirplaneTestSuite : public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
// 100 - number of seats, 500.0 – fuel tank capacity
Airplane airplane(100, 500.0);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfPassengers(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfSeats(), 100);
TS_ASSERT_EQUALS(airplane.GetNumberOfPilots(), 0);
TS_ASSERT_EQUALS(airplane.GetNumberOfStewardess(), 0);
TS_ASSERT_EQUALS(airplane.GetFuelTankCapacity(), 500.0);

// 50 - number of seats, 400.3 – fuel tank capacity
Airplane airplane1(50, 400.3);
TS_ASSERT_EQUALS(airplane1.GetNumberOfSeats(), 50);
TS_ASSERT_EQUALS(airplane1.GetFuelTankCapacity(), 400.3);
}
};

Запускаем тестыRunning 1 testIn AirplaneTestSuite::TestAirplaneConstruction:d:\vivid\unit_testing\unit_testing\airplane\airplanetestsuite.h:23: Error: Expected (airplane.GetFuelTankCapacity() == 400.3), found (500.0000 != 400.3000)Failed 1 of 1

Слайд 32Реализация поддержки заправки самолета
При заправке самолета должно произойти увеличение находящегося

в нем топлива на заданную величину
Сначала пишем тест, проверяющий работу

нового метода Refuel()
Реализация поддержки заправки самолетаПри заправке самолета должно произойти увеличение находящегося в нем топлива на заданную величинуСначала пишем

Слайд 33Добавляем новый тест, проверяющий возможность заправки самолета топливом
class AirplaneTestSuite :

public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
...
}

void TestAirplaneRefuel()
{
Airplane airplane(100, 500.0);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
airplane.Refuel(100.0);
TS_ASSERT_EQUALS(airplane.GetFuel(), 100.0);
airplane.Refuel(200.0);
TS_ASSERT_EQUALS(airplane.GetFuel(), 300.0);
}
};

Добавляем новый тест, проверяющий возможность заправки самолета топливомclass AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		...	}	void TestAirplaneRefuel()	{		Airplane airplane(100, 500.0);				TS_ASSERT_EQUALS(airplane.GetFuel(), 0);

Слайд 34Доработка класса Airplane
class Airplane
{
public:
Airplane(size_t numberOfSeats, double fuelTankCapacity)
:m_numberOfSeats(numberOfSeats)
,m_fuelTankCapacity(fuelTankCapacity)
,m_fuel(0)
{
}
double GetFuel()const{return m_fuel;}

...

void

Refuel(double amountOfFuel)
{
m_fuel += amountOfFuel;
}
private:
size_t m_numberOfSeats;
double m_fuelTankCapacity;
double m_fuel;
};

Доработка класса Airplane class Airplane{public:	Airplane(size_t numberOfSeats, double fuelTankCapacity)		:m_numberOfSeats(numberOfSeats)		,m_fuelTankCapacity(fuelTankCapacity)		,m_fuel(0)	{	}	double GetFuel()const{return m_fuel;}		...		void Refuel(double amountOfFuel)	{		m_fuel += amountOfFuel;	}private:	size_t m_numberOfSeats;	double m_fuelTankCapacity;	double m_fuel;};

Слайд 35Дорабатываем тест – нельзя залить топлива больше, чем позволяет бак
class

AirplaneTestSuite : public CxxTest::TestSuite
{
public:
...
void testAirplaneRefuel()
{
Airplane airplane(100, 500.0);

TS_ASSERT_EQUALS(airplane.GetFuel(), 0);
airplane.Refuel(100.0);
TS_ASSERT_EQUALS(airplane.GetFuel(), 100.0);
airplane.Refuel(200.0);
TS_ASSERT_EQUALS(airplane.GetFuel(),

300.0);

// amount of fuel can't exceed the airplane’s fuel tank capacity
airplane.Refuel(300.0);
TS_ASSERT_EQUALS(airplane.GetFuel(), airplane.GetFuelTankCapacity());
}
};
Дорабатываем тест – нельзя залить топлива больше, чем позволяет бакclass AirplaneTestSuite : public CxxTest::TestSuite{public:	...	void testAirplaneRefuel()	{		Airplane airplane(100, 500.0);				TS_ASSERT_EQUALS(airplane.GetFuel(),

Слайд 36Запускаем тест
Running 2 tests.
In AirplaneTestSuite::TestAirplaneRefuel:
D:\vivid\unit_testing\unit_testing\airplane\AirplaneTestSuite.h:38: Error: Expec
ted (airplane.GetFuel() == airplane.GetFuelTankCapacity()),

found (600.0000 != 5
00.0000)
Failed 1 of 2 tests
Success rate: 50%

Запускаем тестRunning 2 tests.In AirplaneTestSuite::TestAirplaneRefuel:D:\vivid\unit_testing\unit_testing\airplane\AirplaneTestSuite.h:38: Error: Expected (airplane.GetFuel() == airplane.GetFuelTankCapacity()), found (600.0000 != 500.0000)Failed 1 of 2

Слайд 37Дорабатываем класс Airplane
class Airplane
{
public:
...

void Refuel(double amountOfFuel)
{
m_fuel += amountOfFuel;
if (m_fuel

> m_fuelTankCapacity)
{
m_fuel = m_fuelTankCapacity;
}
}
private:
size_t m_numberOfSeats;
double m_fuelTankCapacity;
double m_fuel;
};

Дорабатываем класс Airplane class Airplane{public:	...	void Refuel(double amountOfFuel)	{		m_fuel += amountOfFuel;		if (m_fuel > m_fuelTankCapacity)		{			m_fuel = m_fuelTankCapacity;		}	}private:	size_t m_numberOfSeats;	double m_fuelTankCapacity;	double m_fuel;};

Слайд 38Проверка корректности аргументов
Методы класса должны проверять корректность переданных аргументов
Особенно это

касается данных, поступающих извне (пользователь, сеть, входные устройства и т.д)
Один

из способов просигнализировать об ошибке – выбросить исключение
Необходимо стремиться к тому, чтобы код был exception-safe
При выбрасывании исключения внутри метода класса не должно происходить утечек ресурсов
Не должна нарушаться целостность внутренней структуры объекта
По возможности объект должен остаться в том же состоянии, в каком он находился до выброса исключения
Проверка корректности аргументовМетоды класса должны проверять корректность переданных аргументовОсобенно это касается данных, поступающих извне (пользователь, сеть, входные

Слайд 39Проверяем допустимые границы входных аргументов
Не должно иметься возможности заправить самолет

отрицательным количеством топлива
Вместимость бака самолета должно быть в пределах от

300 до 5000 литров
Количество посадочных мест – от 10 до 200
Проверяем допустимые границы входных аргументовНе должно иметься возможности заправить самолет отрицательным количеством топливаВместимость бака самолета должно быть

Слайд 40Доработка теста
class AirplaneTestSuite : public CxxTest::TestSuite
{
public:
void TestAirplaneConstruction()
{
...
for (size_t seats =

0; seats = 10) && (seats

<= 200))
{
TS_ASSERT_THROWS_NOTHING(Airplane plane(seats, 500));
}
else
{
TS_ASSERT_THROWS(Airplane plane(seats, 500), std::invalid_argument);
}
}
}

...
};

Running 2 tests
In AirplaneTestSuite::TestAirplaneConstruction:
d:\vivid\unit_testing\unit_testing\airplane\airplanetestsuite.h:33: Error: Expec
ted (Airplane plane(seats, 500)) to throw (std::invalid_argument) but it didn't
throw
.
Failed 1 of 2 tests
Success rate: 50%

Доработка тестаclass AirplaneTestSuite : public CxxTest::TestSuite{public:	void TestAirplaneConstruction()	{		...		for (size_t seats = 0; seats = 10) && (seats

Слайд 41Доработка класса Airplane
class Airplane
{
enum
{
MIN_NUMBER_OF_SEATS = 10,
MAX_NUMBER_OF_SEATS = 200,
};
public:
Airplane(size_t numberOfSeats, double

fuelTankCapacity)
:m_numberOfSeats(numberOfSeats)
,m_fuelTankCapacity(fuelTankCapacity)
,m_fuel(0)
{
if ((numberOfSeats < MIN_NUMBER_OF_SEATS) ||
(numberOfSeats > MAX_NUMBER_OF_SEATS)
)
{
throw std::invalid_argument("Number of seats

is out of range");
}
}
...
};
Доработка класса Airplaneclass Airplane{	enum	{		MIN_NUMBER_OF_SEATS = 10,		MAX_NUMBER_OF_SEATS = 200,		};public:	Airplane(size_t numberOfSeats, double fuelTankCapacity)		:m_numberOfSeats(numberOfSeats)		,m_fuelTankCapacity(fuelTankCapacity)		,m_fuel(0)	{		if ((numberOfSeats < MIN_NUMBER_OF_SEATS) ||			(numberOfSeats > MAX_NUMBER_OF_SEATS)			)		{			throw

Слайд 42Далее
Аналогичным образом внедряются проверки и остальных входных аргументов

ДалееАналогичным образом внедряются проверки и остальных входных аргументов

Слайд 43Преимущества использования Unit-тестирования
Поощрение изменений
Облегчение рефакторинга
Легкость обнаружения ошибок
Упрощение интеграции
Устранение сомнений по

поводу надежности отдельных модулей
Документирование кода
Тесты могут служить иллюстрацией использования класса
Отделение

интерфейса классов от реализации
Позволяет уменьшить связность компонентов системы
Преимущества использования Unit-тестированияПоощрение измененийОблегчение рефакторингаЛегкость обнаружения ошибокУпрощение интеграцииУстранение сомнений по поводу надежности отдельных модулейДокументирование кодаТесты могут служить

Слайд 44Преимущества использования Unit-тестирования
Тесты постоянно управляют процессом разработки модуля, предъявляя новые

и новые формальные требования к системе
Успешное прохождение автоматическим тестов свидетельствует

о соответствии программы заданным требованиям
Написанные однажды тесты служат верой и правдой на протяжении жизненного цикла программы
На ручное тестирование приходится тратить силы постоянно
Автоматические тесты выполняются гораздо быстрее ручных
«Вкалывают роботы – счастлив человек» ©
Облегчается совместная работа нескольких человек над одними и теми же модулями
Преимущества использования Unit-тестированияТесты постоянно управляют процессом разработки модуля, предъявляя новые и новые формальные требования к системеУспешное прохождение

Слайд 45Мнимые недостатки автоматических тестов
«Написание тестов увеличивает срок разработки»
Еще больше его

увеличивает поиск ошибок вручную
Качественный код требует дополнительных временных затрат (примерно

на 50% больше)
«В процессе развития программы приходится тратить время на актуализацию ранее написанных тестов»
Не на актуализацию тестов, а на актуализацию требований к программе
«Для моих классов очень сложно написать модульные тесты»
А как Вы умудрились их так криво написать?

Мнимые недостатки автоматических тестов«Написание тестов увеличивает срок разработки»Еще больше его увеличивает поиск ошибок вручнуюКачественный код требует дополнительных

Слайд 46Напутствие
Тестирование Вашего кода - прежде всего, Ваша задача, а уж

потом – тестировщика
Не увлекайтесь чрезмерным написанием тестов
Автоматическое тестирование и TDD

– не панацея
Эффективное их использование приходит с опытом
НапутствиеТестирование Вашего кода - прежде всего, Ваша задача, а уж потом – тестировщикаНе увлекайтесь чрезмерным написанием тестовАвтоматическое

Слайд 47Как добавить модульные тесты к уже написанным классам?
Разработка новых классов

ведется в стиле TDD, а старый код пока не трогаем
В

отсутствие тестов повышается шанс незаметно сломать уже работающий код
Изменение функционала или рефакторинг старых модулей по возможности сопровождается написанием тестов
Как добавить модульные тесты к уже написанным классам?Разработка новых классов ведется в стиле TDD, а старый код

Слайд 48Что должны покрывать модульные тесты?
В идеале – всё
На практике –

критические и нетривиальные участки кода
Код, подверженный частым изменениям
Код, от которого

зависит работоспособность большого количества другого кода
Сложный код
Код с большим количеством зависимостей
Что должны покрывать модульные тесты?В идеале – всёНа практике – критические и нетривиальные участки кодаКод, подверженный частым

Слайд 49Проблемы, мешающие внедрению модульных тестов
Побочные действия кода
вывод в лог
возникновение окон

assertion’ов
запись данных в файл
вызов внешних библиотек
Жесткие зависимости тестируемого класса
Большое количество

зависимостей
Выполнение одним классом нескольких обязанностей
Проблемы, мешающие внедрению модульных тестовПобочные действия кодавывод в логвозникновение окон assertion’овзапись данных в файлвызов внешних библиотекЖесткие зависимости

Слайд 50Устранение проблем (1)
Вынесение кода, выполняющего побочные действия в отдельные классы
Отключение

записи в log и assertion-ов во время модульного тестирования
Уменьшение зависимостей

между модулями (добавление уровней косвенности)
Замена конкретного интерфейса абстрактным
Вынесение класса зависимости в параметры шаблона
Устранение проблем (1)Вынесение кода, выполняющего побочные действия в отдельные классыОтключение записи в log и assertion-ов во время

Слайд 51Устранение проблем (2)
Разбиение класса на более мелкие, каждый из которых

несет ответственность только за одну функциональность
Создание библиотек фальшивых объектов (Mock

objects) для тестирования классов с большим количеством зависимостей
Устранение проблем (2)Разбиение класса на более мелкие, каждый из которых несет ответственность только за одну функциональностьСоздание библиотек

Слайд 52Пример
Добавить возможность дозаправки самолета в воздухе, используя объект типа TankerAircraft

(самолет-заправщик)
В настоящее время дозаправка в воздухе применяется только военными самолетами,

но для нашей задачи – это не важно
ПримерДобавить возможность дозаправки самолета в воздухе, используя объект типа TankerAircraft (самолет-заправщик)В настоящее время дозаправка в воздухе применяется

Слайд 53Самолет и Заправщик
class Airplane
{
public:
...
// заполняет бак самолета топливом у самолета

заправщика
double Refuel(TankerAircraft & aircraft){...}
};
class TankerAircraft
{
public:
...
// запрашивает заданное количество топлива у

заправщика
// возвращает реально доступное, уменьшая количество топлива в заправщике
double RequestFuel(double amount){...}
};
Самолет и Заправщикclass Airplane{public:	...	// заполняет бак самолета топливом у самолета заправщика	double Refuel(TankerAircraft & aircraft){...}};class TankerAircraft{public:	...	// запрашивает заданное

Слайд 54Проблема: связи между реализациями классов
Тестирование класса Airplane затруднено из-за того,

что он оказывается связанным с конкретным классом TankerAircraft
модульное тестирование предполагает

возможность тестирования классов независимо друг от друга
Для решения этой проблемы необходимо выделить интерфейс ITankerAircraft из класса TankerAircraft и связать класс Airplane с выделенным интерфейсом

Проблема: связи между реализациями классовТестирование класса Airplane затруднено из-за того, что он оказывается связанным с конкретным классом

Слайд 55Решение: выделение интерфейса
class Airplane
{
public:
...
// заполняет бак самолета топливом у самолета

заправщика
double Refuel(ITankerAircraft & aircraft){...}
};
class TankerAircraft : public ITankerAircraft
{
public:
...
double RequestFuel(double amount) {...}
};
class

ITankerAircraft
{
public:
...
virtual double RequestFuel(double amount) = 0;
};
Решение: выделение интерфейсаclass Airplane{public:	...	// заполняет бак самолета топливом у самолета заправщика	double Refuel(ITankerAircraft & aircraft){...}};class TankerAircraft : public

Слайд 56Фальшивый заправщик
class MockTankerAircraft : public ITankerAircraft
{
public:
MockTankerAircraft(double requestResult = 0)
:requestFuelResult(0)
,requestedAmount(0)
{
}
...

// заправщик-фальшивка

возвращает заранее подготовленный результат,
// запоминая последнее запрошенное количество топлива
double RequestFuel(double

amount)
{
requestedAmount = amount;
return requestFuelResult;
}

// значения данных переменных будут задаваться и проверяться в тесте
double requestedAmount;
double requestFuelResult;
};
Фальшивый заправщикclass MockTankerAircraft : public ITankerAircraft{public:	MockTankerAircraft(double requestResult = 0)		:requestFuelResult(0)		,requestedAmount(0)	{	}	...	// заправщик-фальшивка возвращает заранее подготовленный результат,	// запоминая последнее запрошенное

Слайд 57Использование фальшивого заправщика
#include “MockTankerAircraft.h“

class AirplaneTestSuite : public CxxTest::TestSuite
{
public:
...
void TestRefuelUsingTankerAircraft()
{
const size_t

SEATS = 100;
const double TANK_CAPACITY = 1000;

// создаем самолет и

фальшивый заправщик с 800 литрами топлива
Airplane plane(SEATS, TANK_CAPACITY);
MockTankerAircraft tanker(800);

// заправляем самолет у фальшивого заправщика
plane.Refuel(tanker);

// убеждаемся, что запросили топлива на полный бак
TS_ASSERT_EQUALS(tanker.requestedAmount, TANK_CAPACITY);
// убеждаемся, что реально увеличили топливо на столько, сколько дал заправщик
TS_ASSERT_EQUALS(plane.GetFuel(), tanker.requestFuelResult);
}
};
Использование фальшивого заправщика#include “MockTankerAircraft.h“class AirplaneTestSuite : public CxxTest::TestSuite{public:	...	void TestRefuelUsingTankerAircraft()	{		const size_t SEATS = 100;		const double TANK_CAPACITY = 1000;		//

Слайд 58Закон Деметра – «Разговаривай только с близкими друзьями»
Любой метод M

объекта O может вызывать лишь методы
Объекта O
Своих параметров
Любых объектов, создаваемых

внутри метода M
Компонентов объекта O
Полностью следовать этому закону затруднительно – взвешивайте все «за» и «против»
Тем не менее, класс, удовлетворяющий данным требованиям, как правило, проще протестировать
В нем меньше связей от других классов


Закон Деметра – «Разговаривай только с близкими друзьями»Любой метод M объекта O может вызывать лишь методыОбъекта OСвоих

Слайд 59Пример не очень хорошей архитектуры
class Button
{};

class MilitaryButton : public Button
{};
class

Finger
{
public:
void Push(Button & button);
};

class Hand
{
public:
Finger & GetThumb();
};
class Human
{
public:
Hand & GetRightHand();
};

class

President : public Human
{
};

void StartTheWar()
{
President thePresident;
MilitaryButton theRedButton;

thePresident.GetRightHand().GetThumb().Push(theRedButton);
}

Пример не очень хорошей архитектурыclass Button{};class MilitaryButton : public Button{};class Finger{public:	void Push(Button & button);};class Hand{public:	Finger & GetThumb();};class

Слайд 60Улучшаем архитектуру с использованием закона Деметра
class Button
{};

class MilitaryButton : public

Button
{};
class Finger
{
public:
void Push(Button & button);
};

class Hand
{
public:
Finger & GetThumb();
};
class Human
{
public:
Hand &

GetRightHand();
void Push(Button & button)
{
Finger & thumb = GetRightHand().GetThumb();
thumb.Push(button)
}
};

class President : public Human
{
};

void StartTheWar()
{
President thePresident;
MilitaryButton theRedButton;

thePresident.Push(theRedButton);
}

Улучшаем архитектуру с использованием закона Деметраclass Button{};class MilitaryButton : public Button{};class Finger{public:	void Push(Button & button);};class Hand{public:	Finger &

Слайд 61Организационные аспекты
Код, покрытый тестами имеет большую надежность и более высокое

качество
Разработка тестов требует дополнительного времени разработки
Необходимо учитывать при оценке трудоемкости
Разработчики

должны поощряться за написание автоматических тестов и наказываться за их отсутствие
Отсутствие автоматических тестов приводит к усложнению поддержи разработанного кода, усложнению ручного тестирования и, как следствие, к увеличению затрат на проект и сроков разработки
Организационные аспектыКод, покрытый тестами имеет большую надежность и более высокое качествоРазработка тестов требует дополнительного времени разработкиНеобходимо учитывать

Слайд 62Ссылки
Движение к автоматическому тестированию
Ортогональность
Несвязность и закон Деметра
Разработка через тестирование
Юнит-тестирование
Рефакторинг
Тестирование ПО
Модульное

тестирование
Список фреймворков для модульного тестирования

СсылкиДвижение к автоматическому тестированиюОртогональностьНесвязность и закон ДеметраРазработка через тестированиеЮнит-тестированиеРефакторингТестирование ПОМодульное тестированиеСписок фреймворков для модульного тестирования

Обратная связь

Если не удалось найти и скачать доклад-презентацию, Вы можете заказать его на нашем сайте. Мы постараемся найти нужный Вам материал и отправим по электронной почте. Не стесняйтесь обращаться к нам, если у вас возникли вопросы или пожелания:

Email: Нажмите что бы посмотреть 

Что такое TheSlide.ru?

Это сайт презентации, докладов, проектов в PowerPoint. Здесь удобно  хранить и делиться своими презентациями с другими пользователями.


Для правообладателей

Яндекс.Метрика