diff --git a/documents/references.md b/documents/references.md index 4585f73f1d779b6d9f3d5c3da4368f2fea842ebf..2f7b0f3de2fb643bc749bbc91758b7621da6fbb1 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 f27e085c67277b14da88b333a9a5775ff738d47a..5ce498c1850b26170cdead5a1a9cdd9140e90f64 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 1553d0b3a997f42a9ae63e01e8e4d25eb41152aa..9c748eaa20ae65fd5cad732e5f7a73d7b772dfcb 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 0000000000000000000000000000000000000000..bf6e22ccbb3abaa00844eadd348d556ed54c15f8 --- /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 0000000000000000000000000000000000000000..1ff5a9279bad82c96ea3c37d5f392927887aa6f1 --- /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 0000000000000000000000000000000000000000..c6b8034b258dac39a0fe65734824d7703deb89c1 --- /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 0000000000000000000000000000000000000000..9a33115dad9d14e5653d8ede4ff4f9e0b9a08199 --- /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 0000000000000000000000000000000000000000..6c0c7e2a020d9d18837a5b7f47ec7ed34ac83f5c --- /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 0000000000000000000000000000000000000000..2f45ed885746256f819489fb20e845a672bb0f10 --- /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 0000000000000000000000000000000000000000..080b4c3622ea08788ca4a01a7ba2296aa6d417fa --- /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()