-
Notifications
You must be signed in to change notification settings - Fork 23
Anton Charnichenka #1
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
base: master
Are you sure you want to change the base?
Changes from all commits
fa6a37b
3e2ed9b
ada2cc8
712dfab
57f69ba
ce7661d
0e5a5fa
4aefbfe
1a135a1
1150f6d
036fa96
63f81b6
ddaef8c
03d82b6
814f33c
b31cfc1
75d4d64
6173c05
d3eff94
2e7747f
d3e553d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,266 @@ | ||
| """This module contains unit tests for methods and functions from all pycalc modules""" | ||
|
|
||
| # import | ||
| import unittest | ||
| from .tokenizer import Tokenizer | ||
| from .addmultsigns import Multsignsadder | ||
| from .rpn import RPN | ||
| from .constsreplacer import Constsreplacer | ||
| from .rpncalculator import RPNcalculator | ||
| from .utils import is_number | ||
| from .pycalclib import Pycalclib | ||
|
|
||
| # create pycalclib | ||
| pycalclib = Pycalclib(user_module='test_user_module') | ||
|
|
||
|
|
||
| # Tests of 'Tokenizer' class from 'tokenizer' module | ||
| class TokenizerTestCase(unittest.TestCase): | ||
| """Tests for Tokenizer class""" | ||
|
|
||
| def test_extract_operators_and_pos_int_numbers(self): | ||
| """Are operators and positive int numbers extracted properly?""" | ||
| user_expr = '1+2-3*4/5^6**7//8%9' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['1', '+', '2', '-', '3', '*', '4', '/', | ||
| '5', '^', '6', '**', '7', '//', '8', '%', '9']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_operators_and_neg_int_numbers(self): | ||
| """Are operators and negative int numbers extracted properly?""" | ||
| user_expr = '-1+-2--3*-4/-5^-6**-7//-8%-9' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['-1.0', '-', '2', '+', '3', '*', '-4', '/', '-5', | ||
| '^', '-6', '**', '-7', '//', '-8', '%', '-9']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_pos_float_numbers(self): | ||
| """Are positive float numbers extracted properly?""" | ||
| user_expr = '0.1+1.55-112.12' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['0.1', '+', '1.55', '-', '112.12']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_neg_float_numbers(self): | ||
| """Are negative float numbers extracted properly?""" | ||
| user_expr = '-0.1+-1.55--112.12' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['-0.1', '-', '1.55', '+', '112.12']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_comparison_operators(self): | ||
| """Are comparison operators extracted properly?""" | ||
| user_expr = '><>=<=!===' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['>', '<', '>=', '<=', '!=', '==']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_pos_constants(self): | ||
| """Are positive constants extracted properly?""" | ||
| user_expr = 'e+pi-tau/inf*nan' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['e', '+', 'pi', '-', 'tau', '/', 'inf', '*', 'nan']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_neg_constants(self): | ||
| """Are negative constants extracted properly?""" | ||
| user_expr = '-e+-pi--tau/-inf*-nan' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['-e', '-', 'pi', '+', 'tau', '/', '-inf', '*', '-nan']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_brackets(self): | ||
| """Are brackets extracted properly?""" | ||
| user_expr = '()' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['(', ')']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_comma(self): | ||
| """Is comma extracted?""" | ||
| user_expr = 'pow(2,3)' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['pow', '(', '2', ',', '3', ')']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_functions(self): | ||
| """Are functions extracted properly?""" | ||
| user_expr = "round(sin(2)-asin(1))-abs(exp(3))" | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['round', '(', 'sin', '(', '2', ')', '-', 'asin', '(', '1', ')', ')', | ||
| '-', 'abs', '(', 'exp', '(', '3', ')', ')']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_consider_sub_signs_method(self): | ||
| """Are several subtraction and addition signs replaced by one integrated sign?""" | ||
| user_expr = '-1---2+-3+++4+-2' | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(tokens, ['-1.0', '-', '2', '-', '3', '+', '4', '-', '2']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_extract_tokens_error_msg(self): | ||
| """Is error_message created?""" | ||
| user_expr = "2+shikaka(3)" | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| self.assertEqual(error_msg, 'ERROR: invalid syntax') | ||
|
|
||
|
|
||
| # Tests of 'Multsignsadder' class from 'addmultsigns' module | ||
| class MultsignsadderTestCase(unittest.TestCase): | ||
| """Tests for Multsignsadder class""" | ||
|
|
||
| def test_addmultsigns_add_mult_signs(self): | ||
| """Are multiplication signs added to where they implicit were to be in expression?""" | ||
| tokens = ['5', 'tau', '-', '4', 'sin', '(', '7', ')', 'sin', '(', '3', ')', '-', '9', '(', '1', '+', '10', ')'] | ||
| mult_signs_adder = Multsignsadder(tokens, pycalclib) | ||
| extd_tokens = mult_signs_adder.addmultsigns() | ||
| self.assertEqual(extd_tokens, ['5', '*', 'tau', '-', '4', '*', 'sin', '(', '7', ')', '*', 'sin', '(', '3', ')', | ||
| '-', '9', '*', '(', '1', '+', '10', ')']) | ||
|
|
||
| def test_addmultsigns_dont_add_mult_signs(self): | ||
| """Aren't multiplication signs added if it's not needed?""" | ||
| tokens = ['2', '+', '3', '*', '5'] | ||
| mult_signs_adder = Multsignsadder(tokens, pycalclib) | ||
| extd_tokens = mult_signs_adder.addmultsigns() | ||
| self.assertEqual(extd_tokens, ['2', '+', '3', '*', '5']) | ||
|
|
||
| def test_consider_neg_funcs_method(self): | ||
| """Are negative functions tokens replaced by '-1*function' tokens?""" | ||
| tokens = ['2', '*', '-sin', '(', '2', ')'] | ||
| mult_signs_adder = Multsignsadder(tokens, pycalclib) | ||
| mult_signs_adder.consider_neg_functions(mult_signs_adder.tokens) | ||
| self.assertEqual(mult_signs_adder.tokens, ['2', '*', '-1', '*', 'sin', '(', '2', ')']) | ||
|
|
||
| def test_consider_log_args_method(self): | ||
| """Is 'e' added as a base for log function if last was entered with one argument?""" | ||
| tokens = ['log', '(', '33', ')'] | ||
| mult_signs_adder = Multsignsadder(tokens, pycalclib) | ||
| mult_signs_adder.consider_log_args(mult_signs_adder.tokens) | ||
| self.assertEqual(mult_signs_adder.tokens, ['log', '(', '33', ',', 'e', ')']) | ||
|
|
||
|
|
||
| # Tests of 'RPN class' from 'rpn' module | ||
| class RPNTestCase(unittest.TestCase): | ||
| """Tests for RPN class""" | ||
|
|
||
| def test_is_left_associative_method(self): | ||
| """Are left associative operators recognized?""" | ||
| tokens = ['^', '**', '+', '/'] | ||
| rpn = RPN(tokens, pycalclib) | ||
| is_left_associative = [] | ||
| for token in tokens: | ||
| is_left_associative.append(rpn.is_left_associative(token)) | ||
| self.assertEqual(is_left_associative, [False, False, True, True]) | ||
|
|
||
| def test_convert2rpn_method(self): | ||
| """Does 'convert2rpn' method work correctly?""" | ||
| tokens = ['-pi', '*', 'round', '(', '2.23', ')', '//', '5', '*', 'pow', '(', '2', '3', ')'] | ||
| rpn = RPN(tokens, pycalclib) | ||
| result, error_msg = rpn.convert2rpn() | ||
| self.assertEqual(result, ['-pi', '2.23', 'round', '*', '5', '//', '2', '3', 'pow', '*']) | ||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_convert2rpn_method_error_msg(self): | ||
| """Is error_message created?""" | ||
| tokens = ['(', '2', '+', '3', ')', ')'] | ||
| rpn = RPN(tokens, pycalclib) | ||
| result, error_msg = rpn.convert2rpn() | ||
| self.assertEqual(error_msg, 'ERROR: brackets are not balanced') | ||
|
|
||
|
|
||
| # Tests of 'Constsreplacer' class from 'constsreplacer' module | ||
| class ConstsreplacerTestCase(unittest.TestCase): | ||
| """Tests for Constsreplacer class""" | ||
|
|
||
| def test_replace_constants_method(self): | ||
| """Are constants replaced and not constants aren't replaced?""" | ||
| tokens = ['e', '-e', 'pi', '-pi', 'tau', '-tau', '2', 'cos', 'inf', '-nan', '+'] | ||
| constsreplacer = Constsreplacer(tokens, pycalclib) | ||
| replaced_tokens = constsreplacer.replace_constants() | ||
| self.assertEqual(replaced_tokens, ['2.718281828459045', '-2.718281828459045', | ||
| '3.141592653589793', '-3.141592653589793', | ||
| '6.283185307179586', '-6.283185307179586', | ||
| '2', 'cos', 'inf', '-nan', '+']) | ||
|
|
||
|
|
||
| # Tests of 'RPNcalculator' class from 'rpncalculator' module | ||
| class RPNcalculatorTestCase(unittest.TestCase): | ||
| """Tests for RPNcalculator class""" | ||
|
|
||
| def test_evaluate_method_result(self): | ||
| """Does 'evaluate' method actually evaluate RPN math expression and give out correct result?""" | ||
| rpn_tokens = ['2', 'sqrt', '3', '/', '3.14', '*', 'tan'] | ||
| rpncalculator = RPNcalculator(rpn_tokens, pycalclib) | ||
| result, error_msg = rpncalculator.evaluate() | ||
| self.assertEqual(result, 11.009005500434151) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Возможно есть смысл заменить магическую константу в этой строке на выражение на языке Python, чтобы было четко видно, что эта функциональность должна повторять функциональность языка Python. Также в таких случаях можно использовать метод для проверки приблизительного равенства чисел |
||
| self.assertEqual(error_msg, None) | ||
|
|
||
| def test_evaluate_method_error_msg_zero_division(self): | ||
| """Is 'division by zero' error message created?""" | ||
| rpn_tokens = ['2', '0', '/'] | ||
| rpncalculator = RPNcalculator(rpn_tokens, pycalclib) | ||
| result, error_msg = rpncalculator.evaluate() | ||
| self.assertEqual(error_msg, 'ERROR: float division by zero') | ||
|
|
||
| def test_evaluate_method_error_msg_neg_num_in_fract_pow(self): | ||
| """Is 'negative number cannot be raised to a fractional power' error message created?""" | ||
| rpn_tokens = [['-2', '0.5', '**'], ['-2', '0.5', '^']] | ||
| error_msgs = [] | ||
| for rpn_tokens_list in rpn_tokens: | ||
| rpncalculator = RPNcalculator(rpn_tokens_list, pycalclib) | ||
| error_msgs.append(rpncalculator.evaluate()[1]) | ||
| for error_msg in error_msgs: | ||
| self.assertEqual(error_msg, 'ERROR: negative number cannot be raised to a fractional power') | ||
|
|
||
| def test_evaluate_method_error_msg_neg_num_sqrt(self): | ||
| """Is 'root can't be extracted from a negative number' error message created?""" | ||
| rpn_tokens = ['-2', 'sqrt'] | ||
| rpncalculator = RPNcalculator(rpn_tokens, pycalclib) | ||
| result, error_msg = rpncalculator.evaluate() | ||
| self.assertEqual(error_msg, "ERROR: a root can't be extracted from a negative number") | ||
|
|
||
| def test_evaluate_method_error_msg_invalid_syntax(self): | ||
| """Is 'invalid syntax' error message created?""" | ||
| rpn_tokens = ['2', '+'] | ||
| rpncalculator = RPNcalculator(rpn_tokens, pycalclib) | ||
| result, error_msg = rpncalculator.evaluate() | ||
| self.assertEqual(error_msg, "ERROR: invalid syntax") | ||
|
|
||
|
|
||
| # Test of 'Pycalclib' class from 'pycalclib' module | ||
| class PycalclibTestCase(unittest.TestCase): | ||
| """Tests for Pycalclib class""" | ||
|
|
||
| def test_consider_user_module(self): | ||
| """Are user functions added to pycalclib?""" | ||
| self.assertIn('five', pycalclib.functions) | ||
| self.assertIn('squaressum', pycalclib.functions) | ||
|
|
||
|
|
||
| # Tests of 'is_number' function from 'utils' module | ||
| class IsNumberTestCase(unittest.TestCase): | ||
| """Test for 'is_number' function""" | ||
|
|
||
| def test_is_number_function(self): | ||
| """Does 'is_number' function distinguish tokens which are numbers from ones which are not?""" | ||
| tokens = ['.3', '-0.3', '7', 'tan'] | ||
| is_numbers = [] | ||
| for token in tokens: | ||
| is_numbers.append(is_number(token)) | ||
| self.assertEqual(is_numbers, [True, True, True, False]) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| #! /usr/bin/python3 | ||
|
|
||
| # import | ||
| import argparse | ||
| import sys | ||
| from .tokenizer import Tokenizer | ||
| from .addmultsigns import Multsignsadder | ||
| from .rpn import RPN | ||
| from .constsreplacer import Constsreplacer | ||
| from .rpncalculator import RPNcalculator | ||
| from .pycalclib import Pycalclib | ||
|
|
||
|
|
||
| def create_parser(): | ||
| """Creates parser to parse user mathematical expression""" | ||
| parser = argparse.ArgumentParser(prog='pycalc', description='pure Python command line calculator', | ||
| epilog="""Anton Charnichenka for EPAM: Introduction to Python | ||
| and Golang programming, 2018.""") | ||
| parser.add_argument('expression', help="""mathematical expression string to evaluate; | ||
| implicit multiplication is supported""") | ||
| parser.add_argument('--user_module', '-m', default='', help='additional module with user defined functions') | ||
|
|
||
| return parser | ||
|
|
||
|
|
||
| def main(): | ||
| """Calculates user math expression""" | ||
| parser = create_parser() | ||
| namespace = parser.parse_args(sys.argv[1:]) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В данном случае необязательно явно отправлять |
||
| user_expr = namespace.expression | ||
| user_module = namespace.user_module | ||
|
|
||
| # create pycalclib | ||
| pycalclib = Pycalclib(user_module) | ||
|
|
||
| # calculation chain | ||
| # tokenize user's expression string | ||
| tokenizer = Tokenizer(user_expr, pycalclib) | ||
| tokens, error_msg = tokenizer.extract_tokens() | ||
| if error_msg: | ||
| print(error_msg) | ||
| sys.exit(1) | ||
| elif not tokens: | ||
| print('ERROR: no expression was entered') | ||
| sys.exit(1) | ||
| # add implicit multiplication signs to the list of extracted tokens | ||
| mult_signs_adder = Multsignsadder(tokens, pycalclib) | ||
| tokens = mult_signs_adder.addmultsigns() | ||
| # transform extracted tokens into RPN | ||
| rpn = RPN(tokens, pycalclib) | ||
| rpn_tokens, error_msg = rpn.convert2rpn() | ||
| if error_msg: | ||
| print(error_msg) | ||
| sys.exit(1) | ||
| # replace constants with their numeric equivalents | ||
| constsreplacer = Constsreplacer(rpn_tokens, pycalclib) | ||
| rpn_tokens = constsreplacer.replace_constants() | ||
| # evaluate user's expression | ||
| rpncalculator = RPNcalculator(rpn_tokens, pycalclib) | ||
| result, error_msg = rpncalculator.evaluate() | ||
| if error_msg: | ||
| print(error_msg) | ||
| sys.exit(1) | ||
| else: | ||
| print(result) | ||
| sys.exit(0) | ||
|
|
||
|
|
||
| # main | ||
| if __name__ == "__main__": | ||
| main() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Такие комментарии повторяют информацию, которую нам может дать код, к которому эти комментарии прикреплены. Эти комментарии могут быть опущены либо оформлены как
docstringдля функций или классов.Похожие комментарии есть в этом файле в строках 13, 17, 120, 154, 183 и в других файлах.