Всем привет. Недавно по работе возникла потребность разобраться с созданием новых тестов на GTest/GMock. Конкретно мой вопрос был связан с его конструкциями типа EXPECT_CALL и моками — что это за магические штуки и как они работают? Однако, по мере изучения выяснились и другие интересные вещи, с которыми хотел бы поделиться.
Первым делом ответы стал искать на Хабре. Здесь зачастую авторы стараются рассказывать сложные вещи простым языком. Однако по данной теме найденные публикации оказались для меня либо не сильно информативными, либо рассчитанными на очень подготовленных читателей.
Так в [1] подача материала была больше похожа на справочник, который хорошо иметь под рукой, но уже после наработки некоторого опыта с фреймворком. В [2] приведен способ установки GTest на Ubuntu 11, который, как выяснилось требует дополнительных действий. Быстрый старт в [3] оказался нереально быстрым и коротким и заточенным под Visual Studio 20082010. В [4 и 5] можно найти очень серьезные работы по юнит-тестированию и новичку там будет непросто понять идею, когда первый пример начинается с тестирования класса для работы с сетевым соединением и базой данных.
Обзор кода программиста из Microsoft — 6 вещей которые стоит перенять!
Поиск по просторам интернета привел на серию интересных видео от Deepak k Gupta по данному фреймворку на английском языке. И некоторые моменты и примеры из видео я хотел бы осветить тут.
К сожалению автор видео не стал разбираться с установкой GTest, поэтому приведу вначале тот способ установки, что мне удалось найти и апробировать. Система Ubuntu 20.04 (проверялось также и на 18-й версии). Предполагается, что компилятор С++ уже установлен.
Так как инструкция со временем может устареть, то разберем установку подробно, чтобы был понятен принцип, тогда реализацию сможете сами потом скорректировать.
Рассмотрим такой код (main.cpp), который в итоге должен показать, что фреймворк установился и запустился (почти как в test driven development):
#include #include int main(int argc, char **argv)
Для его запуска нужно выполнить из его директории команду:
g++ main.cpp -o test -lgtest -lgmock -pthread
Будет создан исполняемый файл test, при этом не должно выскочить ни единой ошибки. При запуске ./test появится сообщение:
[==========] 0 tests from 0 test suites ran. (0 ms total) [ PASSED ] 0 tests.
Посмотрим на include. Эти заголовочные файлы нужно установить. Проще всего это сделать командой:
sudo apt-get install libgtest-dev libgmock-dev # для ubuntu 20
sudo apt-get install google-mock # для ubuntu 18
После их установки в каталоге с заголовочными файлами /usr/include/ появятся папки gtest и gmock. Однако, для полноценной работы фреймворку нужна еще поддержка многопоточности. Добавим ее:
sudo apt-get install libtbb-dev
Но одних заголовочных файлов мало для запуска примера выше, нужна еще реализация того функционала, что описана в заголовках и обязательно, чтобы она была совместима с вашей системой, поэтому придется компилировать. Это уже не так страшно, как было когда-то. Для компиляции понадобится установить пакет cmake:
PHD 2018 Тестирование исходного кода Подход SAP
sudo apt-get install cmake
Когда вы установили чуть выше libgtest-dev — в вашу систему также добавились исходники googletest и googlemock, которые можно найти в директории /usr/src/googletest/.
Создаем каталог для сборки и переходим в него:
sudo mkdir build
cd build
В этом каталоге запускаем команду:
Две точки рядом с cmake означают, что нужно искать файл сценария CMakeLists.txt в родительском каталоге. Эта команда сгенерирует набор инструкций для компиляции и сборки библиотек gtest и gmock. После чего останется выполнить:
Если все пройдет успешно, то будет создан новый каталог lib, где будут находится 4 файла:
libgmock.a libgmock_main.a libgtest.a libgtest_main.a
Эти файлы содержат реализацию функционала фреймворка и их нужно скопировать в каталог к остальным библиотекам:
sudo cp lib/* /usr/lib
*Для ubuntu 18 библиотеки будут находится в ./googlemock/ и ./googlemock/gtest/
После копирования каталог build можно удалить.
Перейдем в директорию с нашим примером теста, снова запустим его:
g++ main.cpp -o test -lgtest -lgmock -pthread
./test
Теперь тест должен скомпилироваться и запуститься.
Для любителей запускать код в своей любимой IDE можно в директории с main.cpp создать файл CMakeLists.txt такого содержания:
cmake_minimum_required(VERSION 3.0) add_executable(test main.cpp) target_link_libraries(test gtest gmock pthread)
Способ №2 (чуть проще)
Спасибо за подсказку sa2304. Его отличие заключается в том, что исходники GTest устанавливаются в каталог с проектом из git репозитория googletest. Для начала нужно инициализировать пустой репозиторий в своем каталоге с файлом main.cpp:
После чего клонировать репозиторий:
git clone https://github.com/google/googletest.git
В вашем каталоге появится новая директория googletest с исходниками фреймворка. Дальше создаем файл CMakeLists.txt с таким содержимым:
cmake_minimum_required(VERSION 3.0) project(«gtest») enable_testing() add_subdirectory(googletest) add_executable(test_app main.cpp) target_link_libraries(test_app gtest_main gmock_main)
И в файле main.cpp вместо
#include #include
указываем путь к «локальным» заголовочным файлам:
#include «googletest/googlemock/include/gmock/gmock.h» #include «googletest/googletest/include/gtest/gtest.h»
Дальше собственно вернемся к видео с некоторыми моими комментариями.
Как и во всех ЯП вначале показывают т.н. «Hello world». Для GTest он может выглядеть так:
#include using namespace std; TEST(TestGroupName, Subtest_1) < ASSERT_TRUE(1 == 1); >TEST(TestGroupName, Subtest_2) < ASSERT_FALSE(‘b’ == ‘b’); cout int main(int argc, char **argv)
Здесь все интуитивно ясно. Ну почти. Фреймворк активно использует макросы. В макросе TEST первый аргумент в скобках означает название группы тестов, объединенных общей логикой.
Второй аргумент — название конкретного теста в подгруппе.
После запуска в терминале будет видно какой тест прошел успешно, какой нет:
[==========] Running 2 tests from 1 test suite. [———-] Global test environment set-up. [———-] 2 tests from TestGroupName [ RUN ] TestGroupName.Subtest_1 [ OK ] TestGroupName.Subtest_1 (0 ms) [ RUN ] TestGroupName.Subtest_2 /home/gtests/main.cpp:10: Failure Value of: ‘b’ == ‘b’ Actual: true Expected: false [ FAILED ] TestGroupName.Subtest_2 (0 ms) [———-] 2 tests from TestGroupName (0 ms total) [———-] Global test environment tear-down [==========] 2 tests from 1 test suite ran. (0 ms total) [ PASSED ] 1 test. [ FAILED ] 1 test, listed below: [ FAILED ] TestGroupName.Subtest_2 1 FAILED TEST
Дальше для упрощения буду показывать только сами тесты, без заголовков и main функции.
ASSERT_TRUE и ASSERT_FALSE — тоже макросы, реализующие т.н. «утверждения», которые будет проверять фреймворк.
- успешные (success);
- неудачные, но нефатальные (non-fatal failure);
- неудачные, фатальные (fatal failure).
Отличия второго от третьего варианта можно понять, взглянув на код теста выше. Макросы ASSERT_FALSE и ASSERT_TRUE прерывают выполнение теста (fatal failure) и идущая следом команда уже не будет вызвана.
Такое же поведение можно наблюдать у макроса ASSERT_EQ(param1, param2) сравнивающего два своих аргумента на равенство:
TEST(TestGroupName, Subtest_1) < ASSERT_EQ(1, 2); cout
По другому работает макрос EXPECT_EQ — в случае неудачи выполнение кода после него продолжится:
TEST(TestGroupName, Subtest_1) < EXPECT_EQ(1, 2); // логи покажут тут ошибку cout
Для ASSERT_ и EXPECT_ можно использовать следующие окончания:
EQ — Equal
NE — Not Equal
LT — Less Than
LE — Less than or Equal to
GT — Greater Than
GE — Greater than or Equal to
Окончаний на самом деле больше, т.к. при тестировании сравнивают не только целые числа. Для вещественных чисел, строк, предикатов примеры окончаний можно подглядеть в https://habr.com/ru/post/119090/.
Дальше автор видео рассказывает про схему юнит-теста, что каждый тест состоит из трех этапов:
- Arrange — подготовить все необходимые исходные данные для теста.
- Act — запустить проверяемый метод или функцию.
- Assert — сверить результат.
TEST(TestGroupName, increment_by_5) < // Arrange int value = 100; int increment = 5; // Act value = value + increment; // Assert ASSERT_EQ(value, 105); >
Помимо макроса TEST есть и другие. И сейчас мы с ними познакомимся.
Допустим у нас есть такой класс:
class MyClass < string id; public: MyClass(string _id) : id(_id) <>string GetId() < return id; >>;
Напишем тест, проверяющий работу конструктора и геттера:
TEST(TestGroupName, increment_by_5) < // Arrange MyClass mc(«root»); // Act string value = mc.GetId(); // Assert EXPECT_STREQ(value.c_str(), «root»); // строки сравнивают с _STREQ >
В реальной разработке одним методом у класса редко ограничиваются, поэтому для тестирования каждого метода придется раз за разом инициализировать класс, что очень неудобно. Для такого случая есть Test Fixtures.
Для удобства понимания принципа добавим в public секцию еще один метод, добавляющий строку в конец имеющейся:
void AppendToId(string postfix)
Задача: протестировать работу обоих методов, и по-возможности избежать дублирования кода. Начнем с того как будут выглядеть тесты:
TEST_F(MyClassTest, init_class) < // Act string value = mc->GetId(); // Assert EXPECT_STREQ(value.c_str(), «root»); > TEST_F(MyClassTest, append_test) < // Act mc->AppendToId(«_good»); string value = mc->GetId(); // Assert EXPECT_STREQ(value.c_str(), «root_good»); >
В обоих тестах мы не отвлекаемся на «инициализацию» класса и не беспокоимся об освобождении памяти. Более того, запуск нового теста сопровождается созданием экземпляра класса с «чистого листа».
«Инициализация» будет происходить один раз в новом (вспомогательном) классе, унаследованном от testing::Test:
struct MyClassTest : public testing::Test < MyClass *mc; void SetUp() < mc = new MyClass(«root»); >// аналог конструктора void TearDown() < delete mc; >// аналог деструктора >;
В методе SetUp() мы задаем начальные условия, в TearDown() убираем за собой.
Чтобы все заработало мы меняем TEST на TEST_F и первым аргументом указываем имя вспомогательного класса — MyClassTest. Все, можно тестировать и не отвлекаться на мелочи.
Подходим наконец к тому, с чего все началось — EXPECT_CALL и моки.
Взглянем на такую программу:
#include class Mylib < public: void setVoltage(int v) < // complex logic >>; class Myapp < Mylib *mylib_; public: explicit Myapp(Mylib *mylib) : mylib_(mylib)<>; void run(const std::string if (cmd == «ON») < mylib_->setVoltage(220); > else if (cmd == «OFF») < mylib_->setVoltage(0); > > >; int main()
Задача написать тест: если методу run передать «ON», то должен произойти вызов setVoltage(220), т.е. именно setVoltage и непременно с аргументом «220». Причем что там будет выполнено или не выполнено внутри setVoltage(220) нас не должно интересовать.
Чтобы такое осуществить нужно немного поднапрячься. Добавим интерфейс для нашей библиотеки (класса Mylib):
class MylibInterface < public: virtual ~MylibInterface() = default; virtual void setVoltage(int) = 0; >;
и унаследуемся от него:
class Mylib : public MylibInterface < public: void setVoltage(int v) < // complex logic >>;
Это даст нам возможность заменить в классе Myapp поле Mylib и тип аргумента в конструкторе на MylibInterface.
При этом заметим, что логика программы ничуть не изменилась, зато вместо конкретного класса Mylib мы можем подключить любой другой, реализующий интерфейс MylibInterface. Этим мы и воспользуемся. Создадим класс MylibMock, тоже унаследованный от MylibInterface такого содержания:
class MylibMock : public MylibInterface < public: ~MylibMock() override = default; MOCK_METHOD1(setVoltage, void(int)); >;
заодно подключим два заголовочных файла:
#include #include
Обратим внимание на макрос:
Первым аргументом идет имя того самого метода, который мы ожидаем что будет выполнен в нашем будущем тесте. Далее идет сигнатура этого метода. Цифра 1 в названии макроса означает число аргументов у метода setVoltage — один.
*В новых версиях gmock можно использовать такую запись:
MOCK_METHOD(void, setVoltage, (int v), (override));
Теперь все готово к написанию теста:
TEST(MylibTestSuite, mock_mylib_setVoltage)
Читать можно с конца теста: при запуске метода run с аргументом «ON» ожидается однократный вызов setVoltage с аргументом 220.
Чтобы запустить тест (тесты) нужно написать:
int main(int argc, char **argv)
Полный код под спойлером
#include #include #include class MylibInterface < public: virtual ~MylibInterface() = default; virtual void setVoltage(int) = 0; >; class MylibMock : public MylibInterface < public: ~MylibMock() override = default; MOCK_METHOD1(setVoltage, void(int)); >; class Mylib : public MylibInterface < public: void setVoltage(int v) < // complex logic >>; class Myapp < MylibInterface *mylib_; public: explicit Myapp(MylibInterface *mylib) : mylib_(mylib)<>; void run(const std::string if (cmd == «ON») < mylib_->setVoltage(220); > else if (cmd == «OFF») < mylib_->setVoltage(0); > > >; TEST(MylibTestSuite, mock_mylib_setVoltage) < MylibMock mylib_mock; Myapp myapp_mock( EXPECT_CALL(mylib_mock, setVoltage(220)).Times(1); myapp_mock.run(«ON»); >int main(int argc, char **argv)
На этом пока все, надеюсь было понятно и интересно. На самом деле Google C++ Testing Framework содержит много других полезных фишек, упрощающих тестирование. Буду очень рад если кто-нибудь поделится опытом применения gtest/gmock в своей практике.
Источник: habr.com
Модульное тестирование кода на C++ с помощью Qt Test. Часть 2. Расширенное тестирование
В данном руководстве рассматриваются более сложные темы модульного тестирования кода на C++ с помощью Qt Test. Обсуждается и подробно анализируется рабочий пример. Предоставляется полный проект qmake и исходный код на C++.
Больше модульного тестирования кода на C++ с помощью Qt Test
В данном руководстве я собираюсь представить более продвинутые функции Qt Test, фреймворка Qt для модульного тестирования кода на C++. В частности, мы рассмотрим, как работать с проектом с несколькими юнит-тестами, и как реализовать тестирование, управляемое данными. Также мы рассмотрим примеры других макросов для тестирования и интеграцию, предлагаемую Qt Creator.
Это второй пост из серии, посвященной Qt Test. Посты этой серии:
- Модульное тестирование кода на C++ с помощью Qt Test. Часть1. Введение
- Модульное тестирование кода на C++ с помощью Qt Test. Часть 2. Расширенное тестирование
- Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 1. Введение
- Модульное тестирование GUI (графического интерфейса пользователя) с помощью Qt Test. Часть 2. Расширенное тестирование
Чтобы понять новые концепции, представленные в данной статье, рекомендую прочитать предыдущую статью этой серии про модульное тестирование кода на C++ с помощью Qt Test.
Создание улучшенного проекта
Как было показано в первом руководстве, философия Qt Test заключается в том, что каждый тестовый случай является независимым исполняемым файлом. В реальных проектах у вас обычно есть сотни или даже тысячи различных юнит-тестов, и запускать их все вручную определенно не лучший вариант. Лучший способ запускать их и управлять ими – это создать «родительский» проект. Правильный выбор в этом случае – шаблон « Проект с поддиректориями » ( Subdirs Project ), который находится в группе « Другой проект » ( Other Project ) диалогового окна « Новый проект » ( New Project ).
После создания проекта вы вернетесь в диалоговое окно выбора шаблона, чтобы создать первый проект в «родительском» проекте. Вы можете отказаться от этого и перейти к добавлению существующих проектов. В итоге вы получите что-то вроде этого:
Для этого руководства я расширил проект юнит-тестов TestCalculator и создал новый под названием TestIntBitset . В новом проекте тестируется упрощенная реализация битового набора. Повторюсь еще раз, код для тестирования (класс IntBitset ) включен в проект модульного тестирования для упрощения.
Тестирование на основе данных
Продвинутая функция Qt Test – это тестирование на основе данных. Идея состоит в том, чтобы разделить тесты и данные, чтобы избежать длинного списка похожих макросов QVERIFY или QCOMPARE и повторения всего кода, необходимого для инициализации теста.
Чтобы предоставить данные тестовой функции, вы должны создать еще один частный слот с таким же именем функции, но с дополнительным суффиксом » _data «. Например, функцией данных для testDiff() будет testDiff_data() .
Реализация функции данных немного похожа на вставку данных в базу данных. Сначала вы определяете данные, как если бы вы определяли таблицу:
void TestCalculator::testDiff_data() < QTest::addColumn(«a»); QTest::addColumn(«b»); QTest::addColumn(«result»);
Затем добавляете строки значений:
QTest::newRow(«all 0»)
Каждая строка содержит имя и список значений. Вы можете представить, что приведенный выше код преобразован во что-то вроде следующей таблицы:
“all 0” | ||||
1 | “same number” | 10 | 10 |
После того как мы определили функцию данных, мы можем написать тестовую функцию, которая разделена на 2 части.
Первая часть извлекает данные:
void TestCalculator::testDiff() < // извлечение данных QFETCH(int, a); QFETCH(int, b); QFETCH(int, result); // установка значений mCalc.SetA(a); mCalc.SetB(b);
Вторая часть использует эти данные для выполнения проверок:
// тест QCOMPARE(mCalc.Diff(), result); >
Без подхода, основанного на данных, нам пришлось бы многократно повторять инструкции по установке значений и проверке QCOMPARE .
Когда выполняется тест, управляемый данными, функция тестирования вызывается один раз для каждого набора данных, и сообщения журнала выглядят следующим образом:
PASS : TestCalculator::testDiff(all 0) PASS : TestCalculator::testDiff(same number) . еще строки .
Как вы можете заметить, чтобы помочь вам различать случаи, в журнал заносится имя строки данных.
Другие полезные макросы
Qt Test предлагает несколько дополнительных макросов, которые помогут вам справиться с различными ситуациями в ваших юнит-тестах.
Провал теста
Один из этих макросов – QFAIL делает текущий тест проваленным. Его можно использовать, когда вы знаете, что что-то приведет к провалу теста. В этом случае нет смысла тратить время на выполнение теста, вы можете просто провалить его и двигаться дальше.
void TestIntBitset::initTestCase()
В примере проекта я использовал QFAIL в функциях initTestCase и cleanupTestCase , которые представляют собой специальные функции, выполняемые до и после выполнения тестовых функций. Когда initTestCase завершается провалом, в тестовом случае не выполняется ни один из тестов.
Провал одной проверки
Если вы знаете, что конкретный QVERIFY или QCOMPARE завершится провалом, но вы всё равно хотите продолжить выполнение теста, вы можете выполнить проверку с помощью макроса QEXPECT_FAIL :
void TestIntBitset::testSetOff() < mBS.setAllOn(); unsigned int bitsOff = 0; mBS.setBitOff(BITS_IN_BYTE * bitsOff++); mBS.setBitOff(BITS_IN_BYTE * bitsOff++); QEXPECT_FAIL(«», «isAnyOff not implemented yet», Continue); QVERIFY(mBS.isAnyOff()); // . еще тестовый код . >
Первый параметр этого макроса определяет строку данных при выполнении тестирования на основе данных, но при обычном тестировании он может быть установлен на пустую строку. Второй параметр – это сообщение журнала, а третий позволяет вам решить, хотите ли вы продолжить ( Continue ) или прервать ( Abort ) тест в случае сбоя.
При запуске показанного выше теста в выходном журнале будет отображаться что-то вроде этого:
XFAIL : TestIntBitset::testSetOff() isAnyOff not implemented yet Loc: [../../UnitTests/TestIntBitset/TestIntBitset.cpp(67)]
Пропуск теста
Если вы хотите пропустить тест или его часть, вы можете использовать макрос QSKIP , который пометит тест как пропущенный и остановит выполнение:
void TestIntBitset::testOperators()
Никакой код после QSKIP выполняться не будет, но код перед ним – будет, поэтому, если какая-либо проверка завершится неудачей, тест будет считаться проваленным.
При выполнении пропущенного теста в журнале будет отображаться следующий текст:
SKIP : TestIntBitset::testOperators() Operators have not been implemented yet. Loc: [../../UnitTests/TestIntBitset/TestIntBitset.cpp(28)]
Решение об использовании QFAIL и QSKIP иногда может быть спорным. Для этого нет точного правила, и всё зависит от ваших задач проектирования. Лично я предпочитаю использовать QFAIL , когда заранее знаю, что что-то завершится неудачей, и хочу это выделить, а QSKIP , когда не имеет значения, выполнится ли весь тест или его часть.
Предупреждающие сообщения
Если вы хотите напечатать предупреждающее сообщение в журнале тестов, вы можете использовать QWARN . Этот макрос может быть полезен, когда вы хотите уведомить, что что-то в тесте идет не так, как ожидалось.
void TestIntBitset::testSetOff() < mBS.setAllOn(); unsigned int bitsOff = 0; mBS.setBitOff(BITS_IN_BYTE * bitsOff++); // . еще тестовый код . // этот тест будет вызывать предупреждение if((BITS_IN_BYTE * bitsOff) < BITS_IN_INT) QVERIFY(!mBS.isBitOff(BITS_IN_BYTE * bitsOff)); else QWARN(«trying to verify bit out of set bounds»); // . еще тестовый код . >
В этом случае проверка QVERIFY завершится неудачей, поскольку входные данные неверны. Было бы нечестно провалить тест из-за возможной ошибки в локальном коде, но эту ситуацию необходимо выделить. Предупреждение – хороший способ добиться этого.
При запуске теста, содержащего предупреждение, в выходном журнале будет отображаться что-то вроде этого:
WARNING: TestIntBitset::testSetOff() trying to verify value out of set bounds Loc: [../../UnitTests/TestIntBitset/TestIntBitset.cpp(75)]
В предупреждающем сообщении всегда будет само сообщение и то, где было выдано предупреждение.
Интеграция с Qt Creator
Неудивительно, что Qt Creator предлагает отличную интеграцию с Qt Test.
Одна из панелей слева называется « Тесты » ( Tests ) и отображает все юнит-тесты, найденные в вашем «родительском» проекте.
С помощью этой панели вы можете отключить некоторые тесты, запустить их все или запустить только определенный тест. При проведении тестирования на основе данных вы также можете выбрать, какие наборы данных будут включены. Всё это чрезвычайно полезно в реальном проекте, где у вас могут быть сотни или даже тысячи юнит-тестов, и вам нужно проверить/отладить только один/несколько.
При запуске модульного теста с панели « Тесты » результаты отображаются на панели « Результаты тестирования » ( Test Results ), которую также можно открыть с помощью комбинации клавиш Alt + 8 .
Эта панель ясно покажет, какие тесты пройдены, а какие нет, а также покажет другую полезную информацию, особенно в случае сбоя. А именно нажмите на результаты, чтобы перейти к любой ошибке или предупреждению в коде. Кроме того, эта панель позволяет вам фильтровать события, которые вы хотите видеть в журнале, например, вы можете проверять только ошибки или предупреждения.
Комбинация этих двух панелей является отличным дополнением к Qt Creator и Qt Test и предлагает очень мощный инструмент.
Исходный код
Полный исходный код этого руководства доступен на GitHub.
Там вы найдете 3 проекта qmake, но для сборки вам нужно загрузить только первый уровень ( UnitTests.pro ).
Справочная информация
Чтобы узнать больше о Qt Test, вы можете ознакомиться с последней документацией по пространству имен QTest.
Заключение
Функции, обсуждаемые в данном руководстве, делают Qt Test более удобным и полным фреймворком, чем тот, что был представлен в первой части этой серии. В частности, интеграция с Qt Creator чрезвычайно полезна и эффективна. Тестирование, управляемое данными, также может быть очень мощным средством для сокращения кода тестирования и очень простого тестирования различных тестовых случаев.
Всё станет еще интереснее, когда в следующей статье я расскажу о тестировании графического интерфейса, так что следите за новостями.
Теги
Сохранить или поделиться
На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.
В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.
Источник: radioprog.ru
Автоматизация тестирования UI с помощью SikuliX
В статье рассмотрен один из подходов к автоматизации тестирования пользовательского интерфейса. Он основан на использования инструмента SikuliX , языке Python и framework’ е для тестирования: unittest .
- Постановка задачи
- Что такое SikuliX ?
- Развертывание SikuliX
- Установка Java
- Загрузка SikuliX
- Установка SikuliX
Постановка задачи
Лучше всего мы учимся и узнаем что-то новое, когда занимаемся конкретной проблемой. Поэтому поставим для себя следующую задачу: протестировать работу штатного калькулятора MS Windows (реализация выполнена для Windows 10) в части:
- выполнение простых арифметических действий (сложение, вычитание, умножение и деление);
- извлечение квадратного корня;
- вычисление числа обратного данному.
В качестве инструмента для решения задачи выберем SikuliX .
Что такое SikuliX?
SikuliX – это инструмент для автоматизации действий пользователя, которые он может выполнить при работе с графическим интерфейсом. На сегодняшний день, данный продукт работает в Windows , MacOS , Linux , таким образом, он позволяет протестировать большую часть современных приложений с графическим интерфейсом. SikuliX использует технологию распознавания образов на базе OpenCV для идентификации и управления элементами пользовательского интерфейса.
SikuliX поддерживает следующие языки и технологии, базирующиеся на платформе Java : Python (на базе Jython ), RobotFramework , Ruby 1.9 и 2.0 (на базе JRuby ), JavaScript (на базе Java Scripting Engine ), Scala и т.п. Мобильные платформы пока не поддерживаются, но можно использовать эмулятор для тестирования приложения.
Развертывание SikuliX
Рассмотрим процесс установки SikuliX на операционную систему Windows .
Установка Java
Для начала нужно убедиться в том, что у вас установлена версия исполняемой среды Java 8 (у автора не получилось установить SikuliX с Java 10). Вы можете скачать дистрибутив Java и установить ее с сайта Oracle.
Для того, чтобы узнать версию Java , которая установлена на вашем компьютере, откройте консоль (нажмите Ctrl+R и введите в окне cmd , либо найдите программу cmd.exe и запустите ее) и выполните в ней команду java -version .
Загрузка SikuliX
Вторым шагом является загрузка дистрибутива SikuliX , для этого перейдите на соответствующую страницу .
Скачайте последний доступный релиз SikuliX .
Установка SikuliX
Создайте папку, в которую вы хотите установить программу, например C:SikuliX . Далее, скопируйте туда, скачанный файл sikulixsetup-x.x.x.jar . Введите в строке адреса окна команду cmd .
В результате откроется окно командной строки, в котором указанный нами каталог будет текущим.
Для запуска установки введите команду:
java -jar sikulixsetup-1.1.2.jar
Спустя какое время после запуска будет представлено диалоговое окно, в котором, для продолжения установки нужно будет нажать “Yes” .
Далее необходимо выбрать одну из опций.
Pack 1 – будут установлены SikuliX IDE и поддержка скриптового языка Python ( Jython ) и/или Ruby ( JRuby ).
Pack 2 – будут установлены библиотеки для доступа к функционалу SikuliX из Java , SikuliX IDE установлена не будет.
После выбора опции нажмите на кнопку “ Setup Now ”. Далее вам будет представлена сводная информация по настройкам установки, если согласны, то нажмите “ Yes ”.
Запустится процесс установки.
Если вы выбрали опцию Pack 1, то вам будет предложено установить Jython версии 2.7.0, согласитесь с предложенным вариантом (если у вас нет причин отказаться).
После того, как SikuliX будет установлена со всеми необходимыми компонентами, вы увидите следующее окно.
И сообщение в консоли.
Краткий обзор возможностей SikuliX
SikuliX позволяет находить на дисплее графические элементы и использовать их в качестве элементов управления. Например, если вы хотите нажать на кнопку ПУСК в Windows , необходимо в исходный код программы добавить функцию click (), в которую в качестве аргументов передается картинка кнопки ПУСК.
Ниже приведены функции, которые наиболее часто используются при работе с SikuliX .
Функции поиска элементов на экране.
wait(picture [, timeout])
Ожидание появления на экране картинки picture , время ожидания определяется timeout ’ом. Если объект не появится, то будет вызвано исключение.
waitVanish(picture [, timeout])
Ожидание исчезновения с экрана картинки picture , время ожидания определяется timeout ’ом. Если объект не исчезнет, то будет вызвано исключение.
exists(picture[, timeout])
Ожидание появления на экране картинки picture , время ожидания определяется timeout ’ом. Если объект не появится, то будет возвращено значение None . Эту функцию удобно использовать в условных выражениях.
Эмуляция работы с мышью.
click(picture)
Выполняет нажатие левой кнопкой мыши на элементе picture .
doubleClick(picture)
Выполняет двойное нажатие левой кнопкой мыши на элементе picture .
rightClick(picture)
Выполняет нажатие правой кнопкой мыши на элементе picture .
hover(picture)
Выполняет наведение указателя мыши на элемент picture .
Эмуляция нажатий клавиш на клавиатуре.
type([picture, ]text[, modifiers])
Вводит в элемент picture текст – text . Если необходимо, то используются модификаторы – это нажатия клавиш Shift , Ctrl , Alt и т.п. Например, если необходимо нажать клавишу “1” с зажатой Alt, то решение будет выглядеть так type(“1”, KEY_ALT) .
Ниже представлен простой пример, демонстрирующий работу SikuliX .
Простой пример использования SikuliX
Создадим простой пример, демонстрирующий одну из возможностей SikuliX . Задача будет состоять в том, что нужно вызвать окно “Выполнить”, ввести в нем “ cmd ”, нажать Enter и через появившееся окно консоли запустить Notepad .
1. Запустите SikuliX , для этого зайдите в каталог с установленной программой и запустите файл runsikulix.cmd . В результате загрузится оболочка SikuliX IDE .
2. Создайте новый проект, для этого на панели меню выберите File->New .
3. Сохраните созданный проект (мы назовем его prj).
4. Введите исходный код программы.
Особенность SikuliX состоит в том, что она может работать с графическими элементами пользовательского интерфейса, SikuliX IDE предоставляет инструменты для помещения этих элементов в исходный код программы и работы с ними.
Код программы выглядит так:
Как вы может видеть, аргументом функции click () является картинка, для того, чтобы ее туда поместить, установите курсор в нужное место программы, нажмите на кнопку “ Take screenshot ”, выделите на экране нужную область, и она будет помещена в редактор – в нашем случае как аргумент функции.
В текстовом виде этот код выглядит так:
click(«1534932430966.png») sleep(1) type(«cmdn») sleep(1) type(«notepadn») sleep(1) type(«Hello! From SikuliX»)
Запустите программу, для этого нажмите на кнопку “ Run ”. Когда программа будет работать – не двигайте мышкой!
Разработка приложения выполняющего автоматическое тестирование интерфейса калькулятора Windows
Для построения тестирующего приложения будем использовать framework для тестирования unittest . Исходный код проекта вы можете загрузить из репозитория, содержимое которого необходимо поместить в папку с установленным SikuliX. Часть исходного кода, размещенного в статье, будет представлена в картинках для большей наглядности.
Framework unittest предоставляет функционал, который значительно упрощает процесс создания и запуска тестов, более подробно можете прочитать здесь.
Создадим два класса BaseActionsTests и ExtendsActionsTests в CalcSimple . sikuli .
Класс BaseActionsTests отвечает за тестирование выполнения базовых операций: сложение, вычитание, умножение и деление. Добавим в этот класс следующие методы, используемые при запуске тестов.
setUpClass () – метод действует на уровне класса BaseActionsTests , запускается один раз перед запуском тестов класс а, в нем мы проверяем, запущен ли калькулятор, переводим его в режим “ Standart ”. Если он не запущен, то запускаем его.
tearDownClass () – метод действует на уровне класса BaseActionsTests , запускается один раз после отработки всех тестов класса , в нем мы ничего не делаем.
setUp () – метод запускается каждый раз перед запуском любого теста , в нем мы тоже ничего не делаем.
tearDown () – метод запускается каждый раз после работы любого теста , в нем будем производить очистку экрана калькулятор, для этого нужно имитировать нажатие кнопки Esc .
Методы непосредственно тестирующие функционал калькулятора имеют следующие имена:
test_add () – тестирование операции “сложение”.
test_sub () – тестирование операции “вычитание”.
test_mul () – тестирование операции “умножение”.
test_div () – тестирование операции “деление”.
Внутренняя архитектура этих методов схожа и выполнена по следующему принципу: последовательно нажимаем на определенные кнопки, например: “1”, “+”, “4”, “=” и смотрим, совпадает ли результат с тем, что мы ожидаем.
Ниже представлен код метода test_add ().
Класс ExtendsActionsTests отвечает за тестирование выполнения дополнительных операций: извлечение квадратного корня и операция вычисления обратного элемента. Содержимое методов setUpClass (), tearDownClass (), setUp () аналогично тому, что представлено в классе BaseActionsTests .
Для тестирования функционала в классе ExtendsActionsTests используются следующие методы:
test_sqrt () – тестирование операции “извлечение квадратного корня”.
test_one_div_x () – тестирование операции “вычисление обратного элемента”.
Ниже представлен скриншот IDE с введенным кодом программы.
Создадим проект mainTest . sikuli в той же папке, в которой находится CalcSimple . sikuli . Исходный код проекта mainTest приведен ниже.
import unittest import CalcSimple import StringIO import sys import HTMLTestRunner reload(CalcSimple) # Create list of test cases testList = [] testList.append(CalcSimple.BaseActionsTests) testList.append(CalcSimple.ExtendsActionsTests) testLoad = unittest.TestLoader() # Assemble test cases into suite list caseList = [] for testCase in testList: testSuite = testLoad.loadTestsFromTestCase(testCase) caseList.append(testSuite) # Create TestSuite suite = unittest.TestSuite(caseList) # Run tests if len(sys.argv) > 1 and sys.argv[1] == «html»: runner = unittest.TextTestRunner(verbosity=1) filename = «Test1.html» output = open (filename,»wb») runner = HTMLTestRunner.HTMLTestRunner( stream=output, title=»Test report» ) runner.run(suite) else: unittest.TextTestRunner(verbosity=2).run(suite)
Кратко рассмотрим содержимое файла.
1. Импортируем необходимые библиотеки.
import unittest import CalcSimple import StringIO import sys import HTMLTestRunner
2. Загружаем модуль CalcSimple.
reload(CalcSimple)
3. Создаем список тестов и заворачиваем их в TestSuite.
# Assemble test cases into suite list caseList = [] for testCase in testList: testSuite = testLoad.loadTestsFromTestCase(testCase) caseList.append(testSuite) # Create TestSuite suite = unittest.TestSuite(caseList)
4. Запускаем тесты.
# Run tests if len(sys.argv) > 1 and sys.argv[1] == «html»: runner = unittest.TextTestRunner(verbosity=1) filename = «Test1.html» output = open (filename,»wb») runner = HTMLTestRunner.HTMLTestRunner( stream=output, title=»Test report» ) runner.run(suite) else: unittest.TextTestRunner(verbosity=2).run(suite)
Для того, чтобы получить результат тестирования калькулятора в удобочитаемом виде будем использовать HTMLTestRunner .
Для запуска тестирования создайте bat файл со следующим содержимым.
java -Dsikuli.Debug=-2 -jar sikulix.jar -r projectscalcTestmainTest.sikuli —args html
и поместите этот файл в каталог, рядом с каталогом projects.
Если запустить этот файл, то после прохождения тестов будет создан файл Test1.html , содержимое которого (если все тесты прошли успешно) будет примерно таким:
Автоматизация тестирования UI с помощью SikuliX : 4 комментария
- Андрей 08.02.2019 Ребят, спасибо за то, что создали этот ресурс)
Написано по-человечески – мне не нужно расшифровывать кучу терминов и сленга. И темы те ,что нужнs мне в работе и без лишней воды.
Приобрел книжку по pandas, буду рад, если сделаете уроки по ML на Python.
- writer 09.02.2019 Добрый день!
Спасибо за отзыв! Про ML мы раздумываем))
- writer 05.11.2020 Добрый день!
А что конкретно у вас не получается скачать?
Источник: devpractice.ru