MATCHERS:
Eq(20) - równy 20
Gt(60) - wiekszy niż 60
HasSubstr("Kowalski") - zawiera fragment " "
DoubleEq(3.0) - równy dla double
dla kontenerów:
Contains(18) - zawiera 18 (czy kontener zawiera)Each(Gt(11)) - czy każdy większy
Pointwise(Eq(), tab2)) - porównuje adresy kontenerow, czy ten sam
ContainerEQ - porownuje całę kontenery
AllOf(łączy matchery )
A<typ>() - sprawdza typ
Field - pozwala dotrzec do typu
stosuj: (gdzie &DataTab::time określa typstruktury i nazwe pola
(Field(&DataTab::time, Eq(20))
Przykłady:
#include "gtest/gtest.h" #include "gmock/gmock.h" #include "example.h" #include <algorithm> #include <numeric> #include <functional> using namespace ::testing; //1.Write simple test with matcher, in which: // *You will check that pointer ptr points to vector vec TEST(MatcherTest, containerMatchersType) { ASSERT_THAT(ptr, Pointee(vec)); } // *Each elements of vector vec are greater than 100 TEST(MatcherTest, containerMatchersContain) { ASSERT_THAT(vec, Each(Gt(100))); } //2. write matcher which: // *Will check that time is equal to 20 // *Will check that temperature is greater then 60 // *Will check substring "Kowalski" in name string // *Will check double value is equal to 3.0 // *Will check that vector contain value 18 TEST(MatcherTest, containerMatchersTask2) { ASSERT_THAT(structToTest.time,Eq(20) ); ASSERT_THAT(structToTest.temperature,Gt(60) ); ASSERT_THAT(structToTest.name, HasSubstr("Kowalski")); ASSERT_THAT(structToTest.value, DoubleEq(3.0)); ASSERT_THAT(structToTest.vec, Contains(18)); ASSERT_THAT(structToTest, AllOf(Field(&DataTab::time, Eq(20)), Field(&DataTab::temperature, Gt(60)) , Field(&DataTab::name, HasSubstr("Kowalski")), Field(&DataTab::value, DoubleEq(3.0)), Field(&DataTab::vec, Contains(18)))); } //3. Compare tab1 and tab2 from example.h and write matcher to find not matching element. Use generateNumbers() method! TEST(MatcherTest, containerMatchersTask3) { generateNumbers(); ASSERT_THAT(tab1, Pointwise(Eq(), tab2)); ASSERT_THAT(tab1, ElementsAreArray (tab2)); }
TEST_F
uruchamia setup klasy testowej, warto je definiować jako struct (publiczna)
metody klasy restowej:
void SetUp() - ustawia wartości do testó
void TearDown() - kasuje wartości do testów
w SetUp tworzymy SUT - czyli system under Test, instancje testowanej klasy
Jeśli chcemy ustawic setup dla wszystkich testow nie dla pojedynczego z osobna to:
SetUpTestCase()
TearDownTestCase()
uwaga metody statyczne!:
zadania labo01 :
#include "gtest/gtest.h" #include "gmock/gmock.h" #include "example.h" #include <algorithm> #include <numeric> #include <functional> using namespace ::testing; /* Task 1. Prepare a test class for the DBaseManager. Remember * to include a smart pointer to the system under test and (for * design reasons) a vector of detailed entries for the manager * to manage. In the setup, load the test database from the * file lab1_dummydata.txt to MANAGED_DBusing the auxiliary static * method DBaseLoader::loadFromFile. Try to not do this in a way * resulting in more overhead than needed. * * Task 2. Extend the test class by adding a matcher for objects * of the DBaseSimpleEntry class with the signature: * auto matchSimpleDBEntry(const DBaseSimpleEntry & expectedEntry) * Make sure to nest predefined matchers within it which are most * suitable for the types used in the structure. * * Task 3. Extend the test class by adding a matcher for objects * of the DBaseDetailedEntry class with the signature: * auto matchDetailedDBEntry(const DBaseDetailedEntry & expectedEntry) */ struct DBaseManagerTest : public Test { static void SetUpTestCase() { DBaseLoader::loadFromFile("lab1_dummydata.txt", MANAGED_DB); } void SetUp() override { sut = std::make_unique<DBaseManager>(&MANAGED_DB); } void TearDown() override { } auto matchSimpleDBEntry(const DBaseSimpleEntry & expectedEntry) { return AllOf(Field(&DBaseSimpleEntry::surname, HasSubstr(expectedEntry.surname)), Field(&DBaseSimpleEntry::accountBalance, DoubleEq(expectedEntry.accountBalance))); } std::unique_ptr<DBaseManager> sut; auto matchDetailedDBEntry(const DBaseDetailedEntry & expectedEntry) { return AllOf(Field(&DBaseDetailedEntry::firstName, HasSubstr(expectedEntry.firstName)), Field(&DBaseDetailedEntry::secondName, HasSubstr(expectedEntry.secondName)), Field(&DBaseDetailedEntry::surname, HasSubstr(expectedEntry.surname)), Field(&DBaseDetailedEntry::accountBalance, DoubleEq(expectedEntry.accountBalance)), Field(&DBaseDetailedEntry::securities, DoubleEq(expectedEntry.securities)), Field(&DBaseDetailedEntry::goldDeposits, Eq(expectedEntry.goldDeposits)), Field(&DBaseDetailedEntry::debt, FloatEq(expectedEntry.debt)) ); } }; /* Task 4. Test that the getGoldInBank method of DBaseManager * works as intended. John wrote that the value of gold deposits * in the bank was $5,371,000 on the day it was closed. */ TEST_F(DBaseManagerTest, getGoldInBankRetunsSumOfGoldDeposits) { constexpr auto EXPECTED_VALUE=5371000; EXPECT_EQ(sut->getGoldInBank(),EXPECTED_VALUE); } /* Task 5. Test that the searchBalanceSimple method of DBaseManager * works as intended. It should fill the vector of simple enetries * passed by refeence as first argument with entries corresponding * to customers with an account balance greater or equal to the value * passed as second argument. Specify a dummy value (i.e. $50,000) * and check that it works as intended. * * Hint: you don't need to know the exact size of the container * to write the unit test. */ TEST_F(DBaseManagerTest, searchBalanceSimpleShouldWorkAsIntended) { constexpr double DUMMY_VALUE1=50000; std::vector<DBaseSimpleEntry> simpleDB; sut->searchBalanceSimple(simpleDB,DUMMY_VALUE1); ASSERT_THAT(simpleDB, Contains(Field(&DBaseSimpleEntry::accountBalance, Gt(DUMMY_VALUE1)))); } /* Task 6. Test that the searchSurnameSimple method of DBaseManager * works as intended. Use a dummy string "ill" to test that the method * searches the database and fills the vector of simple entries * passed by reference as first argument with customer data for * customers whose surnames contain "ill". You asked John about * such customers and, conveniently, there were only four of them * in the test database. The expected customer data output by the * method is provided in DUMMY_CONTAINER_01. * * Hint: use the matchSimpleDBEntry matcher you wrote earlier. */ TEST_F(DBaseManagerTest, searchSurnameSimpleOnTestDBReturnsPredefinedCustomersInSimplifiedForm) { std::string DUMMY_VALUE1="ill"; std::vector<DBaseSimpleEntry> simpleDB; sut->searchSurnameSimple(simpleDB,DUMMY_VALUE1); ASSERT_THAT(simpleDB, ElementsAre(matchSimpleDBEntry(DUMMY_CONTAINER_01[0]), matchSimpleDBEntry(DUMMY_CONTAINER_01[1]), matchSimpleDBEntry(DUMMY_CONTAINER_01[2]), matchSimpleDBEntry(DUMMY_CONTAINER_01[3]))); } /* Task 7. Test that the searchSurname method of DBaseManager works * as intended. Use a dummy string "Ma" to test that the method * searches the database and fills the vector of detailed entries * passed by reference as first argument with customer data for * customers whose surnames contain "Ma". You asked John about * such customers and, conveniently, there were only four of them * in the test database. The expected customer data output by the * method is provided in DUMMY_CONTAINER_02. * * Hint: use the matchDetailedDBEntry matcher you wrote earlier. */ TEST_F(DBaseManagerTest, searchSurnameOnTestDBReturnsPredefinedCustomers) { std::string DUMMY_VALUE1="Ma"; std::vector<DBaseDetailedEntry> simpleDB; sut->searchSurname(simpleDB,DUMMY_VALUE1); /* for(auto a : simpleDB ) std::cout<<a.surname<<" "<<a.accountBalance<<std::endl; */ ASSERT_THAT(simpleDB, ElementsAre(matchDetailedDBEntry(DUMMY_CONTAINER_02[0]), matchDetailedDBEntry(DUMMY_CONTAINER_02[1]), matchDetailedDBEntry(DUMMY_CONTAINER_02[2]), matchDetailedDBEntry(DUMMY_CONTAINER_02[3]))); } /* Task 8. Test that the searchBalance method of DBaseManager works * as intended. You asked John for the top three customers (those * with the highest account balance) in the test database. Their data * is provided in DUMMY_HIGH_BALANCE_CUSTOMERS. Their account balance * is in each case at least $96,937.69. The searchBalance method should * fill the vector of detailed enetries passed by refeence as first * argument with entries corresponding to customers with an account * balance greater or equal to the value passed as second argument. */ TEST_F(DBaseManagerTest, searchBalanceOnTestDBShouldReturnThreeCustomersWithHighestBalance) { } /* Task 9. Test that searchDebtors works as intended. (difficult) * * The method should fill the vector of tuples (surname, debt) passed * by reference as first argument with entries corresponding to clients * who owe the bank at least as much as passed as second argument. Debt * values are negative numbers. * * John says that the bank's highest debtors were Mrs. Mickle with a debt * of $998.06, Mr. Cadiz with a debt of $997.19 and Mrs. Rothenberger * with a debt of $990.44. * * They were the only customers with a debt greater than $970, so you * can use -970.0f as the argument for the tested method. * * Hint: the difficulty here is tuple element matching in a short, * compact and readable way. Consider using the ResultOf matcher. * It does not however accept get<0> and get<1> as functions, * so yo'd need to make (somewhat tedious) wrappers for them. * */ TEST_F(DBaseManagerTest, searchDebtorsReturnsGreatestDebtors) { std::vector<std::tuple<std::string, float>> simpleDB; constexpr float DUMMY_VALUE1=0; sut->searchDebtors(simpleDB,DUMMY_VALUE1); for(auto a : simpleDB ) std::cout<<std::get<0>(a)<<" "<<std::get<1>(a)<<std::endl; ASSERT_THAT(simpleDB, ElementsAre( ResultOf([](std::tuple<std::string, float> a_rTarget ) {return std::get<1>(a_rTarget);},DUMMY_VALUE1) )); }
MOCKS
Tworzenie mocka:
int qux (std::string a_bin) metoda qux zwraca int a przyjmuje stringMOCK_METHOD N qux int (std::string a_bin) kolejnosc w kocku (N-liczba zmiennych)
i mock wygląda tak:
MOCK_METHOD1(qux, int(std::string a_bin));
mockowanie nievirtualnych: Napisać mocklasę nie dziedziczącą po oryginalnej) i wstrzykiwać ja do oryginalnego kodu (za pomocą #ifdef UNITTEST )
Google Mock ogarnia przeciążone metody ale uważać z wildcardami __
przyklad 07 z przeciążonym mockiem i wildcardem __:
class BarIf { public: virtual void registerAddedOne(int & a_rArg) = 0; virtual void registerAddedOne(float & a_rArg) = 0; }; //mock do niej: #include <gmock/gmock.h> #include "BarIf.h" struct GMockBar : public BarIf { MOCK_METHOD1(registerAddedOne, void(int & a_rArg)); MOCK_METHOD1(registerAddedOne, void(float & a_rArg)); };
kod testu:
#include <gtest/gtest.h> #include <memory> #include <iostream> #include "Foo.h" #include "GMockBar.h" //#define StrictMock NaggyMock namespace { using namespace testing; constexpr float DUMMY_F_VALUE_1 = 3.14f; constexpr float DUMMY_F_VALUE_2 = 2.72f; constexpr FooFloatData DUMMY_F_DATA { DUMMY_F_VALUE_1, DUMMY_F_VALUE_2 }; } struct FooTest : public Test { FooTest() { std::cout << "Constructor!" << std::endl; } ~FooTest() override { std::cout << "Destructor!" << std::endl; } void SetUp() override { std::cout << "SetUp!" << std::endl; barMock = std::make_unique<StrictMock<GMockBar>>(); sut = std::make_unique<Foo>(barMock.get()); } void TearDown() override { std::cout << "TearDown!" << std::endl; } auto matchFooDataFields(const FooData & a_rExpectedFooData) { return AllOf( Field( &FooData::x, Eq(a_rExpectedFooData.x) ), Field( &FooData::y, Eq(a_rExpectedFooData.y) ) ); } std::unique_ptr<StrictMock<GMockBar>> barMock; std::unique_ptr<Foo> sut; }; TEST_F(FooTest, callingRandomizeShouldRandomizeArgument) { FooFloatData testData = DUMMY_F_DATA; sut->randomize(testData); EXPECT_NE(testData.x, DUMMY_F_DATA.x); EXPECT_NE(testData.y, DUMMY_F_DATA.y); } /* TEST_F(FooTest, callingRandomizeShouldRandomizeArgument2) { FooFloatData testData = DUMMY_F_DATA; EXPECT_CALL(*barMock, registerAddedOne(_)); sut->randomize(testData); EXPECT_NE(testData.x, DUMMY_F_DATA.x); EXPECT_NE(testData.y, DUMMY_F_DATA.y); } */ TEST_F(FooTest, callingRandomizeShouldRandomizeArgument2) { FooFloatData testData = DUMMY_F_DATA; EXPECT_CALL(*barMock, registerAddedOne(An<float&>())); sut->randomize(testData); EXPECT_NE(testData.x, DUMMY_F_DATA.x); EXPECT_NE(testData.y, DUMMY_F_DATA.y); }
NICE STRICT NAGGY
nice - nic nie zgłosi
naggy - warning
strict - error i fail testu
upraszczanie interfejsów
(np bardzo dużo argumentów, mock max do 10) napisać wraper na mocka, nadpisana klasa testowa wywołuje nową metodę z małą ilością argumentów (nazwana ...Simple) która to jest już poprawnie zmockowana:przykład 08
#define GMOCKBAR_H #include <gmock/gmock.h> #include "BarIf.h" struct GMockBar : public BarIf { virtual void qux(int x, float y, double z, std::string s, int * l, float * m, double * n, char & a, int & b, float & c, double & d) { simpleQux(x, s); } MOCK_METHOD2(simpleQux, void(int x, std::string s)); }; #endif // GMOCKBAR_H
#include "gtest/gtest.h" #include "gmock/gmock.h" #include <memory> #include <iostream> #include "Foo.h" #include "GMockBar.h" //#define StrictMock NaggyMock namespace { using namespace testing; } struct FooTest : public Test { FooTest() { } ~FooTest() override { } void SetUp() override { barMock = std::make_unique<StrictMock<GMockBar>>(); sut = std::make_unique<Foo>(barMock.get()); } void TearDown() override { } std::unique_ptr<StrictMock<GMockBar>> barMock; std::unique_ptr<Foo> sut; }; TEST_F(FooTest, callingCorgeShouldmakeCallToQux) { EXPECT_CALL(*barMock, simpleQux(Eq(1), StrEq("foo"))); sut->corge(); }
EXPECT_CALL and ON_CALL
EXPECT_CALL( mockClassObject, method(argMatcher1, argMatcher2, ...) );
jeśli z pointera to *mockClassObject
.Times(1); jeden raz
EXPECT_CALL( mockClassObject, method(...) ).Times(1);
If you know what you’re doing (do not abuse), you can even (or use ON_CALL)
EXPECT_CALL( mockClassObject, method(...) ).Times(AnyNumber());
Times(0); - sprawdzanie czy się nie zawoła.
Zwracanie wartości:
WillOnce(); - zwróci raz
We can do that multiple times
EXPECT_CALL( mockClassObject, method(...) ).WillOnce(...).WillOnce(...);
Don’t abuse WillRepeatedly to specify default behavior (use ON_CALLs).
EXPECT_CALL( mockClassObject, method(...) ).WillRepeatedly(...);
musi być określone konkretnie co zwóci:
EXPECT_CALL( mockClassObject, method(...) ).WillOnce(Return(value));
Zwracanie przez referencję:
For this purpose the actions SetArgReferee<n> and SetArgPointee<n>
The n is the argument number (0-based).
We can also invoke functions with Invoke(f)
jeśli kilka rzeczy do zrobienia DoAll( ... )
można zrobić tak, że w SetUp dać ON_CALL na jakiegoś mocka (zawsze można go wywołać) a w konkretnym teście nadpisać go EXPEC_CALLem (EXPECT_ nadpisuje ON_)
przykład 09:
#include <gtest/gtest.h> #include <memory> #include "Foo.h" #include "GMockBar.h" #include "GMockBaz.h" // #define NiceMock StrictMock namespace { using namespace testing; constexpr int DUMMY_SERIAL = 12345; constexpr float DUMMY_VALUE = 3.14f; } struct FooTest : public Test { void SetUp() override { barMock = std::make_unique<NiceMock<GMockBar>>(); bazMock = std::make_unique<NiceMock<GMockBaz>>(); // ON_CALL(*barMock, registerFoo(An<Foo*>())).WillByDefault(Return()); ON_CALL(*barMock, getDateCreated(An<Foo*>(), An<std::string&>())) .WillByDefault(SetArgReferee<1>("29.02.2021")); sut = std::make_unique<Foo>(barMock.get()); } void TearDown() override { } std::unique_ptr<NiceMock<GMockBar>> barMock; std::unique_ptr<NiceMock<GMockBaz>> bazMock; std::unique_ptr<Foo> sut; }; TEST_F(FooTest, createFooTest) { std::string date = "30.02.2020"; std::string empty; EXPECT_CALL(*barMock, registerFoo(An<Foo*>())); EXPECT_CALL(*barMock, getDateCreated(An<Foo*>(), StrEq(empty))) .WillOnce(SetArgReferee<1>(date)); Foo newFoo(barMock.get()); } TEST_F(FooTest, corgeShouldUpdateInfoIfWorkingProperly) { EXPECT_CALL(*barMock, getBaz()).WillOnce(Return(bazMock.get())); EXPECT_CALL(*bazMock, getData(An<int*>(), FloatEq(0.0f))) .WillOnce(DoAll(SetArgPointee<0>(DUMMY_SERIAL), SetArgReferee<1>(DUMMY_VALUE), Return(true))); EXPECT_CALL(*barMock, updateInfo(Eq(sut.get()), Eq(DUMMY_SERIAL), Eq(DUMMY_VALUE))); sut->corge(); } TEST_F(FooTest, corgeShouldNotUpdateInfoIfGetDataReturnsFalse) { EXPECT_CALL(*barMock, getBaz()).WillOnce(Return(bazMock.get())); EXPECT_CALL(*bazMock, getData(An<int*>(), FloatEq(0.0f))) .WillOnce(DoAll(SetArgPointee<0>(DUMMY_SERIAL), SetArgReferee<1>(DUMMY_VALUE), Return(false))); EXPECT_CALL(*barMock, updateInfo(_,_,_)).Times(0); sut->corge(); } TEST_F(FooTest, corgeShouldDoNothingIfGetBazReturnsNullptr) { EXPECT_CALL(*barMock, getBaz()).WillOnce(Return(nullptr)); EXPECT_CALL(*bazMock, getData(_,_)).Times(0); EXPECT_CALL(*barMock, updateInfo(_,_,_)).Times(0); sut->corge(); } TEST_F(FooTest, getValueShouldReturnFieldSetByCorge) { EXPECT_CALL(*barMock, getBaz()).WillOnce(Return(bazMock.get())); EXPECT_CALL(*bazMock, getData(An<int*>(), FloatEq(0.0f))) .WillOnce(DoAll(SetArgPointee<0>(DUMMY_SERIAL), SetArgReferee<1>(DUMMY_VALUE), Return(true))); EXPECT_CALL(*barMock, updateInfo(Eq(sut.get()), Eq(DUMMY_SERIAL), Eq(DUMMY_VALUE))); sut->corge(); EXPECT_FLOAT_EQ(sut->getValue(), DUMMY_VALUE); } /* ON_CALL(*barMock, getBaz()).WillByDefault(Return(bazMock.get())); ON_CALL(*bazMock, getData(An<int*>(), FloatEq(0.0f))) .WillByDefault(DoAll(SetArgPointee<0>(DUMMY_SERIAL), SetArgReferee<1>(DUMMY_VALUE2), Return(true))); TEST_F(FooTest, getValueShouldReturnFieldSetByCorge2) { sut->corge(); EXPECT_FLOAT_EQ(sut->getValue(), DUMMY_VALUE2); }*/
tu w liniach 56-60 dobry przykład zwracania przez wartość:
EXPECT_CALL(*bazMock, getData(An<int*>(), FloatEq(0.0f)))
.WillOnce(DoAll(SetArgPointee<0>(DUMMY_SERIAL),
SetArgReferee<1>(DUMMY_VALUE),
Return(true)));
RetiresOnSaturation
Castowanie typu MAtcherów
patrz 3.4.11
MOCK std::function
uzywamy AsStdFunction(); patrz 3.4.12
.WillOnce(DoAll(SetArgPointee<0>(DUMMY_SERIAL),
SetArgReferee<1>(DUMMY_VALUE),
Return(true)));
Pamiętaj, ustawiaj Expec_Calle w kolejności ich wywoływania!
RetiresOnSaturation
po wielokrotnym wywołaniu znika (idzie na emeryture :-)
Przyklad 10
#include <gtest/gtest.h>#include <memory>#include "Foo.h"#include "GMockBaz.h"namespace{using namespace testing;}struct FooTest : public Test{void SetUp() override{bazMock = std::make_unique<NiceMock<GMockBaz>>();sut = std::make_unique<Foo>(bazMock.get());}void TearDown() override{}std::unique_ptr<NiceMock<GMockBaz>> bazMock;std::unique_ptr<Foo> sut;};/* Requirement: corge should keep calling* getData until it returns true and data is received.** Test plan: mockup of getData returns false 6 times,* called 7th time returns true and writes data.**/TEST_F(FooTest, corgeShouldRepeatedlyAskForDataUntilSuccess){EXPECT_EQ(sut->getData(), -1);EXPECT_CALL(*bazMock, getData(_)).WillOnce(Return(false)).WillOnce(Return(false)).WillOnce(Return(false)).WillOnce(Return(false)).WillOnce(Return(false)).WillOnce(Return(false)).WillOnce(DoAll(SetArgReferee<0>(13),Return(true)));/*EXPECT_CALL(*bazMock, getData(_)).WillOnce(DoAll(SetArgReferee<0>(13),Return(true)));EXPECT_CALL(*bazMock, getData(_)).Times(6).WillRepeatedly(Return(false));//.RetiresOnSaturation();*/sut->corge();EXPECT_EQ(sut->getData(), 13);}
Castowanie typu MAtcherów
patrz 3.4.11
MOCK std::function
uzywamy AsStdFunction(); patrz 3.4.12
Zadanie LAB02 (tetris)
przykład konfigurowania 4.4
przykład 4.5.3 ParamTest EX:
Przykład zadania TDD wykorzystującego Testy parametryczne LINK
/* This is the main part of the excercise, where you will * use the mocks you wrote earlier to thest TetrominoWell. * * The tests you will mostly write, in sequence, correspond to * a game of Tetris in a miniature Well where square-shaped * Tetrominos keep falling down until they stack and trigger * a game-over. * */ #include <gtest/gtest.h> #include <gmock/gmock.h> #include "TetrisWell.hpp" #include "TetrominoGeneratorMock.hpp" #include "TetrominoMock.hpp" #include <memory> #include <iostream> /* The anonymous namespace contains predefined Well states * for your convenience. The tasks will guide you which ones to * use, but you can check them out now if you wish. * */ namespace { using namespace ::testing; const int BOARD_DIM_X = 6; const int BOARD_DIM_Y = 5; const std::string EMPTY_BOARD = "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┗━━━━━━┛\n"; const std::string BOARD_SPAWN = "┃ ░ ░ ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┗━━━━━━┛\n"; const std::string BOARD_TICK1 = "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░ ░ ░┃\n" "┗━━━━━━┛\n"; const std::string BOARD_TICK2 = "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┗━━━━━━┛\n"; const std::string BOARD_SPAWN2 = "┃ ░ ░ ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┃ ░" BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR " ░┃\n" "┗━━━━━━┛\n"; const std::string BOARD_RIGHT_M1 = "┃ ░ ░ ░┃\n" "┃ ░ ░ ░┃\n" "┃ ░ " BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR "░┃\n" "┃ ░ " BEGIN_YELLOW "█" END_COLOR BEGIN_YELLOW "█" END_COLOR "░┃\n" "┃ ░ ░ ░┃\n" "┗━━━━━━┛\n"; } /* Task 7. Write the TetrisWellTest class. * * Remember to include the following members: * + system under test * + mock of Tetromino * + mock of TetrominoGenerator * + atomic int to use as action parameter * + atomic bool flag signaling game over * Use smart pointer wherever applicable. * * In SetUp, initialize all of the above properly. */ struct TetrisWellTest : public Test { void SetUp() override { actionvar.store(0); gameover.store(false); TetroGenMock = std::make_shared<NiceMock<TetrominoGeneratorMock>>(); TetroMock=std::make_shared<NiceMock<TetrominoMock>>(); sut = std::make_unique<TetrisWell>(BOARD_DIM_X,BOARD_DIM_Y,TetroGenMock,&actionvar, &gameover); } void TearDown() override { } std::unique_ptr<TetrisWell> sut; std::shared_ptr<NiceMock<TetrominoGeneratorMock>> TetroGenMock; std::shared_ptr<NiceMock<TetrominoMock>> TetroMock; std::atomic <int> actionvar; std::atomic <bool> gameover; }; /* Task 8. Test that upon initialization the Well is empty. * Use the EMPTY_BOARD const. Also check that the flag * signaling game over is set to false. You should * check this flag in all subsequent tests. */ TEST_F(TetrisWellTest, uponInitiationWellShouldBeEmpty) { //fields : //getImage is empty? //gameover flag is false ? EXPECT_THAT(sut->getImage(),EMPTY_BOARD ); sut->getImage(); } /* Task 9. Test that the first call to tick() after initialization of * the Well spawns a new Tetromino. * * You should expect a call to getTetromino of the mocked TetrominoGenerator. * The call shoudl return a tetromino object, which is mocked as well in * this test. * * Now calling getImage() should return the Well state defined in BOARD_SPAWN. * However, it will make several calls to the Tetromino object, here mocked: * + 4 calls to getType * + 16 calls to at with different coordinates * * Set up expectations to mock a square tetromino, TetrominoType::Q, for which * calls to at(x,y) of the 4x4 shape map * * 0000 * 0110 * 0110 * 0000 * * return true for 1 and false for 0. * * For convenience, you can print the result of getImage() and the expected * BOARD_SPAWN to std::cout. Expect them to be equal. */ TEST_F(TetrisWellTest, firstTickAfterInitiationShouldSpawnTetromino) { EXPECT_CALL(*m_ptertominoGeneratorMock, getTetromino()) .WillOnce(Return(m_ptertominoMock)); EXPECT_CALL(*m_ptertominoMock,getType()) .Times(4) .WillRepeatedly(Return(TetrominoType::Q)); EXPECT_CALL(*m_ptertominoMock,at(_,_)) .Times(12) .WillRepeatedly(Return(false)); EXPECT_CALL(*m_ptertominoMock,at(1,1)) .WillOnce(Return(true)); EXPECT_CALL(*m_ptertominoMock,at(2,1)) .WillOnce(Return(true)); EXPECT_CALL(*m_ptertominoMock,at(1,2)) .WillOnce(Return(true)); EXPECT_CALL(*m_ptertominoMock,at(2,2)) .WillOnce(Return(true)); sut->tick(); std::string res = sut->getImage(); EXPECT_THAT(res, StrEq(BOARD_SPAWN)); }
TEST_P - parametryczne
przykład konfigurowania 4.4
przykład 4.5.3 ParamTest EX:
#include <gtest/gtest.h> #include <gmock/gmock.h> #include "Client.h" #include "Result.h" //#include "TetrominoGeneratorMock.hpp" #include "CommunicationAdapterIf.h" #include "MessageIds.h" #include <memory> #include <iostream> namespace { using namespace ::testing; } struct CommunicationMock: public CommunicationAdapterIf { MOCK_METHOD1( send, Result (std::unique_ptr<Signal>) ); }; struct ClientTest : public Test { void SetUp() override { CommMock = std::make_shared<NiceMock<CommunicationMock>>(); sut = std::make_unique<Client>(CommMock); } void TearDown() override { } std::unique_ptr<Client> sut; std::shared_ptr<NiceMock<CommunicationMock>> CommMock; }; /* enum class MessageId { GET_RESOURCE_DATA, SET_CONFIG_DATA, DELETE_CONFIG_DATA, SUBSCRIBE_REQ };*/ struct FooParametricTest : public ClientTest, public WithParamInterface<std::tuple<MessageId, std::string>> { }; INSTANTIATE_TEST_CASE_P(FooParametricTestSuite, FooParametricTest, Values(std::make_tuple(MessageId::SUBSCRIBE_REQ, "SENS01"), std::make_tuple(MessageId::SET_CONFIG_DATA , "SENS02"), std::make_tuple(MessageId::GET_RESOURCE_DATA , "SENS03"), std::make_tuple(MessageId::DELETE_CONFIG_DATA, "SENS04")),); TEST_P(FooParametricTest, firsttest) { const auto paramTuple = GetParam(); const auto intToConvert = std::get<0>(paramTuple); const auto expectedResult = std::get<1>(paramTuple); EXPECT_CALL(*CommMock,send(Pointee(AllOf(Field(&Signal::messageId, Eq(static_cast<uint32_t>(intToConvert))), Field(&Signal::sensorName, StrEq(expectedResult)) ))) ); //.WillOnce(Return (expectedResult)); sut->sendMessage(intToConvert); }
Przykład zadania TDD wykorzystującego Testy parametryczne LINK
Rady:
- zamiast friendclass stwórz w siucie testowej klasę dziedziczącą po tej testowanej w niej dopisz metodę dostępową do testowanego obszaru
- SetUp jest miejscem gdzie można uruchamiać EXPECT_CALL, w konstruktorze makro nie zadziała
-nazwy testów bez podkreślników
-testowanie metod prywatnych...
nie stosuj:
#ifdef UNITTEST
friend class FooTest;
#endif
...
a już na pewno NIE ;-)
...
#ifdef UNITTEST
#define private public
#endif
zamiast tego jeśli są virtualne to w c++ można zmienić specyfikator z private na public w klasie testowej. Można również dodać virtual jako rozwiązanie!
-mockowanie nievirtualnych: Napisać mocklasę (nie dziedziczącą po oryginalnej) i wstrzykiwać ja do oryginalnego kodu (za pomocą #ifdef UNITTEST )
- mockowanie zwykłych funkcji nie metod klasy: make a wrapper class with a
method calling the function (with an interface) and use it instead.
- zaleca się stosować nice mocka by nie utrudniać pracy w przyszłości ale można sprawdzić podczas pisania jak się zachowuje z wyższym poziomem np tak: //#define NiceMock NaggyMock
błędy:
- uninteresting - zaskoczenie że została metoda wywołana bez expec_call
- unexpected calls - istnieją calle ale nie spełniają warunków
- unmatched - byłe xpec_call ale nie został wywołany
- do komunikacji między klasamo zawsze powinny być stosowane interfejsy, wtedy latwiej mockowac i testowac
-ustawiaj Expec_Calle w kolejności ich spodziewanego wywoływania!
- co zrobić jak mock jest uniquepointerem (zniknie) - unique mock wrapper jest rozwiązaniem
- od wersji google test 1.8.1 działą R-value czyli możemy przekazywać przez &&
Pliki: