Skip to content

Instantly share code, notes, and snippets.

@mocurin
Last active May 26, 2019 09:32
Show Gist options
  • Save mocurin/1cd6b2f0aa9f893b7998331cdc562c4b to your computer and use it in GitHub Desktop.
Save mocurin/1cd6b2f0aa9f893b7998331cdc562c4b to your computer and use it in GitHub Desktop.
lab05

Laboratory work V

Данная лабораторная работа посвещена изучению фреймворков для тестирования на примере GTest

$ open https://github.com/google/googletest

Tasks

  • 1. Создать публичный репозиторий с названием lab05 на сервисе GitHub
  • 2. Выполнить инструкцию учебного материала
  • 3. Ознакомиться со ссылками учебного материала
  • 4. Составить отчет и отправить ссылку личным сообщением в Slack

Tutorial

$ export GITHUB_USERNAME=<имя_пользователя>
$ alias gsed=sed # for *-nix system
$ cd ${GITHUB_USERNAME}/workspace
$ pushd .
$ source scripts/activate
$ git clone https://github.com/${GITHUB_USERNAME}/lab04 projects/lab05
$ cd projects/lab05
$ git remote remove origin
$ git remote add origin https://github.com/${GITHUB_USERNAME}/lab05
$ mkdir third-party
$ git submodule add https://github.com/google/googletest third-party/gtest
$ cd third-party/gtest && git checkout release-1.8.1 && cd ../..
$ git add third-party/gtest
$ git commit -m"added gtest framework"
$ gsed -i '/option(BUILD_EXAMPLES "Build examples" OFF)/a\
option(BUILD_TESTS "Build tests" OFF)
' CMakeLists.txt
$ cat >> CMakeLists.txt <<EOF

if(BUILD_TESTS)
  enable_testing()
  add_subdirectory(third-party/gtest)
  file(GLOB \${PROJECT_NAME}_TEST_SOURCES tests/*.cpp)
  add_executable(check \${\${PROJECT_NAME}_TEST_SOURCES})
  target_link_libraries(check \${PROJECT_NAME} gtest_main)
  add_test(NAME check COMMAND check)
endif()
EOF
$ mkdir tests
$ cat > tests/test1.cpp <<EOF
#include <print.hpp>

#include <gtest/gtest.h>

TEST(Print, InFileStream)
{
  std::string filepath = "file.txt";
  std::string text = "hello";
  std::ofstream out{filepath};

  print(text, out);
  out.close();

  std::string result;
  std::ifstream in{filepath};
  in >> result;

  EXPECT_EQ(result, text);
}
EOF
$ cmake -H. -B_build -DBUILD_TESTS=ON
$ cmake --build _build
$ cmake --build _build --target test
$ _build/check
$ cmake --build _build --target test -- ARGS=--verbose
$ gsed -i 's/lab04/lab05/g' README.md
$ gsed -i 's/\(DCMAKE_INSTALL_PREFIX=_install\)/\1 -DBUILD_TESTS=ON/' .travis.yml
$ gsed -i '/cmake --build _build --target install/a\
- cmake --build _build --target test -- ARGS=--verbose
' .travis.yml
$ travis lint
$ git add .travis.yml
$ git add tests
$ git add -p
$ git commit -m"added tests"
$ git push origin master
$ travis login --auto
$ travis enable
$ mkdir artifacts
$ sleep 20s && gnome-screenshot --file artifacts/screenshot.png
# for macOS: $ screencapture -T 20 artifacts/screenshot.png
# open https://github.com/${GITHUB_USERNAME}/lab05

Report

$ popd
$ export LAB_NUMBER=05
$ git clone https://github.com/tp-labs/lab${LAB_NUMBER} tasks/lab${LAB_NUMBER}
$ mkdir reports/lab${LAB_NUMBER}
$ cp tasks/lab${LAB_NUMBER}/README.md reports/lab${LAB_NUMBER}/REPORT.md
$ cd reports/lab${LAB_NUMBER}
$ edit REPORT.md
$ gistup -m "lab${LAB_NUMBER}"

Homework

Задание

  1. Создайте CMakeList.txt для библиотеки banking.
  2. Создайте модульные тесты на классы Transaction и Account.
    • Используйте mock-объекты.
    • Покрытие кода должно составлять 100%.
  3. Настройте сборочную процедуру на TravisCI.
  4. Настройте Coveralls.io.

Выполнение

  1. Первым делом создаем стандартный файл CMakeLists.txt. Для удобства дальнейшего подключения тестов расположим его в корне репозитория, а не в /banking, где он лежит изначально.
cmake_minimum_required(VERSION 3.4)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(banking)

add_library(banking STATIC banking/Account.cpp banking/Transaction.cpp)
target_include_directories(banking PUBLIC banking/)

Указываем имя проекта, какую версию симейка и какой стандарт языка нужно использовать. Затем, добавляем в нашу "библиотеку" два .cpp файла, указываем папку, где проект будет искать включаемые файлы. Чтобы собрать тесты придется исправить этот файл:

cmake_minimum_required(VERSION 3.4)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

option(BUILD_TEST "Build tests" OFF)

project(banking)

add_library(banking STATIC banking/Account.cpp banking/Transaction.cpp)
target_include_directories(banking PUBLIC banking/)

if(BUILD_TESTS)
	enable_testing()
	add_subdirectory(submodules/gtest)
	add_executable(check tests/tests.cpp)
	target_link_libraries(check banking gtest_main gmock_main)
	add_test(NAME check COMMAND check)
endif()

Теперь мы добавляем секцию if, в которую система входит при сборке если ранее заданная опция "BUILD_TESTS" поставлена в ON (по умолчанию, как видно в 6-й строке она поставлена в OFF). Это можно сделать при сборке указав -DBUILD_TESTS=ON Касаемо содержимого этой секции: сначала включается тестирование для текущего репозитория. Используется она в связке с add_test, указанной далее. Далее подключается поддиректория с библиотекой gtest, указывается файл который будет собираться и указываются библиотеки, которые будут к этому файлу подключаться. Если с banking и gtest_main все понятно, то gmock_main - библиотека, позволяющая создавать mock-объекты которые понадобятся в третьем задании. В add_test мы указываем имя собираемого файла после NAME и после COMMAND. Обычно после COMMAND нужно указать команды для командной строки, но если указать имя собираемого файла, то в конечном итоге туда подставится его положение.

  1. Модульные тесты.

В конечном итоге мой файл с тестами выглядит так tests/tests.cpp:

#include "Account.h"
#include "Transaction.h"

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class AccountMock : public Account {
public:
	AccountMock(int id, int balance) : Account(id, balance) {}
	MOCK_CONST_METHOD0(GetBalance, int());
	MOCK_METHOD1(ChangeBalance, void(int diff));
	MOCK_METHOD0(Lock, void());
	MOCK_METHOD0(Unlock, void());
};
class TransactionMock : public Transaction {
public:
	MOCK_METHOD3(Make, bool(Account& from, Account& to, int sum));
};

TEST(Account, Mock) {
	AccountMock acc(1, 100);
	EXPECT_CALL(acc, GetBalance()).Times(1);
	EXPECT_CALL(acc, ChangeBalance(testing::_)).Times(2);
	EXPECT_CALL(acc, Lock()).Times(2);
	EXPECT_CALL(acc, Unlock()).Times(1);
	acc.GetBalance();
	acc.ChangeBalance(100); // throw
	acc.Lock();
	acc.ChangeBalance(100);
	acc.Lock(); // throw
	acc.Unlock();
}

TEST(Account, SimpleTest) {
	Account acc(1, 100);
	EXPECT_EQ(acc.id(), 1);
	EXPECT_EQ(acc.GetBalance(), 100);
	EXPECT_THROW(acc.ChangeBalance(100), std::runtime_error);
	EXPECT_NO_THROW(acc.Lock());
	acc.ChangeBalance(100);
	EXPECT_EQ(acc.GetBalance(), 200);
	EXPECT_THROW(acc.Lock(), std::runtime_error);
}

TEST(Transaction, Mock) {
	TransactionMock tr;
	Account ac1(1, 50);
	Account ac2(2, 500);
	EXPECT_CALL(tr, Make(testing::_, testing::_, testing::_))
	.Times(6);
	tr.set_fee(100);
	tr.Make(ac1, ac2, 199);
	tr.Make(ac2, ac1, 500);
	tr.Make(ac2, ac1, 300);
	tr.Make(ac1, ac1, 0); // throw
	tr.Make(ac1, ac2, -1); // throw
	tr.Make(ac1, ac2, 99); // throw
}

TEST(Transaction, SimpleTest) {
	Transaction tr;
	Account ac1(1, 50);
	Account ac2(2, 500);
	tr.set_fee(100);
	EXPECT_EQ(tr.fee(), 100);
	EXPECT_THROW(tr.Make(ac1, ac1, 0), std::logic_error);
	EXPECT_THROW(tr.Make(ac1, ac2, -1), std::invalid_argument);
	EXPECT_THROW(tr.Make(ac1, ac2, 99), std::logic_error);
	EXPECT_FALSE(tr.Make(ac1, ac2, 199));
	EXPECT_FALSE(tr.Make(ac2, ac1, 500));
	EXPECT_TRUE(tr.Make(ac2, ac1, 300));
}

Сначала нужно объявить мок-объекты. Для этого необходимые функции тестриуемого класса должны быть виртуальными, что, я считаю, является главным минусом gmock. В объявлении присутствует MOCK_METHODn(name, signature) Вместо n подставляется количество аргументов функции, вместо name - имя без круглых скобок, вместо signature - сигнатура, т. е возвращаемый тип и типы аргументов в скобках. Важно заметить что наследование происходит с меткой public. Далее, с помощью EXPECT_CALL и .Times() мы задаем сколько раз будет вызываться данная функция. Если вызвана меньше - тест не пройдет. С помощью testing::_ можно указать что функций будет принимать любые аргументы тех типов, что были указаны. Если не указать сколько раз будет происходить вызов, то, согласно официальной документации gmock поведение не определено. Здесь же можно указать когда и что функция будет возвращать. И вот здесь главная загвоздка - все такие "подставные функции" возвращают только то, что для них укажут. Как в таком случае вообще проводить тестирование для меня остается загадкой. В конечном итоге есть две версии тестов - с моками и без. С моками чтобы они были, обычная что-то проверяет. С помощью них, кстати, удалось найти ошибку в файле Transaction.cpp - там в функции make в Debit передавался аккаунт того, куда отправлять. Таким образом деньги у первого просто не снимались, если операция вообще происходила.

  1. Настройка Travis-Ci

Настройка, опять же, происходит по стандартной схеме. Создаем .travis.yml и делаем указываем какими компиляторами и на какой платформе будем собирать. Я взял .travis.yml из л/р №3. В дальнейшем его придется модифицировать чтобы затем запускался coveralls

language: cpp

os:
  - linux

addons:
  apt:
    sources:
    - george-edison55-precise-backports
    packages:
    - cmake
    - cmake-data

script:
- cmake -H. -B_build -DBUILD_TESTS=ON
- cmake --build _build
- cmake --build _build --target test -- ARGS=--verbose

Стоит отметить третью строку с конца. В ней мы переключаем ту самую переменную, которую ранее указали в CMakeLists.txt, позволяя собрать тесты.

Build Status

  1. Coveralls.io

Первым делом нужно добавить файл .coveralls.yml в корень репозитория Его содержимое:

service_name: travis-pro
repo_token: wL1JVmhm7eoUktc7yct8iutKZ9e5JBRmN

О его необходимости в интернете есть очень много мнений, но от себя скажу, что а) без него ничего не запускается в coveralls, даже если правильно все указать в CMakeLists.txt и .travis.yml б) без токена так же ничего работать не будет. А указывать токен, раз репозиторий открытый, мне хотелось несильно. Далее исправим CMakeLists.txt:

option(COVERAGE "Check coverage" ON)
...
if (COVERAGE)
	target_compile_options(check PRIVATE --coverage)
	target_link_libraries(check --coverage)
endif()

Это нужно, чтобы при компиляции создавались определенные файлы, нужные для coveralls. Теперь .travis.yml:

before_install:
- pip install --user cpp-coveralls
...
after_success:
- coveralls --root . -E ".*gtest.*" -E ".*CMakeFiles.*"

Здесь мы загружаем cpp-coveralls и затем, после прохождения проверяем coverage, не включая файлы gtest и CMake с помощью флага -Е (exclude) В итоге, на сайте coveralls.io выводится покрытие кода. Вот мои результаты:

Coverage Status

Выводы

Gmock наверное, не нужен. Во многом потому что, как правило, приходится слишком много всего менять в коде.

Coveralls было очень трудно настроить. В итоге получилось, что я набрал что-то из всех найденных мною методов и оно каким-то чудом заработало. А методов, то есть материалов по coveralls, было очень и очень немного.

Links

Copyright (c) 2015-2019 The ISC Authors
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment