diff --git a/tdd_intro/demo/02_word_count/02_word_count.pro b/tdd_intro/demo/02_word_count/02_word_count.pro new file mode 100644 index 00000000..e1123a37 --- /dev/null +++ b/tdd_intro/demo/02_word_count/02_word_count.pro @@ -0,0 +1,10 @@ +include(../../gtest.pri) + +TEMPLATE = app +CONFIG += console c++11 +CONFIG -= app_bundle +CONFIG -= qt + +SOURCES += \ + test.cpp + diff --git a/tdd_intro/demo/02_word_count/test.cpp b/tdd_intro/demo/02_word_count/test.cpp new file mode 100644 index 00000000..e8ee188b --- /dev/null +++ b/tdd_intro/demo/02_word_count/test.cpp @@ -0,0 +1,132 @@ +/* +Given a phrase, count the occurrences of each word in that phrase. Ignore whitespaces and punctual symbols +For example for the input "olly olly in come free please please let it be in such manner olly" +olly: 3 +in: 2 +come: 1 +free: 1 +please: 2 +let: 1 +it: 1 +be: 1 +manner: 1 +such: 1 +*/ + +#include +#include +#include + +using words_mt = std::map; +const static char wordSeparator = ' '; + +bool TrimWord(std::string& phrase, std::string& word, const char seperator) +{ + if (phrase.empty()) + { + return false; + } + + size_t index = phrase.find_first_of(seperator); + if (index == std::string::npos || index == phrase.size()) + { + word = phrase; + phrase.clear(); + } + else + { + word = phrase.substr(0, index); + phrase = phrase.substr(index + 1, phrase.size() - index); + } + + return true; +} + +words_mt SeparateWords(const std::string& phrase) +{ + words_mt words; + std::string currentPhrase = phrase; + std::string curentWord; + + while (TrimWord(currentPhrase, curentWord, wordSeparator)) + { + ++words[curentWord]; + } + + return words; +} + +TEST(WordsCount, TestSeparateFirstWord) +{ + words_mt words = SeparateWords("hello"); + EXPECT_EQ(1, words.size()); + EXPECT_TRUE(words.find("hello") != words.end()); + EXPECT_EQ(1, words["hello"]); +} + +TEST(WordsCount, TestTrimOneWord) +{ + std::string phrase = "tdd course"; + std::string word; + EXPECT_TRUE(TrimWord(phrase, word, ' ')); + EXPECT_EQ("course", phrase); + + EXPECT_TRUE(TrimWord(phrase, word, ' ')); + EXPECT_EQ("", phrase); + + EXPECT_FALSE(TrimWord(phrase, word, ' ')); +} + +TEST(WordsCount, TestCountSeveralDifferentWords) +{ + words_mt words = SeparateWords("hello bro"); + EXPECT_EQ(2, words.size()); + + EXPECT_TRUE(words.find("hello") != words.end()); + EXPECT_EQ(1, words["hello"]); + + EXPECT_TRUE(words.find("bro") != words.end()); + EXPECT_EQ(1, words["bro"]); +} + +TEST(WordsCount, TestCountSeveralSameWords) +{ + words_mt words = SeparateWords("hello bro hello"); + + EXPECT_EQ(2, words.size()); + EXPECT_TRUE(words.find("hello") != words.end()); + EXPECT_EQ(2, words["hello"]); +} + +TEST(WordsCount, TestNoWordsWhenEmptyInput) +{ + words_mt words = SeparateWords(""); + EXPECT_TRUE(words.empty()); +} + +TEST(WordsCount, TestOneWordWhenSeparatorInEnd) +{ + words_mt words = SeparateWords("hello "); + + EXPECT_EQ(1, words.size()); + EXPECT_TRUE(words.find("hello") != words.end()); + EXPECT_EQ(1, words["hello"]); +} + +TEST(WordsCount, AcceptanceTest) +{ + words_mt words = SeparateWords("olly olly in come free please please let it be in such manner olly"); + + ASSERT_EQ(10, words.size()); + + ASSERT_EQ(3, words["olly"]); + ASSERT_EQ(2, words["in"]); + ASSERT_EQ(1, words["come"]); + ASSERT_EQ(1, words["free"]); + ASSERT_EQ(2, words["please"]); + ASSERT_EQ(1, words["let"]); + ASSERT_EQ(1, words["it"]); + ASSERT_EQ(1, words["be"]); + ASSERT_EQ(1, words["manner"]); + ASSERT_EQ(1, words["such"]); +} diff --git a/tdd_intro/demo/demo.pro b/tdd_intro/demo/demo.pro index c2905cb9..9e9ba630 100644 --- a/tdd_intro/demo/demo.pro +++ b/tdd_intro/demo/demo.pro @@ -4,6 +4,7 @@ SUBDIRS += \ 01_bob \ 01_fizz_buzz \ 02_anagram \ + 02_word_count \ #03_allergies \ 03_roman_numerals \ 04_timer diff --git a/tdd_intro/homework/01_leap_year/test.cpp b/tdd_intro/homework/01_leap_year/test.cpp index 4f186c8b..19bae075 100644 --- a/tdd_intro/homework/01_leap_year/test.cpp +++ b/tdd_intro/homework/01_leap_year/test.cpp @@ -13,3 +13,58 @@ If your language provides a method in the standard library that does this look-u */ #include + + +const size_t g_four = 4; +const size_t g_oneHundred = 100; +const size_t g_fourHundred = g_four * g_oneHundred; + +bool isDivisible(size_t number, size_t divider) +{ + return number % divider == 0; +} + +bool IsLeapYear(size_t year) +{ + if (isDivisible(year, g_fourHundred)) + { + return true; + } + if (isDivisible(year, g_oneHundred)) + { + return false; + } + if (isDivisible(year, g_four)) + { + return true; + } + + return false; +} + +TEST(LeapYear, TestDivisible400) +{ + EXPECT_TRUE(IsLeapYear(g_fourHundred)); + EXPECT_TRUE(IsLeapYear(g_fourHundred * 2)); + EXPECT_TRUE(IsLeapYear(g_fourHundred * 3)); +} + +TEST(LeapYear, TestDivisible4AndNot100) +{ + EXPECT_TRUE(IsLeapYear(104)); + EXPECT_TRUE(IsLeapYear(404)); + EXPECT_TRUE(IsLeapYear(2008)); + EXPECT_TRUE(IsLeapYear(2012)); +} + +TEST(LeapYear, TestDivisible100) +{ + EXPECT_FALSE(IsLeapYear(g_oneHundred)); + EXPECT_FALSE(IsLeapYear(g_oneHundred * 5)); +} + +TEST(LeapYear, TestNotDivisible4_400_100) +{ + EXPECT_FALSE(IsLeapYear(333)); + EXPECT_FALSE(IsLeapYear(555)); +} diff --git a/tdd_intro/homework/02_ternary_numbers/test.cpp b/tdd_intro/homework/02_ternary_numbers/test.cpp index 17503028..2245f55e 100644 --- a/tdd_intro/homework/02_ternary_numbers/test.cpp +++ b/tdd_intro/homework/02_ternary_numbers/test.cpp @@ -1,4 +1,6 @@ #include +#include +#include /* Convert a ternary number, represented as a string (e.g. '102012'), to its decimal equivalent using first principles. @@ -16,3 +18,72 @@ The last place in a ternary number is the 1's place. The second to last is the 3 If your language provides a method in the standard library to perform the conversion, pretend it doesn't exist and implement it yourself. */ + +size_t ConvertTernaryToDecimal(size_t numerical, size_t position) +{ + if(numerical > 2) + { + return 0; + } + + double ternaryPow = std::pow(3, position); + return static_cast(ternaryPow) * numerical; +} + +size_t ConvertTernaryToDecimal(const std::string& ternary) +{ + size_t result = 0; + size_t position = ternary.size(); + for (const char ternarySymbChar: ternary) + { + int ternaryNumb = std::atoi(&ternarySymbChar); + result += ConvertTernaryToDecimal(static_cast(ternaryNumb), --position); + } + + return result; +} + +TEST(TernaryNumber, TestConvertTernaryNum_Pos0_ToDecimal) +{ + EXPECT_EQ(0, ConvertTernaryToDecimal(0, 0)); + EXPECT_EQ(1, ConvertTernaryToDecimal(1, 0)); + EXPECT_EQ(2, ConvertTernaryToDecimal(2, 0)); +} + +TEST(TernaryNumber, TestConvertTernaryNum_Larger0_ToDecimal) +{ + EXPECT_EQ(3, ConvertTernaryToDecimal(1, 1)); + EXPECT_EQ(0, ConvertTernaryToDecimal(0, 2)); + EXPECT_EQ(54, ConvertTernaryToDecimal(2, 3)); +} + +TEST(TernaryNumber, TestConvertInvalidTernary) +{ + EXPECT_EQ(0, ConvertTernaryToDecimal(4, 0)); + EXPECT_EQ(0, ConvertTernaryToDecimal(5, 0)); + EXPECT_EQ(0, ConvertTernaryToDecimal(20, 0)); +} + +TEST(TernaryNumber, TestConvertOneTernaryNumericalStrToDecimal) +{ + EXPECT_EQ(0, ConvertTernaryToDecimal("0")); + EXPECT_EQ(1, ConvertTernaryToDecimal("1")); + EXPECT_EQ(2, ConvertTernaryToDecimal("2")); + EXPECT_EQ(0, ConvertTernaryToDecimal("3")); +} + +TEST(TernaryNumber, TestConvertTernaryStrToDecimal) +{ + EXPECT_EQ(302, ConvertTernaryToDecimal("102012")); +} + +TEST(TernaryNumber, TestEmptyTernaryString) +{ + EXPECT_EQ(0, ConvertTernaryToDecimal("")); +} + +TEST(TernaryNumber, TestIncorrectTernaryNumberInString) +{ + EXPECT_EQ(302, ConvertTernaryToDecimal("192612")); + EXPECT_EQ(302, ConvertTernaryToDecimal("1*2%12")); +} diff --git a/tdd_intro/homework/03_bank_ocr/test.cpp b/tdd_intro/homework/03_bank_ocr/test.cpp index a01540b9..248f7b7c 100644 --- a/tdd_intro/homework/03_bank_ocr/test.cpp +++ b/tdd_intro/homework/03_bank_ocr/test.cpp @@ -100,6 +100,19 @@ struct Display std::string lines[g_linesInDigit]; }; +const std::map s_digitsMap{ + { " _ | ||_|", '0'}, + { " | |", '1'}, + { " _ _||_ ", '2'}, + { " _ _| _|", '3'}, + { " |_| |", '4'}, + { " _ |_ _|", '5'}, + { " _ |_ |_|", '6'}, + { " _ | |", '7'}, + { " _ |_||_|", '8'}, + { " _ |_| _|", '9'} +}; + const Digit s_digit0 = { " _ ", "| |", "|_|" @@ -195,3 +208,118 @@ const Display s_display123456789 = { " _ _ _ _ _ _ _ ", " | _| _||_||_ |_ ||_||_|", " ||_ _| | _||_| ||_| _|" }; + +char DetectDigit(const Digit& digit) +{ + const std::string digitHash = digit.lines[0] + digit.lines[1] + digit.lines[2]; + + const auto& value = s_digitsMap.find(digitHash); + if (value != s_digitsMap.end()) + { + return value->second; + } + + throw std::runtime_error("Invalid digit format"); +} + +Digit ParseDigitFromDisplay(const Display& display, size_t digitPos) +{ + if (digitPos > 9) + { + throw std::runtime_error("Invalid display offset"); + } + + Digit digit; + const size_t offset = digitPos * g_digitLen; + for(size_t i = 0; i < g_linesInDigit; ++i) + { + digit.lines[i] = display.lines[i].substr(offset, g_digitLen); + } + + return digit; +} + + +std::string ParseDigits(const Display& display) +{ + std::string finalDigits; + for (size_t i = 0; i < g_digitsOnDisplay; ++i) + { + Digit digit = ParseDigitFromDisplay(display, i); + finalDigits += DetectDigit(digit); + } + + return finalDigits; +} + +TEST(BankOcr, TestDetectValidDigits) +{ + EXPECT_EQ('0', DetectDigit(s_digit0)); + EXPECT_EQ('1', DetectDigit(s_digit1)); + EXPECT_EQ('2', DetectDigit(s_digit2)); + EXPECT_EQ('3', DetectDigit(s_digit3)); + EXPECT_EQ('4', DetectDigit(s_digit4)); + EXPECT_EQ('5', DetectDigit(s_digit5)); + EXPECT_EQ('6', DetectDigit(s_digit6)); + EXPECT_EQ('7', DetectDigit(s_digit7)); + EXPECT_EQ('8', DetectDigit(s_digit8)); + EXPECT_EQ('9', DetectDigit(s_digit9)); +} + +TEST(BankOcr, TestThrowExceptionWhenDetectInvalidDigits) +{ + const static Digit invalidDigit = { " _ ", + "| |", + "| |"}; + + EXPECT_THROW(DetectDigit(invalidDigit), std::runtime_error); +} + +TEST(BankOcr, TestParseFirstDigitFromDisplay0) +{ + EXPECT_EQ('0', ParseDigits(s_displayAll0)[0]); +} + +TEST(BankOcr, TestParseFirstDigitFromDisplay1) +{ + EXPECT_EQ('2', ParseDigits(s_display123456789)[1]); +} + +TEST(BankOcr, TestParseDigit1FromDisplayNumber0) +{ + const Digit digit = ParseDigitFromDisplay(s_display123456789, 0); + + EXPECT_EQ(s_digit1.lines[0], digit.lines[0]); + EXPECT_EQ(s_digit1.lines[1], digit.lines[1]); + EXPECT_EQ(s_digit1.lines[2], digit.lines[2]); +} + +TEST(BankOcr, TestParseDigit2FromDisplayNumber2) +{ + const Digit digit = ParseDigitFromDisplay(s_display123456789, 1); + + EXPECT_EQ(s_digit2.lines[0], digit.lines[0]); + EXPECT_EQ(s_digit2.lines[1], digit.lines[1]); + EXPECT_EQ(s_digit2.lines[2], digit.lines[2]); +} + +TEST(BankOcr, TestExceptionWhenParseUnknownDisplayOffset) +{ + EXPECT_THROW(ParseDigitFromDisplay(s_display123456789, 10), std::runtime_error); +} + +TEST(BankOcr, AcceptanceTest) +{ + EXPECT_EQ("000000000", ParseDigits(s_displayAll0)); + EXPECT_EQ("111111111", ParseDigits(s_displayAll1)); + EXPECT_EQ("222222222", ParseDigits(s_displayAll2)); + EXPECT_EQ("333333333", ParseDigits(s_displayAll3)); + EXPECT_EQ("444444444", ParseDigits(s_displayAll4)); + EXPECT_EQ("555555555", ParseDigits(s_displayAll5)); + EXPECT_EQ("666666666", ParseDigits(s_displayAll6)); + EXPECT_EQ("777777777", ParseDigits(s_displayAll7)); + EXPECT_EQ("888888888", ParseDigits(s_displayAll8)); + EXPECT_EQ("999999999", ParseDigits(s_displayAll9)); + EXPECT_EQ("123456789", ParseDigits(s_display123456789)); + +} diff --git a/tdd_intro/homework/04_weather_client/test.cpp b/tdd_intro/homework/04_weather_client/test.cpp index 346ea809..8d91384e 100644 --- a/tdd_intro/homework/04_weather_client/test.cpp +++ b/tdd_intro/homework/04_weather_client/test.cpp @@ -47,6 +47,13 @@ Each line means "" : "": #include #include +static const char s_responseDataSeprator = ';'; + +static const std::string s_nineAM = "09:00"; +static const std::string s_ninePM = "21:00"; +static const std::string s_threeAM = "03:00"; +static const std::string s_threePM = "15:00"; + struct Weather { short temperature = 0; @@ -60,6 +67,34 @@ struct Weather } }; +double GetLineDoble(std::istringstream& stream) +{ + std::string value; + if (!std::getline(stream, value, s_responseDataSeprator)) + { + throw std::runtime_error("Error parsing"); + } + + return std::stod(value); +} + +Weather ParseWeather(const std::string& response) +{ + Weather weather; + std::istringstream responseStream(response); + + weather.temperature = static_cast(GetLineDoble(responseStream)); + weather.windDirection = static_cast(GetLineDoble(responseStream)); + if(weather.windDirection > 359) + { + throw std::runtime_error("Invalid paserd value for wind direction"); + } + + weather.windSpeed = GetLineDoble(responseStream); + + return weather; +} + class IWeatherServer { public: @@ -68,6 +103,12 @@ class IWeatherServer virtual std::string GetWeather(const std::string& request) = 0; }; +class MockWeatherServer : public IWeatherServer +{ +public: + MOCK_METHOD1(GetWeather, std::string(const std::string& request)); +}; + // Implement this interface class IWeatherClient { @@ -79,3 +120,113 @@ class IWeatherClient virtual double GetAverageWindDirection(IWeatherServer& server, const std::string& date) = 0; virtual double GetMaximumWindSpeed(IWeatherServer& server, const std::string& date) = 0; }; + + +class WeatherClient : public IWeatherClient +{ +public: + virtual double GetAverageTemperature(IWeatherServer& server, const std::string& date) override + { + throw std::runtime_error("not implemented"); + } + virtual double GetMinimumTemperature(IWeatherServer& server, const std::string& date) + { + std::set temperatures; + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_threeAM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_nineAM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_threePM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_ninePM).temperature); + + return *temperatures.begin(); + } + + virtual double GetMaximumTemperature(IWeatherServer& server, const std::string& date) override + { + std::set temperatures; + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_threeAM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_nineAM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_threePM).temperature); + temperatures.emplace(GetWeatherByDate(server, date + s_responseDataSeprator + s_ninePM).temperature); + + return *temperatures.rbegin(); + } + + virtual double GetAverageWindDirection(IWeatherServer& server, const std::string& date) override + { + throw std::runtime_error("not implemented"); + } + + virtual double GetMaximumWindSpeed(IWeatherServer& server, const std::string& date) override + { + throw std::runtime_error("not implemented"); + } + +private: + Weather GetWeatherByDate(IWeatherServer& server, const std::string& date) + { + std::string response = server.GetWeather(date); + return ParseWeather(response); + } +}; + +TEST(WeatherClient, TestParseTemperatureFromResponse) +{ + Weather weather = ParseWeather("1;1;1"); + EXPECT_EQ(1, weather.temperature); +} + +TEST(WeatherClient, TestThrowWhenCannotParseTemperature) +{ + EXPECT_THROW(ParseWeather("1"), std::runtime_error); +} + +TEST(WeatherClient, TestParseWindDirectionFromResponse) +{ + Weather weather = ParseWeather("1;1;1"); + EXPECT_EQ(1, weather.windDirection); +} + +TEST(WeatherClient, TestParseInvalidWindDirectionFromResponse) +{ + EXPECT_THROW(ParseWeather("1;360;1"), std::runtime_error); +} +TEST(WeatherClient, TestParseWindSpeedFromResponse) +{ + Weather weather = ParseWeather("1;1;1"); + EXPECT_EQ(1, weather.windSpeed); +} + +TEST(WeatherClient, TestParseDoubleWindSpeedFromResponse) +{ + Weather weather = ParseWeather("1;1;4.6"); + EXPECT_EQ(4.6, weather.windSpeed); +} + +//Test client + +TEST(WeatherClient, TestGetMinimumTemperature_21pm) +{ + MockWeatherServer server; + WeatherClient client; + + EXPECT_CALL(server, GetWeather("31.08.2018;03:00")).WillOnce(testing::Return("20;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;09:00")).WillOnce(testing::Return("6;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;15:00")).WillOnce(testing::Return("7;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;21:00")).WillOnce(testing::Return("5;0;0")); + + EXPECT_EQ(5, client.GetMinimumTemperature(server, "31.08.2018")); +} + +TEST(WeatherClient, TestGetMaximumTemperature_3pm) +{ + MockWeatherServer server; + WeatherClient client; + + EXPECT_CALL(server, GetWeather("31.08.2018;03:00")).WillOnce(testing::Return("20;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;09:00")).WillOnce(testing::Return("6;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;15:00")).WillOnce(testing::Return("30;0;0")); + EXPECT_CALL(server, GetWeather("31.08.2018;21:00")).WillOnce(testing::Return("5;0;0")); + + EXPECT_EQ(30, client.GetMaximumTemperature(server, "31.08.2018")); +} +