From b8a0451059b84d20cfef87edcb471e060050d442 Mon Sep 17 00:00:00 2001 From: brodriguez8774 <brodriguez8774@gmail.com> Date: Sun, 29 Sep 2019 13:53:50 -0400 Subject: [PATCH] Implement tests for UserInterface class, using mock input --- documents/references.md | 30 ++++- resources/interface.py | 9 +- resources/knapsack.py | 7 +- .../display_values.txt | 45 +++++++ .../use_input_and_solve.txt | 113 ++++++++++++++++++ .../use_json_and_solve.txt | 73 +++++++++++ tests/{test.py => main.py} | 0 tests/mock_user_input/display_values.txt | 5 + tests/mock_user_input/use_input_and_solve.txt | 15 +++ tests/mock_user_input/use_json_and_solve.txt | 8 ++ tests/resources/interface.py | 82 +++++++++++++ 11 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 tests/expected_program_output/display_values.txt create mode 100644 tests/expected_program_output/use_input_and_solve.txt create mode 100644 tests/expected_program_output/use_json_and_solve.txt rename tests/{test.py => main.py} (100%) create mode 100644 tests/mock_user_input/display_values.txt create mode 100644 tests/mock_user_input/use_input_and_solve.txt create mode 100644 tests/mock_user_input/use_json_and_solve.txt create mode 100644 tests/resources/interface.py diff --git a/documents/references.md b/documents/references.md index 4585f73..2f7b0f3 100644 --- a/documents/references.md +++ b/documents/references.md @@ -20,9 +20,37 @@ Used to move to the "Script's Directory", regardless of where the script was lau ### Reading/Parsing JSON File with Python <https://linuxconfig.org/how-to-parse-data-from-json-into-python> -## Padding Console Output in Python +### Padding Console Output in Python <https://stackoverflow.com/a/5676884> +### UnitTesting Subtests +Using subtests: <https://www.caktusgroup.com/blog/2017/05/29/subtests-are-best/> + +### Testing the "resources/interface.py" File +We were told to have tests for everything, and our teacher linked us this <https://stackoverflow.com/a/36491341>.<br> +Okay, cool. I implemented it and it was simple enough. Except I realized that I was running input methods through tests, +but not actually testing anything. + +My next thought was "okay, the interface doesn't do anything but display output for the user. So I just need a way to +read in stdout", which lead me to <https://stackoverflow.com/a/39320622>. This is great and works well enough for +basic print() statements. ...but it doesn't seem to catch logging, which is what I use for my output. + +A good bit of research later, and I found <https://stackoverflow.com/a/34920727> which lead me to +<https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertLogs>. They seem great in theory, but I +couldn't figure out how to get them to work. + +While trying to troubleshoot the above two links, I stumbled upon <https://stackoverflow.com/a/53257669> and thought +"hey, that looks a lot like my own logging setup". But alas, I couldn't determine how to get that to work with testing +either (<https://stackoverflow.com/q/7472863> seems to imply it has to do with the UnitTest runner replacing +stdout/stderr on initialization?). + +But that put an idea in my head. "Can I just temporarily replace logging with something that I know works, such as +the print() function?" And thus, my current implementation was born. `knapsack.py` and `interface.py` both have a +`__init__` variable that simply replaces `logging.info` with `print`, if True. Doing so makes the second link (above) a +valid solution, allowing me to read in predefined input, and compare results against expected output. + +It might not be the cleanest solution, but it works fine for this small of a project, and gives me a good place to start +if I ever want to look into this further, in the future. ## Older Relevant References References linked in previous assignments. diff --git a/resources/interface.py b/resources/interface.py index f27e085..5ce498c 100644 --- a/resources/interface.py +++ b/resources/interface.py @@ -21,9 +21,14 @@ logger = init_logging.get_logger(__name__) class UserInterface(): - def __init__(self): + def __init__(self, testing_output=False): self._run_program_bool_ = True - self._knapsack_algorithm = FractionalKnapsackAlgorithm() + self._knapsack_algorithm = FractionalKnapsackAlgorithm(testing_output) + + # For catching output and comparing during testing. + # See documents/references.md for more info. + if testing_output: + logger.info = print def user_interface_loop(self): """ diff --git a/resources/knapsack.py b/resources/knapsack.py index 1553d0b..9c748ea 100644 --- a/resources/knapsack.py +++ b/resources/knapsack.py @@ -24,12 +24,17 @@ class FractionalKnapsackAlgorithm(): """ Knapsack algorithm. """ - def __init__(self): + def __init__(self, testing_output=False): self._item_set_ = [] self._max_weight_ = 0 self._items_populated_ = False self._weight_populated_ = False + # For catching output and comparing during testing. + # See documents/references.md for more info. + if testing_output: + logger.info = print + def set_item_set(self, item_set): """ Sets group of items to choose from. diff --git a/tests/expected_program_output/display_values.txt b/tests/expected_program_output/display_values.txt new file mode 100644 index 0000000..bf6e22c --- /dev/null +++ b/tests/expected_program_output/display_values.txt @@ -0,0 +1,45 @@ +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. +Current Max Weight: 0 +Current Item Set: [] + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Current Max Weight: 0 +Current Item Set: [] + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. +Exiting Program diff --git a/tests/expected_program_output/use_input_and_solve.txt b/tests/expected_program_output/use_input_and_solve.txt new file mode 100644 index 0000000..1ff5a92 --- /dev/null +++ b/tests/expected_program_output/use_input_and_solve.txt @@ -0,0 +1,113 @@ +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter a positive integer for "Max Weight": +"Max Weight" set to 4 + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter Item "Benefit" amount (must be a positive integer): +Enter Item "Cost" amount (must be a positive integer): +Successfully added new item. +Current Item Set: +================================================================ +| Item # | Total Benefit | Total Amount | Value Per One Weight | +================================================================ +| 1 | 35 | 3 | 11.666666666666666 | +================================================================ + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter Item "Benefit" amount (must be a positive integer): +Enter Item "Cost" amount (must be a positive integer): +Successfully added new item. +Current Item Set: +================================================================ +| Item # | Total Benefit | Total Amount | Value Per One Weight | +================================================================ +| 1 | 35 | 3 | 11.666666666666666 | +| 2 | 20 | 2 | 10.0 | +================================================================ + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter Item "Benefit" amount (must be a positive integer): +Enter Item "Cost" amount (must be a positive integer): +Successfully added new item. +Current Item Set: +================================================================ +| Item # | Total Benefit | Total Amount | Value Per One Weight | +================================================================ +| 1 | 35 | 3 | 11.666666666666666 | +| 2 | 20 | 2 | 10.0 | +| 3 | 20 | 2 | 10.0 | +================================================================ + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. + +Current Max Weight: 4 +Current Item Set: +=============================================================================== +| Item # | Total Benefit | Total Amount | Value Per One Weight | Amount Taken | +=============================================================================== +| 1 | 35 | 3 | 11.666666666666666 | 3 | +| 2 | 20 | 2 | 10.0 | 1 | +| 3 | 20 | 2 | 10.0 | 0 | +=============================================================================== + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. +Exiting Program diff --git a/tests/expected_program_output/use_json_and_solve.txt b/tests/expected_program_output/use_json_and_solve.txt new file mode 100644 index 0000000..c6b8034 --- /dev/null +++ b/tests/expected_program_output/use_json_and_solve.txt @@ -0,0 +1,73 @@ +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter a positive integer for "Max Weight": +"Max Weight" set to 4 + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back +Enter a JSON formatted file to import from: +Successfully set new Item Set from JSON data. +Current Item Set: +================================================================ +| Item # | Total Benefit | Total Amount | Value Per One Weight | +================================================================ +| 1 | 35 | 3 | 11.666666666666666 | +| 2 | 20 | 2 | 10.0 | +| 3 | 20 | 2 | 10.0 | +================================================================ + +PROGRAM VALUES MENU +Select an option: + 1) Display current program values. + 2) Set "Max Weight" value. + 3) Set "Item Set" by JSON import. + 4) Add value to "Item Set" manually. + 5) Clear "Max Weight" value. + 6) Clear entire "Item Set". + 0) Back + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. + +Current Max Weight: 4 +Current Item Set: +=============================================================================== +| Item # | Total Benefit | Total Amount | Value Per One Weight | Amount Taken | +=============================================================================== +| 1 | 35 | 3 | 11.666666666666666 | 3 | +| 2 | 20 | 2 | 10.0 | 1 | +| 3 | 20 | 2 | 10.0 | 0 | +=============================================================================== + +MAIN MENU +Select an option: + 1) Display current program values. + 2) Set program values. + 3) Solve Knapsack Problem with current program values. + 0) Exit program. +Exiting Program diff --git a/tests/test.py b/tests/main.py similarity index 100% rename from tests/test.py rename to tests/main.py diff --git a/tests/mock_user_input/display_values.txt b/tests/mock_user_input/display_values.txt new file mode 100644 index 0000000..9a33115 --- /dev/null +++ b/tests/mock_user_input/display_values.txt @@ -0,0 +1,5 @@ +1 +2 +1 +0 +0 diff --git a/tests/mock_user_input/use_input_and_solve.txt b/tests/mock_user_input/use_input_and_solve.txt new file mode 100644 index 0000000..6c0c7e2 --- /dev/null +++ b/tests/mock_user_input/use_input_and_solve.txt @@ -0,0 +1,15 @@ +2 +2 +4 +4 +35 +3 +4 +20 +2 +4 +20 +2 +0 +3 +0 diff --git a/tests/mock_user_input/use_json_and_solve.txt b/tests/mock_user_input/use_json_and_solve.txt new file mode 100644 index 0000000..2f45ed8 --- /dev/null +++ b/tests/mock_user_input/use_json_and_solve.txt @@ -0,0 +1,8 @@ +2 +2 +4 +3 +example_item_set_2.json +0 +3 +0 diff --git a/tests/resources/interface.py b/tests/resources/interface.py new file mode 100644 index 0000000..080b4c3 --- /dev/null +++ b/tests/resources/interface.py @@ -0,0 +1,82 @@ +""" +Date: 09-20-19 +Class: CS5310 +Assignment: Fractional Knapsack Problem +Author: Brandon Rodriguez + + +Tests for User Interface Class. +""" + +# System Imports. +import contextlib, sys, unittest +from io import StringIO + +# User Class Imports. +from resources.interface import UserInterface + + +class TestUserInterface(unittest.TestCase): + def setUp(self): + self.interface = UserInterface(testing_output=True) + self.original_stdin = sys.stdin + + def tearDown(self): + sys.stdin = self.original_stdin + + def test_display_values(self): + # Redirect stdin and stdout. + sys.stdin = open('tests/mock_user_input/display_values.txt') + temp_stdout = StringIO() + + # Call interface function. + with contextlib.redirect_stdout(temp_stdout): + self.interface.user_interface_loop() + + # Verify output against file of expected output. + actual_output = temp_stdout.getvalue().strip() + output_file = open('tests/expected_program_output/display_values.txt') + expected_output = output_file.read().strip() + self.assertEqual(actual_output, expected_output) + + # Close open files. + sys.stdin.close() + output_file.close() + + def test_use_json_and_solve(self): + # Redirect stdin and stdout. + sys.stdin = open('tests/mock_user_input/use_json_and_solve.txt') + temp_stdout = StringIO() + + # Call interface function. + with contextlib.redirect_stdout(temp_stdout): + self.interface.user_interface_loop() + + # Verify output against file of expected output. + actual_output = temp_stdout.getvalue().strip() + output_file = open('tests/expected_program_output/use_json_and_solve.txt') + expected_output = output_file.read().strip() + self.assertEqual(actual_output, expected_output) + + # Close open files. + sys.stdin.close() + output_file.close() + + def test_use_input_and_solve(self): + # Redirect stdin and stdout. + sys.stdin = open('tests/mock_user_input/use_input_and_solve.txt') + temp_stdout = StringIO() + + # Call interface function. + with contextlib.redirect_stdout(temp_stdout): + self.interface.user_interface_loop() + + # Verify output against file of expected output. + actual_output = temp_stdout.getvalue().strip() + output_file = open('tests/expected_program_output/use_input_and_solve.txt') + expected_output = output_file.read().strip() + self.assertEqual(actual_output, expected_output) + + # Close open files. + sys.stdin.close() + output_file.close() -- GitLab