TDD Calculator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Response to http://misko.hevery.com/2009/11/17/how-to-get-started-with-tdd/ | |
import unittest | |
class CalculatorTest(unittest.TestCase): | |
def setUp(self): | |
class MockView(object): | |
def __init__(self): | |
self.exp = "" | |
def set_expression(self, exp): | |
self.exp = exp | |
def get_expression(self): | |
return self.exp | |
self.view = MockView() | |
self.calculator = Calculator(self.view) | |
def testShouldEchoSingleNumberPress(self): | |
self._pushSequenceAndAssertEquals("1", "1") | |
def testShouldConcatenateNumberPresses(self): | |
self._pushSequenceAndAssertEquals("139", "139") | |
def testShouldDisplayNotEvaluatedExpression(self): | |
self._pushSequenceAndAssertEquals("1+3", "1+3") | |
def testShouldEvaluateExpressionOnEqualPress(self): | |
self._pushSequenceAndAssertEquals("1+3=", "4") | |
def testShouldEvaluateExpressionOnOperator(self): | |
self._pushSequenceAndAssertEquals("1+3+", "4+") | |
def testShouldHandleDecimalInput(self): | |
self._pushSequenceAndAssertEquals("38.7", "38.7") | |
def testShouldIngoreMultipleDecimalPoints(self): | |
self._pushSequenceAndAssertEquals("38.7..8..", "38.78") | |
def testShouldHandleAllBinOps(self): | |
self._pushSequenceAndAssertEquals("1+2*4/2-2=", "4") | |
def testShouldNotPrintIntAsFloat(self): | |
self._pushSequenceAndAssertEquals("4=", "4") | |
def testShouldRoundFloatToTwoDecimals(self): | |
self._pushSequenceAndAssertEquals("1.251=", "1.25") | |
def testShouldAddDecimalNumbers(self): | |
self._pushSequenceAndAssertEquals("4.5+3.15=", "7.65") | |
def testShouldIgnoreOperatorBeforeNumber(self): | |
self._pushSequenceAndAssertEquals("+*34", "34") | |
def testShouldIgnoreOperatorAfterOperator(self): | |
self._pushSequenceAndAssertEquals("4/*-=", "4/") | |
def testShouldDisplayNegativeNumber(self): | |
self._pushSequenceAndAssertEquals("4-6=", "-2") | |
def _pushSequenceAndAssertEquals(self, sequence, shouldEqual): | |
for char in sequence: | |
self.calculator.push(char) | |
self.assertEquals(shouldEqual, self.view.get_expression()) | |
class Calculator(object): | |
def __init__(self, view): | |
self.view = view | |
self.tokens = [""] | |
def push(self, char): | |
if char in "=+-*/": | |
if self._is_last_token_number(): | |
self._eval() | |
if char != "=": | |
self.tokens.append(char) | |
self.tokens.append("") | |
elif char != "." or "." not in self.tokens[-1]: | |
self.tokens[-1] += char | |
self._display_expression() | |
def _is_last_token_number(self): | |
try: | |
float(self.tokens[-1]) | |
return True | |
except Exception: | |
return False | |
def _eval(self): | |
if len(self.tokens) == 3: | |
self._eval_binop() | |
elif len(self.tokens) == 1: | |
number, is_float = self._pop_number() | |
self._append_number(number, is_float) | |
def _pop_number(self): | |
numstr = self.tokens.pop(0) | |
if "." in numstr: | |
return (float(numstr), True) | |
return (int(numstr), False) | |
def _append_number(self, number, is_float): | |
rounded_number = number | |
if is_float: | |
rounded_number = round(number, 2) | |
self.tokens.append(str(rounded_number)) | |
def _eval_binop(self): | |
op_map = { | |
"+": lambda x, y: x + y, | |
"-": lambda x, y: x - y, | |
"*": lambda x, y: x * y, | |
"/": lambda x, y: x / y, | |
} | |
n1, n1_is_float = self._pop_number() | |
op = self.tokens.pop(0) | |
n2, n2_is_float = self._pop_number() | |
res = op_map[op](n1, n2) | |
res_is_float = n1_is_float or n2_is_float | |
self._append_number(res, res_is_float) | |
def _display_expression(self): | |
self.view.set_expression("".join(self.tokens)) | |
if __name__ == '__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment