Темы лекции: Unit-тестирование в Qt.
Практическое задание: Unit-тестирование в Qt.
Тренер: Игорь Шкулипа, к.т.н.
С++ Библиотеки STL и Qt. Занятие 10
http://www.slideshare.net/IgorShkulipa 2
Модульное тестирование
Unit testing (юнит тестирование или модульное тестирование) —
заключается в изолированной проверке каждого отдельного элемента
путем запуска тестов в искусственной среде.
Unit (Элемент) — наименьший компонент, который можно
скомпилировать.
Драйверы — модули тестов, которые запускают тестируемый элемент.
Заглушки — заменяют недостающие компоненты, которые вызываются
элементом и выполняют следующие действия:
• возвращаются к элементу, не выполняя никаких других действий;
• отображают трассировочное сообщение и иногда предлагают
тестировщику продолжить тестирование;
• возвращают постоянное значение или предлагают тестеру самому
ввести возвращаемое значение;
• осуществляют упрощенную реализацию недостающей
компоненты;
• имитируют исключительные или аварийные условия.
http://www.slideshare.net/IgorShkulipa 3
Подходы к unit-тестированию
White-box testing. Для конструирования тестов используются
внутренняя структура кода и управляющая логика. При этом существует
вероятность, что код будет проверяться так, как он был написан, а это не
гарантирует корректность логики.
Black-box testing. Для конструирования тестов используются требования
и спецификации ПО.
Недостатки:
• таким способом невозможно найти взаимоуничтожающихся
ошибок,
• некоторые ошибки возникают достаточно редко (ошибки работы с
памятью) и потому их трудно найти и воспроизвести
http://www.slideshare.net/IgorShkulipa 4
Стратегия модульного тестирования
Модульное тестирование является одной из ключевых практик
методологии экстремального программирования:
• Написание тестов помогает войти в рабочий ритм
• Придает уверенность в работоспособности кода.
• Дает запас прочности при дальнейшей интеграции или изменениях
кода.
Модульное тестирование оправдано, если оно:
• Снижает время на отладку
• Дает возможность поиска ошибок с меньшими затратами, нежели
при других подходах
• Дает возможность дешевого поиска ошибок при изменениях кода в
дальнейшем
http://www.slideshare.net/IgorShkulipa 5
Цель модульного тестирования
Получение работоспособного кода с наименьшими затратами. И его применение
оправдано тогда и только тогда, когда оно дает больший эффект, нежели другие
методы.
Отсюда следует несколько выводов:
• Нет смысла писать тесты на весь код. Некоторые ошибки проще найти
на более поздних стадиях. Так, например, для ООП данное правило может
звучать так: нет смысла писать тесты на класс, который используется
только одним классом. Эффективней написать тесты на вызывающий класс
и создать тесты тестирующие все участки кода.
• Писать тесты для кода потенциально подверженного изменениям
более выгодно, чем для кода, изменение которого не предполагается.
Сложная логика меняется чаще, чем простая. Следовательно, в первую
очередь имеет смысл писать модульные тесты на сложную логику. А на
простую логику писать позднее или вообще тестировать другими методами.
• Для того чтобы как можно реже изменять тесты следует хорошо
планировать интерфейсы. То же самое можно сказать и применительно к
написанию исходного кода. Действительно, создание хорошей архитектуры
часто определяет дальнейший ход проекта. И есть оптимум, на каком этапе
архитектура «достаточно хороша».
http://www.slideshare.net/IgorShkulipa 6
Планирование тестов
• Код с не оттестированными участками не может быть опубликован
• Тесты должны базироваться на спецификации
• На каждое требование должен быть, как минимум, один тест. Неважно,
ручной или автоматический
• Простой тест нужен т.к. несмотря на малую вероятность нахождения
ошибки, цена пропущенной ошибки чрезмерно высока
• Наиболее эффективный способ создания тестового набора —
совместное использование методов черного и белого ящиков.
http://www.slideshare.net/IgorShkulipa 7
TDD
TDD (Test-Driven Development) - это методика разработки, позволяющая
оптимизировать использование модульных тестов. Стоит подчеркнуть,
что речь идет именно об оптимальном, а не максимальном
применении. Задача, которую преследует TDD, - достижение баланса
между усилиями и результатом.
С практической точки зрения, основой TDD является цикл
«red/green/refactor»:
• в первой фазе программист пишет тест,
• во второй - код, необходимый для того, чтобы тест работал,
• в третьей, при необходимости, производится рефакторинг.
В соответствии с принципом "Test First", следует писать только такой
код, который абсолютно необходим, чтобы тесты выполнялись
успешно.
http://www.slideshare.net/IgorShkulipa 8
Возможности Unit-тестирования в Qt
В Qt за юнит-тестирование отвечает модуль QTestLib (testlib). Он
предоставляет набор макросов для тестирования.
Есть несколько методов проведения тестов:
• Создать тестовый проект в дочерней директории тестируемого
проекта и тестировать в нем
• Тестировать методом qExec(…) в основном проекте
http://www.slideshare.net/IgorShkulipa 9
Создание модульного теста
http://www.slideshare.net/IgorShkulipa 10
Класс, который будем тестировать
class FactorialClass
{
public:
FactorialClass(){}
static double Factorial(double number)
{
if (number <0) return 0;
if (number==0) return 1;
else return number*Factorial(number-1);
}
};
http://www.slideshare.net/IgorShkulipa 11
Класс-тест
class UnitTest: public QObject
{
Q_OBJECT
public:
UnitTest()
{}
private slots:
void checkFactorial()
{
QCOMPARE(FactorialClass::Factorial(6), 720.0);
QCOMPARE(FactorialClass::Factorial(5), 120.0);
QCOMPARE(FactorialClass::Factorial(4), 24.0);
QCOMPARE(FactorialClass::Factorial(-1), 0.0);
QCOMPARE(FactorialClass::Factorial(-100), 0.0);
}
};
http://www.slideshare.net/IgorShkulipa 12
Функция main
int main(int argc, char *argv[])
{
UnitTest ut;
QTest::qExec(&ut);
return 0;
}
http://www.slideshare.net/IgorShkulipa 13
Результат
********* Start testing of UnitTest *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS : UnitTest::initTestCase()
PASS : UnitTest::checkFactorial()
PASS : UnitTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of UnitTest *********
http://www.slideshare.net/IgorShkulipa 14
Результат 2
void checkFactorial()
{
QCOMPARE(FactorialClass::Factorial(7), 720.0);
QCOMPARE(FactorialClass::Factorial(5), 120.0);
QCOMPARE(FactorialClass::Factorial(4), 24.0);
QCOMPARE(FactorialClass::Factorial(-1), 0.0);
QCOMPARE(FactorialClass::Factorial(-100), 0.0);
}
********* Start testing of UnitTest *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS : UnitTest::initTestCase()
FAIL! : UnitTest::checkFactorial() Compared doubles are not
the same (fuzzy compare)
Actual (FactorialClass::Factorial(7)): 5040
Expected (720.0): 720
debug/../../ForUnitTest/unittest.h(16) : failure location
PASS : UnitTest::cleanupTestCase()
Totals: 2 passed, 1 failed, 0 skipped
********* Finished testing of UnitTest *********
http://www.slideshare.net/IgorShkulipa 15
Результат 3
void checkFactorial()
{
QCOMPARE(FactorialClass::Factorial(6), 720.0);
QCOMPARE(FactorialClass::Factorial(5), 120.0);
QCOMPARE(FactorialClass::Factorial(4), 24.0);
QCOMPARE(FactorialClass::Factorial(-1), 0.0);
QVERIFY(FactorialClass::Factorial(-100));
}
********* Start testing of UnitTest *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS : UnitTest::initTestCase()
FAIL! : UnitTest::checkFactorial()
'FactorialClass::Factorial(-100)' returned FALSE. ()
debug/../../ForUnitTest/unittest.h(20) : failure location
PASS : UnitTest::cleanupTestCase()
Totals: 2 passed, 1 failed, 0 skipped
********* Finished testing of UnitTest *********
http://www.slideshare.net/IgorShkulipa 16
Класс QTest. Типы
enum AttributeIndex
{AI_Undefined, AI_Name, AI_Result, AI_Tests, ..., AI_Iterations }
enum KeyAction
{Press, Release, Click }
enum LogElementType
{LET_Undefined, LET_Property, LET_Properties, LET_Failure, ...,
LET_SystemError }
enum MouseAction
{MousePress, MouseRelease, MouseClick, MouseDClick, MouseMove }
enum QBenchmarkMetric
{FramesPerSecond, BitsPerSecond, BytesPerSecond,
WalltimeMilliseconds, ..., BytesAllocated }
enum TestFailMode
{Abort, Continue }
http://www.slideshare.net/IgorShkulipa 17
Класс QTest. Методы
void addColumn()
const char * benchmarkMetricName()
const char * benchmarkMetricUnit()
const char * currentDataTag()
bool currentTestFailed()
const char * currentTestFunction()
void ignoreMessage()
void keyClick()
void keyClicks()
void keyEvent()
void keyPress()
void keyRelease()
void mouseClick()
void mouseDClick()
void mouseEvent()
void mouseMove()
void mousePress()
void mouseRelease()
QTestData & newRow()
bool qCompare()
int qExec()
QString qFindTestData()
void qSleep()
void qWait()
bool qWaitForWindowActive()
void sendKeyEvent()
void setBenchmarkResult()
void simulateEvent()
char * toHexRepresentation()
char * toString()
QTouchEventSequence touchEvent()
void waitForEvents()
Ссылка на описание
http://www.slideshare.net/IgorShkulipa 18
Макросы
QBENCHMARK - используется для измерения производительности кода в тесте.
QBENCHMARK_ONCE - используется для измерения производительности кода в тесте, но
запускается только один раз.
QCOMPARE(actual, expected) - сравнивает фактическое значение с ожидаемым
значению с помощью оператора сравнения.
QEXPECT_FAIL(dataIndex, comment, mode) - отмечает следующий QCOMPARE() или
QVERIFY() в качестве ожидаемой ошибки. Вместо того печати ошибки в лог, будет
сообщение, что ожидается ошибка.
QFAIL(message) - этот макрос может использоваться, чтобы вызвать ошибки теста
QFETCH(type, name) - создает локальную переменную в стеке с именем и типом
QFINDTESTDATA(filename) - возвращает QString для TestData указанного по имени
файла, или пустой QString если файл не может быть найден.
QSKIP(description) – вызванный из тестового метода, макрос останавливает
выполнение теста без добавления ошибки в лог.
QTEST(actual, testElement) - сравнивает фактическое значение с элементом
testElement из данных теста.
QTEST_APPLESS_MAIN(TestClass) - реализует функцию main(), которая выполняет все
тесты в TestClass.
QTEST_GUILESS_MAIN(TestClass) - реализует функцию main(), которая создает объект
QCoreApplication и TestClass, и выполняет все тесты в порядке в котором они были
определены.
http://www.slideshare.net/IgorShkulipa 19
Макросы
QTEST_MAIN(TestClass) - реализует функцию main(), которая создает объект
приложения и TestClass, и выполняет все тесты в порядке в котором они были
определены.
QTRY_COMPARE(actual, expected) - выполняет сравнение фактического и ожидаемого
значений путем вызова QTRY_COMPARE_WITH_TIMEOUT () с таймаутом пять секунд.
QTRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout) - похож на QCOMPARE(),
но выполняет сравнение фактического и ожидаемого значений неоднократно, пока
либо два значения не равны или превышен тайм-аут.
QTRY_VERIFY(condition) - проверяет состояние, вызывая
QTRY_VERIFY_WITH_TIMEOUT () с таймаутом пять секунд.
QTRY_VERIFY_WITH_TIMEOUT(condition, timeout) - похож на QVERIFY(), но проверяет
условие неоднократно, пока либо оно выполнится или превышен тайм-аут.
QVERIFY2(condition, message) - ведет себя так же, как QVERIFY(), за исключением
того, он выводит сообщение, когда условие ложно.
QVERIFY(condition) - проверяет, является ли условие истинным или нет. Если
истинно, то выполнение продолжается. Если нет, то сбой записывается в логи тест
дальше не выполняется.
QWARN(message) – добавляет предупреждение в лог.
http://www.slideshare.net/IgorShkulipa 20
Лабораторная работа №10. Unit-тестирование
При необходимости, протестировать юнит-тестами Тетрис и
курсовой проект.

C++ STL & Qt. Занятие 10.

  • 1.
    Темы лекции: Unit-тестированиев Qt. Практическое задание: Unit-тестирование в Qt. Тренер: Игорь Шкулипа, к.т.н. С++ Библиотеки STL и Qt. Занятие 10
  • 2.
    http://www.slideshare.net/IgorShkulipa 2 Модульное тестирование Unittesting (юнит тестирование или модульное тестирование) — заключается в изолированной проверке каждого отдельного элемента путем запуска тестов в искусственной среде. Unit (Элемент) — наименьший компонент, который можно скомпилировать. Драйверы — модули тестов, которые запускают тестируемый элемент. Заглушки — заменяют недостающие компоненты, которые вызываются элементом и выполняют следующие действия: • возвращаются к элементу, не выполняя никаких других действий; • отображают трассировочное сообщение и иногда предлагают тестировщику продолжить тестирование; • возвращают постоянное значение или предлагают тестеру самому ввести возвращаемое значение; • осуществляют упрощенную реализацию недостающей компоненты; • имитируют исключительные или аварийные условия.
  • 3.
    http://www.slideshare.net/IgorShkulipa 3 Подходы кunit-тестированию White-box testing. Для конструирования тестов используются внутренняя структура кода и управляющая логика. При этом существует вероятность, что код будет проверяться так, как он был написан, а это не гарантирует корректность логики. Black-box testing. Для конструирования тестов используются требования и спецификации ПО. Недостатки: • таким способом невозможно найти взаимоуничтожающихся ошибок, • некоторые ошибки возникают достаточно редко (ошибки работы с памятью) и потому их трудно найти и воспроизвести
  • 4.
    http://www.slideshare.net/IgorShkulipa 4 Стратегия модульноготестирования Модульное тестирование является одной из ключевых практик методологии экстремального программирования: • Написание тестов помогает войти в рабочий ритм • Придает уверенность в работоспособности кода. • Дает запас прочности при дальнейшей интеграции или изменениях кода. Модульное тестирование оправдано, если оно: • Снижает время на отладку • Дает возможность поиска ошибок с меньшими затратами, нежели при других подходах • Дает возможность дешевого поиска ошибок при изменениях кода в дальнейшем
  • 5.
    http://www.slideshare.net/IgorShkulipa 5 Цель модульноготестирования Получение работоспособного кода с наименьшими затратами. И его применение оправдано тогда и только тогда, когда оно дает больший эффект, нежели другие методы. Отсюда следует несколько выводов: • Нет смысла писать тесты на весь код. Некоторые ошибки проще найти на более поздних стадиях. Так, например, для ООП данное правило может звучать так: нет смысла писать тесты на класс, который используется только одним классом. Эффективней написать тесты на вызывающий класс и создать тесты тестирующие все участки кода. • Писать тесты для кода потенциально подверженного изменениям более выгодно, чем для кода, изменение которого не предполагается. Сложная логика меняется чаще, чем простая. Следовательно, в первую очередь имеет смысл писать модульные тесты на сложную логику. А на простую логику писать позднее или вообще тестировать другими методами. • Для того чтобы как можно реже изменять тесты следует хорошо планировать интерфейсы. То же самое можно сказать и применительно к написанию исходного кода. Действительно, создание хорошей архитектуры часто определяет дальнейший ход проекта. И есть оптимум, на каком этапе архитектура «достаточно хороша».
  • 6.
    http://www.slideshare.net/IgorShkulipa 6 Планирование тестов •Код с не оттестированными участками не может быть опубликован • Тесты должны базироваться на спецификации • На каждое требование должен быть, как минимум, один тест. Неважно, ручной или автоматический • Простой тест нужен т.к. несмотря на малую вероятность нахождения ошибки, цена пропущенной ошибки чрезмерно высока • Наиболее эффективный способ создания тестового набора — совместное использование методов черного и белого ящиков.
  • 7.
    http://www.slideshare.net/IgorShkulipa 7 TDD TDD (Test-DrivenDevelopment) - это методика разработки, позволяющая оптимизировать использование модульных тестов. Стоит подчеркнуть, что речь идет именно об оптимальном, а не максимальном применении. Задача, которую преследует TDD, - достижение баланса между усилиями и результатом. С практической точки зрения, основой TDD является цикл «red/green/refactor»: • в первой фазе программист пишет тест, • во второй - код, необходимый для того, чтобы тест работал, • в третьей, при необходимости, производится рефакторинг. В соответствии с принципом "Test First", следует писать только такой код, который абсолютно необходим, чтобы тесты выполнялись успешно.
  • 8.
    http://www.slideshare.net/IgorShkulipa 8 Возможности Unit-тестированияв Qt В Qt за юнит-тестирование отвечает модуль QTestLib (testlib). Он предоставляет набор макросов для тестирования. Есть несколько методов проведения тестов: • Создать тестовый проект в дочерней директории тестируемого проекта и тестировать в нем • Тестировать методом qExec(…) в основном проекте
  • 9.
  • 10.
    http://www.slideshare.net/IgorShkulipa 10 Класс, которыйбудем тестировать class FactorialClass { public: FactorialClass(){} static double Factorial(double number) { if (number <0) return 0; if (number==0) return 1; else return number*Factorial(number-1); } };
  • 11.
    http://www.slideshare.net/IgorShkulipa 11 Класс-тест class UnitTest:public QObject { Q_OBJECT public: UnitTest() {} private slots: void checkFactorial() { QCOMPARE(FactorialClass::Factorial(6), 720.0); QCOMPARE(FactorialClass::Factorial(5), 120.0); QCOMPARE(FactorialClass::Factorial(4), 24.0); QCOMPARE(FactorialClass::Factorial(-1), 0.0); QCOMPARE(FactorialClass::Factorial(-100), 0.0); } };
  • 12.
    http://www.slideshare.net/IgorShkulipa 12 Функция main intmain(int argc, char *argv[]) { UnitTest ut; QTest::qExec(&ut); return 0; }
  • 13.
    http://www.slideshare.net/IgorShkulipa 13 Результат ********* Starttesting of UnitTest ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : UnitTest::initTestCase() PASS : UnitTest::checkFactorial() PASS : UnitTest::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of UnitTest *********
  • 14.
    http://www.slideshare.net/IgorShkulipa 14 Результат 2 voidcheckFactorial() { QCOMPARE(FactorialClass::Factorial(7), 720.0); QCOMPARE(FactorialClass::Factorial(5), 120.0); QCOMPARE(FactorialClass::Factorial(4), 24.0); QCOMPARE(FactorialClass::Factorial(-1), 0.0); QCOMPARE(FactorialClass::Factorial(-100), 0.0); } ********* Start testing of UnitTest ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : UnitTest::initTestCase() FAIL! : UnitTest::checkFactorial() Compared doubles are not the same (fuzzy compare) Actual (FactorialClass::Factorial(7)): 5040 Expected (720.0): 720 debug/../../ForUnitTest/unittest.h(16) : failure location PASS : UnitTest::cleanupTestCase() Totals: 2 passed, 1 failed, 0 skipped ********* Finished testing of UnitTest *********
  • 15.
    http://www.slideshare.net/IgorShkulipa 15 Результат 3 voidcheckFactorial() { QCOMPARE(FactorialClass::Factorial(6), 720.0); QCOMPARE(FactorialClass::Factorial(5), 120.0); QCOMPARE(FactorialClass::Factorial(4), 24.0); QCOMPARE(FactorialClass::Factorial(-1), 0.0); QVERIFY(FactorialClass::Factorial(-100)); } ********* Start testing of UnitTest ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : UnitTest::initTestCase() FAIL! : UnitTest::checkFactorial() 'FactorialClass::Factorial(-100)' returned FALSE. () debug/../../ForUnitTest/unittest.h(20) : failure location PASS : UnitTest::cleanupTestCase() Totals: 2 passed, 1 failed, 0 skipped ********* Finished testing of UnitTest *********
  • 16.
    http://www.slideshare.net/IgorShkulipa 16 Класс QTest.Типы enum AttributeIndex {AI_Undefined, AI_Name, AI_Result, AI_Tests, ..., AI_Iterations } enum KeyAction {Press, Release, Click } enum LogElementType {LET_Undefined, LET_Property, LET_Properties, LET_Failure, ..., LET_SystemError } enum MouseAction {MousePress, MouseRelease, MouseClick, MouseDClick, MouseMove } enum QBenchmarkMetric {FramesPerSecond, BitsPerSecond, BytesPerSecond, WalltimeMilliseconds, ..., BytesAllocated } enum TestFailMode {Abort, Continue }
  • 17.
    http://www.slideshare.net/IgorShkulipa 17 Класс QTest.Методы void addColumn() const char * benchmarkMetricName() const char * benchmarkMetricUnit() const char * currentDataTag() bool currentTestFailed() const char * currentTestFunction() void ignoreMessage() void keyClick() void keyClicks() void keyEvent() void keyPress() void keyRelease() void mouseClick() void mouseDClick() void mouseEvent() void mouseMove() void mousePress() void mouseRelease() QTestData & newRow() bool qCompare() int qExec() QString qFindTestData() void qSleep() void qWait() bool qWaitForWindowActive() void sendKeyEvent() void setBenchmarkResult() void simulateEvent() char * toHexRepresentation() char * toString() QTouchEventSequence touchEvent() void waitForEvents() Ссылка на описание
  • 18.
    http://www.slideshare.net/IgorShkulipa 18 Макросы QBENCHMARK -используется для измерения производительности кода в тесте. QBENCHMARK_ONCE - используется для измерения производительности кода в тесте, но запускается только один раз. QCOMPARE(actual, expected) - сравнивает фактическое значение с ожидаемым значению с помощью оператора сравнения. QEXPECT_FAIL(dataIndex, comment, mode) - отмечает следующий QCOMPARE() или QVERIFY() в качестве ожидаемой ошибки. Вместо того печати ошибки в лог, будет сообщение, что ожидается ошибка. QFAIL(message) - этот макрос может использоваться, чтобы вызвать ошибки теста QFETCH(type, name) - создает локальную переменную в стеке с именем и типом QFINDTESTDATA(filename) - возвращает QString для TestData указанного по имени файла, или пустой QString если файл не может быть найден. QSKIP(description) – вызванный из тестового метода, макрос останавливает выполнение теста без добавления ошибки в лог. QTEST(actual, testElement) - сравнивает фактическое значение с элементом testElement из данных теста. QTEST_APPLESS_MAIN(TestClass) - реализует функцию main(), которая выполняет все тесты в TestClass. QTEST_GUILESS_MAIN(TestClass) - реализует функцию main(), которая создает объект QCoreApplication и TestClass, и выполняет все тесты в порядке в котором они были определены.
  • 19.
    http://www.slideshare.net/IgorShkulipa 19 Макросы QTEST_MAIN(TestClass) -реализует функцию main(), которая создает объект приложения и TestClass, и выполняет все тесты в порядке в котором они были определены. QTRY_COMPARE(actual, expected) - выполняет сравнение фактического и ожидаемого значений путем вызова QTRY_COMPARE_WITH_TIMEOUT () с таймаутом пять секунд. QTRY_COMPARE_WITH_TIMEOUT(actual, expected, timeout) - похож на QCOMPARE(), но выполняет сравнение фактического и ожидаемого значений неоднократно, пока либо два значения не равны или превышен тайм-аут. QTRY_VERIFY(condition) - проверяет состояние, вызывая QTRY_VERIFY_WITH_TIMEOUT () с таймаутом пять секунд. QTRY_VERIFY_WITH_TIMEOUT(condition, timeout) - похож на QVERIFY(), но проверяет условие неоднократно, пока либо оно выполнится или превышен тайм-аут. QVERIFY2(condition, message) - ведет себя так же, как QVERIFY(), за исключением того, он выводит сообщение, когда условие ложно. QVERIFY(condition) - проверяет, является ли условие истинным или нет. Если истинно, то выполнение продолжается. Если нет, то сбой записывается в логи тест дальше не выполняется. QWARN(message) – добавляет предупреждение в лог.
  • 20.
    http://www.slideshare.net/IgorShkulipa 20 Лабораторная работа№10. Unit-тестирование При необходимости, протестировать юнит-тестами Тетрис и курсовой проект.