1 Введение
1.1 Знакомство с Google Mock
Google Mock — это среда тестирования для C++, разработанная Google. Она является частью среды тестирования Google Test. gmock позволяет разработчикам создавать макеты объектов, которые могут заменить реальные зависимости в модульных тестах, делая тестирование более гибким и независимым. Используя gmock, разработчики могут сосредоточиться на тестировании корректности логики кода, не беспокоясь о сложности внешних зависимостей.
1.2Почему стоит выбрать Google Mock
Среди множества сред тестирования C++ gmock выделяется своими мощными функциями и простотой использования. Вот некоторые из основных причин выбрать gmock:
Гибкость: gmock поддерживает настраиваемое поведение моделирования и может моделировать сложные зависимости.
lПростота использования: дизайн API gmock прост и интуитивно понятен, его легко освоить и использовать.
lПоддержка сообщества. Будучи продуктом Google, gmock имеет активное сообщество и богатые ресурсы документации.
lИнтеграция: gmock можно легко интегрировать с Google Test, чтобы обеспечить универсальное решение для тестирования.
2 Основы Google Mock
2.1 Важность тестирования
Прежде чем мы углубимся в Google Mock, мы должны сначала осознать важность тестирования при разработке программного обеспечения. Тестирование — ключевая часть обеспечения качества программного обеспечения, помогающая нам находить и исправлять потенциальные ошибки и дефекты. Модульное тестирование — это самая базовая форма тестирования, которая позволяет нам независимо тестировать различные части кода.
2.2 Установка и настройка Google Mock
Прежде чем мы начнем использовать Google Mock, нам необходимо сначала установить и настроить его. Вот основные шаги по установке Google Mock:
1. Получите Google Test. Поскольку Google Mock является частью Google Test, нам необходимо сначала получить исходный код Google Test.
2. Скомпилируйте Google Test. Скомпилируйте библиотеку Google Test в соответствии с вашей операционной системой и компилятором.
3. Настройте проект. Настройте путь к заголовочному файлу и пути к библиотеке Google Test и Google Mock в своем проекте.
2.3 Структура тестовых случаев
Типичный тестовый пример обычно включает в себя следующие части:
lТестовая сборка: установите среду и условия, необходимые для тестирования.
lВыполнить тест: запустить тестируемый код.
l Утверждение: проверьте, соответствует ли вывод кода ожидаемому.
lОчистка: очистка окружающей среды после завершения теста.
#include <gtest/gtest.h>
class MyMathTest : public ::testing::Test {
protected:
void SetUp() override {
// тестовая сборка
}
void TearDown() override {
// убирать
}
};
TEST_F(MyMathTest, TestAddition) {
// Выполнять
int result = 3 + 2;
// утверждение
EXPECT_EQ(5, result);
}
2.4 Шаги по написанию тестовых примеров
Написание тестовых случаев обычно следует следующим шагам:
1. Определите цель тестирования: уточните часть кода или функцию, которую вы хотите протестировать.
2. Напишите тестовый код. Используйте макросы и утверждения Google Test для написания тестовой логики.
3. Запустите тест. Скомпилируйте и запустите тест, чтобы проверить, соответствуют ли результаты ожидаемым.
4. Анализ и корректировка: корректируйте тестовые примеры или тестируемый код на основе результатов тестирования.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class NetworkService {
virtual std::string fetchData(){return "";}
};
class MockNetworkService : public NetworkService {
public:
MOCK_METHOD(std::string, fetchData, (), (override));
};
TEST(NetworkServiceTest, FetchDataTest) {
MockNetworkService mockService;
EXPECT_CALL(mockService, fetchData())
.WillOnce(testing::Return("Mocked Data"));
std::string result = mockService.fetchData();
EXPECT_EQ("Mocked Data", result);
}
3 Создайте тестовые примеры
3.1 Важность тестовых случаев
Тестовые сценарии являются основой модульного тестирования. Они определяют входные данные, процесс выполнения и ожидаемые результаты теста. Написание высококачественных тестовых примеров гарантирует, что ваш код останется стабильным и надежным при его изменении и расширении.
3.2 Основные принципы тестовых случаев
При написании тест-кейсов следует следовать следующим основным принципам:
l Независимость: каждый тестовый пример должен выполняться независимо от других тестовых примеров и не должен зависеть от внешнего состояния.
l Повторяемость. Вы должны получать одни и те же результаты независимо от того, когда и где вы запускаете тестовые примеры.
lАвтоматизация: тестовые примеры должны выполняться автоматически, чтобы уменьшить количество ручного вмешательства.
l Всеобъемлющий охват. Тестовые примеры должны охватывать все важные функциональные точки и граничные условия.
3.3 Структура тестовых случаев
Полный тестовый пример обычно включает в себя следующие части:
lПредварительные условия: условия, которые необходимо выполнить перед началом теста.
lВходные данные: входные данные для тестового примера.
lШаги выполнения: конкретные шаги для выполнения теста.
lОжидаемые результаты: ожидаемые результаты после завершения теста.
lЛогика проверки: логика проверки соответствия фактических результатов ожидаемым.
3.4 Пример: простой тестовый пример сложения
#include <gtest/gtest.h>
class Adder {
public:
int add(int a, int b) {
return a + b;
}
};
TEST(AdderTest, HandlesPositiveNumbers) {
Adder adder;
EXPECT_EQ(5, adder.add(2, 3));
}
TEST(AdderTest, HandlesNegativeNumbers) {
Adder adder;
EXPECT_EQ(-1, adder.add(-2, 1));
}
TEST(AdderTest, HandlesZero) {
Adder adder;
EXPECT_EQ(0, adder.add(0, 0));
}
3.5 Параметризация тестовых случаев
#include <gtest/gtest.h>
#include <tuple>
class Adder {
public:
int add(int a, int b) {
return a + b;
}
};
class AdderTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(AdderTest, AddReturnsExpectedResult) {
Adder adder;
int a, b, expected;
std::tie(a, b, expected) = GetParam();
EXPECT_EQ(expected, adder.add(a, b));
}
INSTANTIATE_TEST_CASE_P(AdderTests, AdderTest,
::testing::Values(std::make_tuple(2, 3, 5),
std::make_tuple(-2, 1, -1),
std::make_tuple(0, 0, 0)));
3.6 Организация тест-кейсов
В больших проектах очень важно правильно организовать тест-кейсы. Обычно тестовые примеры должны быть организованы в соответствии с тестируемым классом или модулем. Использование соглашений об именах и структур каталогов может помочь поддерживать и находить тестовые примеры.
3.7 Пример: тестирование сложной логики
Давайте рассмотрим более сложный пример, тестируя простой класс банковского счета:
#include <gtest/gtest.h>
class BankAccount {
public:
BankAccount(int balance) : balance_(balance) {}
void deposit(int amount) { balance_ += amount; }
int getBalance() const { return balance_; }
private:
int balance_;
};
TEST(BankAccountTest, DepositIncreasesBalance) {
BankAccount account(100);
account.deposit(50);
EXPECT_EQ(150, account.getBalance());
}
TEST(BankAccountTest, DepositWithNegativeAmountThrows) {
BankAccount account(100);
account.deposit(-50);
EXPECT_EQ(50, account.getBalance());
}
4. Основные понятия Mocking
4.1 Что такое насмешка?
Mocking — это метод тестирования, который позволяет тестировщикам моделировать (издеваться) над поведением объекта или интерфейса, чтобы изолировать тестируемый код во время тестирования. Мок-объекты часто используются для замены реальных зависимостей, чтобы тесты могли выполняться независимо от внешних систем или компонентов.
4.2 Разница между Mocking и Stub
lMock: обычно используется для проверки правильности вызова зависимостей тестируемым кодом, включая количество вызовов, параметры, порядок вызова и т. д.
lStub: возвращает заранее определенные данные ответа, которые в основном используются для проверки логики кода, а не для проверки правильности вызова.
4.3 Зачем использовать Mocking?
lИзоляция: Mocking позволяет запускать тесты независимо от внешних систем, повышая стабильность и надежность тестов.
lГибкость: можно моделировать различные сложные ситуации, включая ошибки, исключения, задержки и т. д.
lЭффективность: позволяет избежать взаимодействия с внешними системами и ускоряет выполнение тестов.
4.4. Издевательство с помощью Google Mock
Google Mock предоставляет богатый набор API для создания и настройки объектов Mock. Вот основные шаги для создания макетов с помощью Google Mock:
1. Определите макетный интерфейс. Определите макетную версию в соответствии с классом или интерфейсом, который необходимо имитировать.
2. Используйте макрос MOCK_METHOD: определите метод Mock в интерфейсе Mock.
3. Установите ожидания: используйте EXPECT_CALL, чтобы установить ожидаемое поведение Mock-объекта.
4. Проверьте вызов. В конце теста Google Mock автоматически проверит, соответствует ли вызов объекта Mock ожиданиям.
4.5 Пример: имитация простой зависимости
#include <gtest/gtest.h>
class Database {
public:
virtual ~Database() {}
virtual std::string query(const std::string& sql) const{return sql;}
};
class MockDatabase:public Database {
public:
MOCK_METHOD(std::string, query, (const std::string& sql), (const, override));
};
class DataProcessor {
public:
explicit DataProcessor(Database* db) : db_(db) {}
std::string process(const std::string& data) {
return data + " processed with " + db_->query("SELECT * FROM table");
}
private:
Database* db_;
};
TEST(DataProcessorTest, ProcessesDataWithDatabaseQuery) {
MockDatabase mockDb;
DataProcessor processor(&mockDb);
EXPECT_CALL(mockDb, query("SELECT * FROM table"))
.WillOnce(testing::Return("Mocked query result"));
std::string result = processor.process("Data");
EXPECT_EQ("Data processed with Mocked query result", result);
}
4.6 Продвинутые методы издевательств
lПоследовательные вызовы: используйте InSequence EXPECT_CALL для имитации последовательности вызовов методов.
l Любое количество вызовов. Используйте Times(), чтобы указать диапазон раз, когда метод может быть вызван.
lОбъединить Mock и Stub: используйте поведение Mock и Stub одновременно в одном Mock-объекте.
4.6.1 Использование Mock для проверки последовательности вызова метода
1) Ожидайте всех последовательных вызовов
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::InSequence;
class MyClass {
public:
virtual bool firstMethod(int){return true;}
virtual bool secondMethod(int){return true;}
};
class MockMyClass {
public:
MOCK_METHOD(bool, firstMethod,(int));
MOCK_METHOD(bool, secondMethod,(int));
};
TEST(SomeClassTest, CallsMethodsInOrder) {
MockMyClass mock;
{
InSequence seq;
EXPECT_CALL(mock, firstMethod(testing::_))
.WillOnce(testing::Return(true));
EXPECT_CALL(mock, secondMethod(testing::_))
.WillOnce(testing::Return(true));
}
//EXPECT_EQ(true, mock.firstMethod(1));
//EXPECT_EQ(true, mock.secondMethod(2));
EXPECT_EQ(true, mock.secondMethod(2));
EXPECT_EQ(true, mock.firstMethod(1));
}
Если вы выполните приведенный выше код, будет сообщено об ошибке.
[ FAILED ] SomeClassTest.CallsMethodsInOrder (1 ms)
[----------] 1 test from SomeClassTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (1 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] SomeClassTest.CallsMethodsInOrder
Будет правильно, если вы сначала выполните метод «mock.firstMethod(1)», а затем «mock. SecondMethod(2).
2) Ожидайте частичных последовательных вызовов
Предложение After EXPECT_CALL может выполнять частичные последовательные вызовы.
Используя предложение InSequence()
using ::testing::Sequence;
...
Sequence s1, s2;
EXPECT_CALL(foo, A()).InSequence(s1, s2);
EXPECT_CALL(bar, B()).InSequence(s1);
EXPECT_CALL(bar, C()).InSequence(s2);
EXPECT_CALL(foo, D()).InSequence(s2);
Порядок выполнения
Как показано ниже (where s1 is A -> B, and s2 is A -> C -> D):
S1:A--------B
|
S2:A--------C ---------D
4.6.2 Любое количество звонков
EXPECT_CALL(mock_object,method_name(matchers...)) создает фиктивный объект mock_object. Этот объект имеет метод с именем имя_метода, а параметры метода являются сопоставителями.... EXPECT_CALL необходимо использовать перед любыми фиктивными объектами. Следующие методы необходимо вызывать в следующем порядке:
EXPECT_CALL(mock_object, method_name(matchers...))
.With(multi_argument_matcher) // Can be used at most once
.Times(cardinality) // Can be used at most once
.InSequence(sequences...) // Can be used any number of times
.After(expectations...) // Can be used any number of times
.WillOnce(action) // Can be used any number of times
.WillRepeatedly(action) // Can be used at most once
.RetiresOnSaturation(); // Can be used at most once
4.6.3 Объединение макета и заглушки
В одном и том же Mock-объекте мы можем использовать одновременно поведение Mock и Stub, что позволяет нам гибко контролировать поведение Mock-объекта в разных сценариях тестирования.
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::Invoke;
class Database {
public:
virtual ~Database() {}
virtual std::string query(const std::string& sql) const{return sql;}
virtual void connect(){};
};
class MockDatabase:public Database {
public:
MOCK_METHOD(std::string, query, (const std::string& sql), (const, override));
MOCK_METHOD(void, connect, (), (override));
};
TEST(SomeClassTest, CombineMockAndStub) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, connect()) //куча
.Times(1);
ON_CALL(mockDb, query(_)) //mock
.WillByDefault(testing::Return("Stubbed Query Result"));
mockDb.connect();
EXPECT_EQ("Stubbed Query Result",mockDb.query("query"));
}
4.7 Имитация сложных типов
Иногда нам нужно имитировать методы, возвращающие сложные типы. Google Mock предоставляет фабричные функции, такие как NiceMock и StrictMock, которые могут упростить создание и настройку объектов Mock.
4.7.1NiceMock
NiceMock — это оболочка, предоставляемая Google Mock (gmock), которая позволяет создавать более «щадящие» макеты объектов. В отличие от StrictMock, NiceMock не генерирует ошибку для неуказанных вызовов, но по умолчанию генерирует соответствующее возвращаемое значение или поведение.
Вот простой пример NiceMock:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using ::testing::Return;
// определить интерфейс
class MyInterface {
public:
virtual int Foo() = 0;
virtual void Bar(int x) = 0;
}
// Создайте макет класса для интерфейса.
class MockMyInterface : public MyInterface {
public:
MOCK_METHOD(int, Foo, (), (override));
MOCK_METHOD(void, Bar, (int), (override));
};
TEST(NiceMockTest, NiceMockExample) {
// Использование NiceMock для переноса MockMyInterface
::testing::NiceMock nice_mock;
// Указано желаемое поведение Foo(), но не указано желаемое поведение Bar().
EXPECT_CALL(nice_mock, Foo()).WillOnce(Return(42));
// Вызов Foo() должен вернуть 42
EXPECT_EQ(nice_mock.Foo(), 42);
// При вызове Bar() без указания желаемого поведения NiceMock не сообщит об ошибке и по умолчанию выполняет бездействующую операцию.
nice_mock.Bar(10);
}
4.7.2 StrictMock
StrictMock — это еще одна оболочка, предоставляемая Google Mock (gmock). В отличие от NiceMock, StrictMock сообщит об ошибке для неуказанных вызовов. Это означает, что вы должны указать желаемое поведение для всех методов макетного объекта, иначе тест завершится неудачно, если во время теста будет вызван метод без установленных ожиданий. Вот пример использования StrictMock:
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using ::testing::Return;
// определить интерфейс
class MyInterface {
public:
virtual int Foo() = 0;
virtual void Bar() = 0;
};
// Создать для интерфейса mock добрый
class MockMyInterface : public MyInterface {
public:
MOCK_METHOD(int, Foo, (), (override));
MOCK_METHOD(void, Bar, (), (override));
};
TEST(StrictMockTest, StrictMockExample) {
// использовать StrictMock Упаковка MockMyInterface
::testing::StrictMock strict_mock;
// для Foo() и Bar() Метод указывает ожидаемую строку для
EXPECT_CALL(strict_mock, Foo()).WillOnce(Return(42));
EXPECT_CALL(strict_mock, Bar());
// вызов Foo(), должен вернуть 42
EXPECT_EQ(strict_mock.Foo(), 42);
// вызов Bar(), оправдывая установленные ранее ожидания
strict_mock.Bar();
// если попытатьсявызов Никаких ожиданий не установленоmockметод,тест провалится
// Например: strict_mock.SomeUndeclaredMethod(); // Это приведет к сбою теста
}
l Используя метод NiceMock, если вы попытаетесь вызвать фиктивный метод без установки ожиданий, тест завершится неудачно;
lПри использовании метода StrictMock, если вы попытаетесь вызвать фиктивный метод без установки ожиданий, тест завершится неудачей.
5. Продвинутые навыки насмешки
5.1 Введение
Продвинутые навыки макетирования — это продвинутые навыки модульного тестирования, которые могут помочь тестировщикам более точно моделировать сложные сценарии, тем самым улучшая охват и качество тестирования. В этом разделе мы углубимся в некоторые продвинутые методы создания макетов и продемонстрируем их применение на богатых примерах.
5.2. Используйте ON_CALL для настройки Mock-поведения.
Макрос ON_CALL позволяет нам указать поведение по умолчанию для методов объекта Mock, что очень полезно при тестировании, особенно когда методы объекта Mock необходимо вызывать повторно в разных тестовых случаях.
знак:
ON_CALL(mock_object,method_name(matchers...))
Определяет действие, выполняемое после вызова метода имя_метода объекта макет_объекта. Если сопоставители опущены, это будет похоже на то, что каждый параметр имеет значение «_», что означает, что может быть сопоставлено любое значение.
ON_CALL(mock_object, method_name(matchers...))
.With(multi_argument_matcher) // Can be used at most once
.WillByDefault(action); // Required
lWith(multi_argument_matcher):Сделайте параметрыдляобщее совпадение。GoogleTestСделать все параметрыдляодинtupleДаватьmatcher,multi_argument_matcherв конце концов будетдаMatcher<std::tuple<A1, …, An>>。этотвызовмаксимум один раз
lWillByDefault(action): указывает действие, которое будет выполнено после вызова фиктивной функции. Этот вызов происходит ровно один раз.
посмотрите пример
#include <gmock/gmock.h>
class Database {
public:
virtual ~Database() {}
virtual std::string query(const std::string& sql) const{return sql;}
};
class MockDatabase:public Database {
public:
MOCK_METHOD(std::string, query, (const std::string& sql), (const, override));
};
class DataProcessor {
public:
explicit DataProcessor(Database* db) : db_(db) {}
std::string process(const std::string& data) {
return data + " processed with " + db_->query("SELECT * FROM table");
}
private:
Database* db_;
};
TEST(AdvancedMockingTest, CustomDefaultBehavior) {
MockDatabase mockDb;
ON_CALL(mockDb, query(testing::_))
.WillByDefault(testing::Return("Default Query Result"));
EXPECT_EQ("Default Query Result", mockDb.query("Any SQL"));
}
5.3 Моделирование исключений
В некоторых случаях нам может потребоваться смоделировать метод, вызывающий исключение, чтобы проверить способность объекта обрабатывать исключения.
class Calculator {
public:
virtual int divide(int a, int b) = 0;
};
class MockCalculator : public Calculator {
public:
MOCK_METHOD(int, divide, (int a, int b), (override));
};
TEST(CalculatorTest, TestCalculator) {
MockCalculator mockCalculator;
// Установите строку макета объекта для
//…
EXPECT_CALL(mockCalculator, divide(testing::_,0))
.WillOnce(testing::Throw(std::runtime_error("Divisor cannot be 0")));
// Проверьте строку макета объектадля //…
EXPECT_THROW(mockCalculator.divide(6, 0), std::runtime_error);
}
Имитируйте исключения посредством тестирования::Throw и EXPECT_THROW.
5.4 Использование функции обратного вызова Invoke
Функция Invoke позволяет нам вызывать функцию обратного вызова в методе Mock, что очень полезно, когда нам нужно динамически возвращать результаты на основе входных параметров.
#include <gmock/gmock.h>
class Database {
public:
virtual ~Database() {}
virtual std::string query(const std::string& sql) const{return sql;}
};
class MockDatabase:public Database {
public:
MOCK_METHOD(std::string, query, (const std::string& sql), (const, override));
};
class DataProcessor {
public:
explicit DataProcessor(Database* db) : db_(db) {}
std::string process(const std::string& data) {
return data + " processed with " + db_->query("SELECT * FROM table");
}
private:
Database* db_;
};
TEST(AdvancedMockingTest, UseInvokeForDynamicReturn) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, query(testing::_))
.WillOnce(testing::Invoke([](const std::string& sql) {
return "Result for " + sql;
}));
EXPECT_EQ("Result for SELECT * FROM table", mockDb.query("SELECT * FROM table"));
}
5.5 Моделирование сложных структур данных
Когда метод Mock возвращает сложную структуру данных, мы можем использовать WithArgs для сопоставления определенных параметров и возврата соответствующих результатов.
// Определить структуру
struct QueryResult {
std::string name;
int age;
};
// Гипотетический функциональный интерфейс
class DatabaseService {
public:
virtual QueryResult executeQuery(const std::string& sql) = 0;
};
// Mockдобрый
class MockDatabaseService : public DatabaseService {
public:
MOCK_METHOD(QueryResult, executeQuery, (const std::string&), (override));
};
TEST(MockDatabaseService, MockComplexReturnType) {
MockDatabaseService mockDb;
QueryResult alice{"Alice", 30};
EXPECT_CALL(mockDb, executeQuery(testing::_))
.Times(2)
.WillOnce(testing::WithArgs<0>(testing::Invoke([](const std::string& sql) {
return QueryResult{"Alice", 30};
})))
.WillRepeatedly(testing::WithArgs<0>(testing::Invoke([](const std::string& sql) {
return QueryResult{"Bob", 40};
})));
QueryResult result = mockDb.executeQuery("Alice");
EXPECT_EQ("Alice",result.name);
EXPECT_EQ(30,result.age);
result = mockDb.executeQuery("Bob");
EXPECT_EQ("Bob",result.name);
EXPECT_EQ(40,result.age);
}
5.6 Моделирование побочных эффектов вызовов методов
Иногда нам может потребоваться смоделировать побочные эффекты вызова метода, такие как изменение общего состояния или запуск обратного вызова.
#include <gmock/gmock.h>
using ::testing::_;
using ::testing::Invoke;
class Database {
public:
virtual ~Database() {}
virtual std::string update(int id, const std::string& value) const{return value;}
};
class MockDatabase:public Database {
public:
MOCK_METHOD(std::string, update, (int id, const std::string& value), (const, override));
};
TEST(MockDatabase, SimulateSideEffects) {
MockDatabase mockDb;
std::string sideEffect;
EXPECT_CALL(mockDb, update(_, _))
.WillOnce(Invoke([&sideEffect](int id, const std::string& value) {
return sideEffect = "Updated " + std::to_string(id) + " to " + value;
}));
mockDb.update(1, "value");
EXPECT_EQ("Updated 1 to value", sideEffect);
}
5.7 Множественные вызовы моделируемых методов
Время можно использовать для указания количества вызовов метода Mock, что очень полезно для тестирования циклов или рекурсивной логики.
#include <gmock/gmock.h>
class NetworkService {
virtual std::string fetchData(){return "";}
};
class MockNetworkService : public NetworkService {
public:
MOCK_METHOD(std::string, fetchData, (), (override));
};
class MockDatabase:public NetworkService {
public:
MOCK_METHOD(std::string, fetchData, (), (override));
};
TEST(AdvancedMockingTest, MultipleInvocations) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, fetchData())
.Times(3)
.WillRepeatedly(testing::Return("Data"));
for (int i = 0; i < 3; ++i) {
EXPECT_EQ("Data", mockDb.fetchData());
}
}
5.8. Использование UnorderedElementsAre для сопоставления контейнеров
Когда параметром метода Mock является контейнер, мы можем использовать UnorderedElementsAre для сопоставления элементов в контейнере без указания порядка элементов.
#include <gmock/gmock.h>
class Database {
public:
virtual ~Database() {}
virtual void insert(const std::vector& values){}
};
class MockDatabase{
public:
MOCK_METHOD(void, insert, (const std::vector& values));
};
// использоватьMockDatabaseдобрыйизметод
void SomeFunctionUsingMockDatabase(MockDatabase& db) {
std::vector values = {3, 1, 2};
db.insert(values);
}
//тестовый код
TEST(AdvancedMockingTest, MatchUnorderedContainer) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, insert(testing::UnorderedElementsAre(1, 2, 3)));
SomeFunctionUsingMockDatabase(mockDb);
}
6. Утверждения и ожидания Google Mock
6.1 Важность утверждений
Утверждения — это ключевой инструмент модульного тестирования, позволяющий проверить логическую корректность кода. Они позволяют тестировщикам указывать ожидаемые результаты и немедленно сообщать об ошибках, если результаты не соответствуют ожиданиям.
6.2 Базовые утверждения Google Test
Для проверки того, что результаты теста соответствуют ожиданиям, предоставляется ряд основных утверждений.
lASSERT_TRUE:если условиедля Фальшивый,Тогда тест не пройден.
lEXPECT_TRUE:То же, что и выше,Но условиядля Фальшивыйчастест Продолжить выполнение。
lASSERT_EQ:Проверьте два значениядаравны или нет,если не равно Тогда тест не пройден.
TEST(BasicAssertionsTest, ChecksEquality) {
int result = someFunction();
ASSERT_EQ(42, result) << "someFunction did not return the expected value.";}
6.3 Ожидания
Ожидаемые вызовы — это механизм Google Mock, позволяющий указать, как объект Mock должен вызываться в тесте.
lEXPECT_CALL:создаватьодиножидатьвызов。
lTimes:Укажите ожиданиявызовраз。
TEST(MockExpectationsTest, CallsFunctionOnce) {
MockFunction mock;
EXPECT_CALL(mock, Call())
.Times(1);
mock.Call();
}
6.4 Сопоставители
Google Mock предоставляет богатые средства сопоставления, которые позволяют нам использовать сложные условия в ожидаемых вызовах.
ltesting::_:соответствовать любому значению。
ltesting::Eq(x):совпадение равноxценить.
ltesting::Le(x):Совпадает менее чемxценить.
ltesting::Ge(x):Соответствие больше или равно x ценить.
TEST(MockMatchersTest, MatchesSpecificValue) {
MockFunction mock;
EXPECT_CALL(mock, Call(testing::Ge(10)))
.Times(1);
mock.Call(10); // Матч успешен
}
6.5 Объединение утверждений
Объединение утверждений позволяет нам построить более сложную логику проверки.
ltesting::AllOf:все условия соблюдены。
ltesting::AnyOf:Любое условие соблюдено。
TEST(CombinationAssertionsTest, ChecksMultipleConditions) {
int result = someFunction();
ASSERT_THAT(result, testing::AllOf(testing::Ge(40), testing::Le(50)));
}
6.6 Действия по утверждению (Действия)
Действия утверждения используются в Google Mock, чтобы указать, что должен делать объект Mock при ожидаемом вызове.
ltesting::Return(x):возвращаемое значение x。
ltesting::Throw:выдать исключение。
TEST(MockActionsTest, ReturnsSpecificValue) {
MockFunction mock;
EXPECT_CALL(mock, Call())
.WillOnce(testing::Return(42));
EXPECT_EQ(42, mock.Call());
}
6.7 Валидаторы
Валидаторы позволяют нам выполнять пользовательскую логику проверки при вызове Mock-объекта.
ltesting::SaveArg:держатьвызовпараметр。
ltesting::InvokeWithoutArgs:вызовфункция без параметров。
TEST(MockValidatorsTest, SavesArgument) {
MockFunction mock;
int savedArg;
EXPECT_CALL(mock, Call(testing::_))
.WillOnce(testing::SaveArg<0>(&savedArg));
mock.Call(42);
ASSERT_EQ(42, savedArg);
}
6.8 Расширенное использование ожиданий и утверждений
Расширенное использование включает установку порядка ожиданий, использование параметризованных ожиданий и т. д.
•InSequence:убеждатьсявызовпроисходить в определенном порядке。
•testing::Each:соответствовать каждому элементу。
using ::testing::InSequence;
using ::testing::Return;
TEST(SequenceExpectationsTest, CallsInOrder) {
InSequence seq;
MockFunction mock;
EXPECT_CALL(mock, Call(1))
.WillOnce(Return(2));
EXPECT_CALL(mock, Call(2))
.WillOnce(Return(3));
EXPECT_EQ(2, mock.Call(1));
EXPECT_EQ(3, mock.Call(2));
}
7 Интеграция
Судя по приведенным выше случаям. Две программы, калькулятор и доступ к данным, могут быть интегрированы.
7.1 Калькулятор
Calculator.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class Calculator {
public:
virtual ~Calculator() {}
virtual int add(int a, int b) = 0;
virtual int subtract(int a, int b) = 0;
virtual int multiply(int a, int b) = 0;
virtual int divide(int a, int b) = 0;
};
class MockCalculator : public Calculator {
public:
MOCK_METHOD(int, add, (int a, int b), (override));
MOCK_METHOD(int, subtract, (int a, int b), (override));
MOCK_METHOD(int, multiply, (int a, int b), (override));
MOCK_METHOD(int, divide, (int a, int b), (override));
};
TEST(CalculatorTest, TestCalculator) {
MockCalculator mockCalculator;
// Установите строку макета объекта для
ON_CALL(mockCalculator, add(testing::_,testing::_))
.WillByDefault(testing::Invoke([](int a, int b) { return a + b; }));
ON_CALL(mockCalculator, subtract(testing::_,testing::_))
.WillByDefault(testing::Invoke([](int a, int b) { return a - b; }));
ON_CALL(mockCalculator, multiply(testing::_,testing::_))
.WillByDefault(testing::Invoke([](int a, int b) { return a * b; }));
ON_CALL(mockCalculator, divide(testing::_,testing::_))
.WillByDefault(testing::Invoke([](int a, int b) { return a / b; }));
// Проверьте строку макета объектадля
EXPECT_EQ(mockCalculator.add(2, 3), 5);
EXPECT_EQ(mockCalculator.subtract(5, 3), 2);
EXPECT_EQ(mockCalculator.multiply(5, 3), 15);
EXPECT_EQ(mockCalculator.divide(6, 3), 2);
}
TEST(CalculatorTest, TestCalculatorDivisorZero) {
MockCalculator mockCalculator;
// Установите строку макета объекта для
EXPECT_CALL(mockCalculator, divide(testing::_,0))
.WillOnce(testing::Throw(std::runtime_error("Divisor cannot be 0")));
// Проверьте строку макета объектадля
//…
EXPECT_THROW(mockCalculator.divide(6, 0), std::runtime_error);
}
7.2 Доступ к базе данных
Database.cpp
#include <gmock/gmock.h>
#include <string>
#include <unordered_map>
#include <memory>
#include <iostream>
#include <vector>
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::UnorderedElementsAre;
using ::testing::WithArgs;
using ::std::string;
using ::std::vector;
using ::std::to_string;
// Определить структуру
struct QueryResult {
string name;
int age;
};
class Database {
public:
virtual ~Database() {}
virtual string query(const string& sql) const{return sql;}
virtual string fetchData(){return "";}
virtual string update(int id, const string& value) const{return value;}
virtual QueryResult executeQuery(const string& sql) = 0;
virtual void insert(const vector& values){}
virtual void connect(){}
};
class MockDatabase:public Database {
public:
MOCK_METHOD(string, query, (const string& sql), (const, override));
MOCK_METHOD(string, fetchData, (), (override));
MOCK_METHOD(string, update, (int id, const string& value), (const, override));
MOCK_METHOD(QueryResult, executeQuery, (const string&), (override));
MOCK_METHOD(void, insert, (const vector& values), (override));
MOCK_METHOD(void, connect, (), (override));
};
class DataProcessor {
public:
explicit DataProcessor(Database* db) : db_(db) {}
string process(const string& data) {
return data + " processed with " + db_->query("SELECT * FROM table");
}
private:
Database* db_;
};
TEST(DataBaseTest, ProcessesDataWithDatabaseQuery) {
MockDatabase mockDb;
DataProcessor processor(&mockDb);
EXPECT_CALL(mockDb, query("SELECT * FROM table"))
.WillOnce(Return("Mocked query result"));
EXPECT_EQ("Data processed with Mocked query result", processor.process("Data"));
}
TEST(DataBaseTest, CombineMockAndStub) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, connect())
.Times(1);
ON_CALL(mockDb, query(_))
.WillByDefault(Return("Stubbed Query Result"));
mockDb.connect();
EXPECT_EQ("Stubbed Query Result",mockDb.query("query"));
}
TEST(DataBaseTest, UseInvokeForDynamicReturn) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, query(_))
.WillOnce(Invoke([](const string& sql) {
return "Result for " + sql;
}));
EXPECT_EQ("Result for SELECT * FROM table", mockDb.query("SELECT * FROM table"));
}
TEST(DataBaseTest, CustomDefaultBehavior) {
MockDatabase mockDb;
ON_CALL(mockDb, query(_))
.WillByDefault(Return("Stubbed Query Result"));
EXPECT_EQ("Stubbed Query Result", mockDb.query("query"));
}
TEST(DataBaseTest, FetchDataTest) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, fetchData())
.WillOnce(Return("Mocked Data"));
string result = mockDb.fetchData();
EXPECT_EQ("Mocked Data", result);
}
TEST(DataBaseTest, SimulateSideEffects) {
MockDatabase mockDb;
string sideEffect;
EXPECT_CALL(mockDb, update(_, _))
.WillOnce(Invoke([&sideEffect](int id, const string& value) {
return sideEffect = "Updated " + to_string(id) + " to " + value;
}));
mockDb.update(1, "value");
EXPECT_EQ("Updated 1 to value", sideEffect);
}
TEST(DataBaseTest, MultipleInvocations) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, fetchData())
.Times(3)
.WillRepeatedly(Return("Data"));
for (int i = 0; i < 3; ++i) {
EXPECT_EQ("Data", mockDb.fetchData());
}
}
void SomeFunctionUsingMockDatabase(MockDatabase& db) {
vector values = {3, 1, 2};
db.insert(values);
}
TEST(DataBaseTest, MatchUnorderedContainer) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, insert(UnorderedElementsAre(1, 2, 3)));
SomeFunctionUsingMockDatabase(mockDb);
}
TEST(DataBaseTest, MockComplexReturnType) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, executeQuery(_))
.Times(2)
.WillOnce(WithArgs<0>(Invoke([](const string& sql) {
return QueryResult{"Alice", 30};
})))
.WillRepeatedly(WithArgs<0>(Invoke([](const string& sql) {
return QueryResult{"Bob", 40};
})));
QueryResult result = mockDb.executeQuery("Alice");
EXPECT_EQ("Alice",result.name);
EXPECT_EQ(30,result.age);
result = mockDb.executeQuery("Bob");
EXPECT_EQ("Bob",result.name);
EXPECT_EQ(40,result.age);
}