Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init calculator unit test #10

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5.1)
project(dummy_cmake_project)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

set(SOURCES project.cc)
Expand Down
166 changes: 166 additions & 0 deletions src/Calculator/calculator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#include "include/calculator.h"
#include <iostream>
#include <algorithm>
#include <sstream>
#include <iterator>

Calculator::Calculator()
{
initOperators();
}

double Calculator::calculate(std::string input)
{
clear();

if (!parseInput(input)) {
return 0.0;
}

for (int priority = maxOperatorsPriority(); priority >= 0; priority--) {
for (std::size_t currentOperatorIndex = 0;
currentOperatorIndex < m_operators.size();
currentOperatorIndex++) {
if (operatorPriority(m_operators.at(currentOperatorIndex)) == priority) {
if (!intermediateCalculation(currentOperatorIndex)) {
return 0.0;
}
currentOperatorIndex -= 1;
}
}
}

return m_operands.front();
}

std::string Calculator::error() const
{
return m_error;
}

bool Calculator::parseInput(std::string input)
{
std::istringstream inputStream(input);
std::vector<std::string> tokens(std::istream_iterator<std::string>{inputStream},
std::istream_iterator<std::string>());

for (auto token : tokens) {

if (token.size() == 1 && isOperator(token.at(0))) {
m_operators.push_back(token.at(0));
continue;
}

if (isNumber(token)) {
m_operands.push_back(atof(token.c_str()));
continue;
}

m_error = "input simbol: " + token + " not supported!";
return false;
}

if (m_operands.size() - 1 != m_operators.size()) {
m_error = "input format error! number of operands and number of operators mismach!";
return false;
}

return true;
}

void Calculator::clear()
{
m_operands = {};
m_operators = {};
m_error.clear();
}


bool Calculator::isOperator(char oper) {
bool Ok = m_availableOperators.find(oper) != m_availableOperators.end();
return Ok;
}

bool Calculator::isNumber(std::string token)
{
// consider negative numbers
if (token.at(0) == '-') {
token.erase(0, 1);
}
bool isDigit = !token.empty() &&
std::find_if(token.begin(),
token.end(),
[](unsigned char c) {
return !std::isdigit(c);
}
) == token.end();
return isDigit;
}

bool Calculator::intermediateCalculation(u_int operator_idx) {

auto oper = m_operators.at(operator_idx);
auto op1 = m_operands.at(operator_idx);
auto op2 = m_operands.at(operator_idx + 1);

double tmp_res{0.0};

switch (oper) {

case '*' :
tmp_res = op1 * op2;
break;

case '/' :
if (op2 == 0) {
m_error = "division by zero is undefined";
return false;
}
tmp_res = op1 / op2;
break;

case '+' :
tmp_res = op1 + op2;
break;

case '-' :
tmp_res = op1 - op2;
break;

default:
m_error = "Operator not implemented!";
return false;
}

m_operators.erase(m_operators.begin() + operator_idx);
m_operands[operator_idx] = tmp_res;
m_operands.erase(m_operands.begin() + operator_idx + 1);

return true;
}

void Calculator::initOperators()
{
m_availableOperators.insert({'*', 1});
m_availableOperators.insert({'/', 1});
m_availableOperators.insert({'+', 0});
m_availableOperators.insert({'-', 0});
}

int Calculator::maxOperatorsPriority()
{
using u_map_type = decltype(m_availableOperators)::value_type;
auto pr = std::max_element
(
std::begin(m_availableOperators), std::end(m_availableOperators),
[] (const u_map_type & p1, const u_map_type & p2) {
return p1.second < p2.second;
}
);
return pr->first;
}

int Calculator::operatorPriority(const char &oper)
{
return m_availableOperators.at(oper);
}
30 changes: 30 additions & 0 deletions src/Calculator/include/calculator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <string>
#include <unordered_map>
#include <vector>

class Calculator{

public:
Calculator();

double calculate(std::string input);
std::string error() const;

private:

GoranShekerov marked this conversation as resolved.
Show resolved Hide resolved
void initOperators();
void clear();
bool parseInput(std::string input);
bool intermediateCalculation(u_int operator_idx);
bool isOperator(char oper);
bool isNumber(std::string token);
int maxOperatorsPriority();
int operatorPriority(const char& oper);

std::vector<double> m_operands;
std::vector<char> m_operators;
std::unordered_map<char, u_char> m_availableOperators;
std::string m_error;
};
5 changes: 5 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ target_link_libraries(ProjectTest PUBLIC ProjectLib)

add_executable (RunTest ${CMAKE_SOURCE_DIR}/run.cc run_tests.cc)
add_gtest(RunTest)

add_executable(CalculatorTest
${CMAKE_CURRENT_SOURCE_DIR}/calculator_test.cpp
${CMAKE_SOURCE_DIR}/src/Calculator/calculator.cpp)
add_gtest(CalculatorTest)
69 changes: 69 additions & 0 deletions test/calculator_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <gtest/gtest.h>

#include "../src/Calculator/include/calculator.h"

#include <iostream>

class CalculatorTest : public ::testing::Test {

public:
Calculator calculator;
};

//// TDD + extreme programing
//// basic design calc(std::sting) -> double

TEST_F(CalculatorTest, Init) {
ASSERT_EQ(1, 1);
}

TEST_F(CalculatorTest, AddPositive) {
ASSERT_EQ(calculator.calculate("2 3 +"), 5);
}

TEST_F(CalculatorTest, AddNegative) {
ASSERT_EQ(calculator.calculate("-2 -3 +"), -5);
}

TEST_F(CalculatorTest, Substract) {
ASSERT_EQ(calculator.calculate("5 2 -"), 3);
}

TEST_F(CalculatorTest, Multiply) {
ASSERT_EQ(calculator.calculate("3 7 *"), 21);
}

TEST_F(CalculatorTest, Devide) {
ASSERT_EQ(calculator.calculate("21 7 /"), 3);
}

TEST_F(CalculatorTest, MultiplyByZero) {
ASSERT_EQ(calculator.calculate("21 0 *"), 0);
}

TEST_F(CalculatorTest, DevideByZero) {
ASSERT_EQ(calculator.calculate("21 0 /"), 0);
ASSERT_EQ(calculator.error(), "division by zero is undefined");
}

TEST_F(CalculatorTest, ComplexAdd) {
ASSERT_EQ(calculator.calculate("2 3 6 + +"), 11);
}

TEST_F(CalculatorTest, ComplexPriorityOperation) {
ASSERT_EQ(calculator.calculate("2 3 6 + *"), 20);
GoranShekerov marked this conversation as resolved.
Show resolved Hide resolved
}

TEST_F(CalculatorTest, ComplexPriorityOperationMultiplyByZero) {
ASSERT_EQ(calculator.calculate("2 3 0 + *"), 2);
}

TEST_F(CalculatorTest, ComplexPriorityOperationDevideByZero) {
ASSERT_EQ(calculator.calculate("2 3 0 + /"), 0);
ASSERT_EQ(calculator.error(), "division by zero is undefined");
}

TEST_F(CalculatorTest, SimbolNotSuported) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not enough tests for negative test cases

ASSERT_EQ(calculator.calculate("2 3 #"), 0);
ASSERT_EQ(calculator.error(), "input simbol: # not supported!");
}