Skip to content

Commit

Permalink
Added unit testing edits and README
Browse files Browse the repository at this point in the history
  • Loading branch information
caleb-sitton-inl committed Jun 26, 2024
1 parent 45ce938 commit 2e4071b
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 57 deletions.
60 changes: 60 additions & 0 deletions tests/unit_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Unit Testing
## HERON
The `test_heron.py` file contains unit tests for the `FORCE/src/heron/create_componentsets_in_HERON()` function. These are designed to help developers identify bugs and isolate their causes. To this end, a brief overview of the most important unique scenarios each unit test addresses is listed below.

### TestMinimalInput
This test is designed to check main functionality with minimal edge cases. It checks that:
- New Component node was added and updated with content, including:
- economics node
- capex CashFlow, with all attributes
- reference price
- reference driver, with conversion from kW to mW

### TestExpandedInput1
This test considers a more complex HERON input XML. It checks that:
- Case node is transferred uncorrupted
- Contents of multiple existing components that should not be updated are uncorrupted
- New component's reference driver value is not converted if input is in mW
- DataGenerators node is transferred uncorrupted

### TestExpandedInput2
This test focuses on ensuring correct updating of economics and CashFlow nodes and subnodes. It checks that:
- Multiple components that need updating can be merged correctly with new components
- Existing CashFlow of type non-capex is uncorrupted
- capex CashFlow is added when a non-capex CashFlow exists but a capex CashFlow does not
- economics node is found successfully when non-economics subnode of the component precedes the economics subnode
- CashFlows are updated correctly when both a non-capex and a capex CashFlow exists
- Non-CashFlow subnode of economics node is uncorrupted and CashFlow is still found
- Existing capex CashFlow has
- Uncorrupted attributes
- Uncorrupted subnode that does not need updating
- All three existing subnodes that need updating correctly replaced
- CashFlow subnodes are updated correctly when two of the three types that need updating exist

### TestNoComponentsNode
This test examines a single edge case where the Components node is missing from the HERON input XML script. It checks that:
- A new Components node is created if one does not exist
- The new Component node is placed within this new Components node

### TestNoComponentNodes
This test examines the edge case where a Components node exists, but no Component nodes. It checks that:
- The new component is added, with content

### TestMissingSubnodes
This test considers scenarios where a component that needs to be updated is missing an economics or CashFlow subnode. It checks that:
- If the economics node is missing, it is created with correct content
- If the CashFlow node is missing, it is created with correct content

### TestEmptyCompSetsFolder
This test checks the function's behavior when the provided component set folder is empty. It checks that:
- The open function was not called (e.g., no file was opened)
- The existing component was not corrupted

### TestCompSetsFolderWithBadJSON
This test ensures a correct response to a component set file that is not is proper JSON format. It checks that:
- The function throws a ValueError

### TestCompSetsFolderMultFiles
This test checks the function's filtering system regarding which component set files it should open. It checks that:
- Only files whose names start with "componentSet" are opened
- Only files of type .txt or .json are opened
80 changes: 23 additions & 57 deletions tests/unit_tests/test_heron.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ def test_expanded_input_2(self, mock_listdir, mock_parse):
ref_driver = cashflow[0].findall('./reference_driver')
self.assertEqual(len(ref_driver), 1)
ref_driver_value = ref_driver[0].findall('./fixed_value')
self.assertEqual(ref_driver_value.text, '3100')
self.assertEqual(ref_driver_value[0].text, '3100')

# Reference price
ref_price = cashflow[0].findall('./reference_price')
Expand Down Expand Up @@ -376,13 +376,13 @@ def test_expanded_input_2(self, mock_listdir, mock_parse):
ref_driver = cashflow[0].findall('./reference_driver')
self.assertEqual(len(ref_driver), 1)
ref_driver_value = ref_driver[0].findall('./fixed_value')
self.assertEqual(ref_driver_value.text, '4100')
self.assertEqual(ref_driver_value[0].text, '4100')

# Reference price
ref_price = cashflow[0].findall('./reference_price')
self.assertEqual(len(ref_price), 1)
ref_price_value = ref_price[0].findall('./fixed_value')
self.assertEqual(ref_price_value[0].text, '4200')
self.assertEqual(ref_price_value[0].text, '-4200')

# Scaling factor
scaling_factor = cashflow[0].findall('./scaling_factor_x')
Expand Down Expand Up @@ -575,7 +575,6 @@ def test_empty_compsets_folder(self, mock_open, mock_listdir, mock_parse):
self.assertEqual(len(component_nodes), 1)
self.assertEqual(component_nodes[0].attrib['name'], 'ExistingComponent')

@unittest.skip("Waiting for function update (issue #18)")
class TestCompSetsFolderWithBadJSON(unittest.TestCase):
def setUp(self):
# Example of a minimal XML structure
Expand Down Expand Up @@ -605,13 +604,12 @@ def test_compsets_folder_bad_json(self, mock_open, mock_listdir, mock_parse):
caught_bad_json = False
try:
result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml")
except ('json_read_error_name'): #FIXME: replace with correct error once function is updated
except ValueError:
caught_bad_json = True

with self.subTest("Did not respond correctly to bad component set file content"):
self.assertEqual(caught_bad_json, True)

@unittest.skip("Waiting for function update (issue #18)")
class TestCompSetsFolderMultFiles(unittest.TestCase):
def setUp(self):
# Example of a minimal XML structure
Expand All @@ -629,73 +627,41 @@ def setUp(self):

@patch('xml.etree.ElementTree.parse')
@patch('os.listdir')
def test_compsets_folder_mult_files(self, mock_listdir, mock_parse):
# Open mock will return the same read_data each time it is called
# This is acceptable only because the content of the result tree is untested
@patch('builtins.open',
new_callable=mock_open,
read_data="""{
"Component Set Name": "NewComponent",
"Reference Driver": 1000,
"Reference Driver Power Units": "mW",
"Reference Price (USD)": 2000,
"Scaling Factor": 0.5
}""")
def test_compsets_folder_mult_files(self, mock_open, mock_listdir, mock_parse):
# Set up the parse mock to return an XML tree
mock_parse.return_value = self.tree
# Only the txt and json files whose names start with 'componentSet' should be opened
files_list = ['component.json', 'componentSet.csv', 'componentSet.json', 'componentSetStuff.txt',
'xcomponentSet.json', 'Set.json', 'compSet.json', 'ComponentSet.json']
files_list = ['component.json', 'README', 'componentSet.csv', 'xcomponentSet.json',
'componentSet.json', 'componentSetStuff.txt',
'aFolder', 'Set.json', 'compSet.json', 'ComponentSet.json']
mock_listdir.return_value = files_list

# Set up the open mock to return different files each time it's used
# It should only be called twice, but it contains data for up to four reads
# So it won't break the function when an extra file or two is opened, and the unit tester can catch the bug
mock_open_mult = mock_open()
mock_open_mult.side_effect = [mock_open(read_data =
"""{
"Component Set Name": "NewComponent0",
"Reference Driver": 1000,
"Reference Driver Power Units": "mW",
"Reference Price (USD)": 1000,
"Scaling Factor": 0.1
}""").return_value,
mock_open(read_data =
"""{
"Component Set Name": "NewComponent1",
"Reference Driver": 2100,
"Reference Driver Power Units": "mW",
"Reference Price (USD)": 2200,
"Scaling Factor": 0.2
}""").return_value,
mock_open(read_data =
"""{
"Component Set Name": "NewComponent2",
"Reference Driver": 3100,
"Reference Driver Power Units": "mW",
"Reference Price (USD)": 3200,
"Scaling Factor": 0.3
}""").return_value,
mock_open(read_data =
"""{
"Component Set Name": "NewComponent3",
"Reference Driver": 4100,
"Reference Driver Power Units": "mW",
"Reference Price (USD)": 4200,
"Scaling Factor": 0.4
}""").return_value]

# Call the function with patch for open function
with patch('builtins.open', mock_open_mult):
result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml")
# Call the function
result_tree = create_componentsets_in_HERON("/fake/folder", "/fake/heron_input.xml")

# Verify open function was called on correct files
for file in files_list:
# if file should have been opened
if file in ['componentSet.json', 'componentSetStuff.txt']:
with self.subTest(msg="File was not opened and should have been", file = file):
# Verify file was opened
self.assertIn(call('/fake/folder/'+file), mock_open_mult.call_args_list)
self.assertIn(call('/fake/folder/'+file), mock_open.call_args_list)
# if file should not have been opened
else:
with self.subTest(msg="File was opened and should not have been", file = file):
# Verify file was not opened
self.assertNotIn(call('/fake/folder/'+file), mock_open_mult.call_args_list)

components_node = result_tree.find('./Components')

# Verify component nodes were updated
component_nodes = components_node.findall('./Component')
self.assertEqual(len(component_nodes), 3)
self.assertNotIn(call('/fake/folder/'+file), mock_open.call_args_list)

# This is not needed for running tests through FORCE/run_tests
# It does allow tests to be run via the unit tester when test_heron is run directly
Expand Down
15 changes: 15 additions & 0 deletions tests/unit_tests/tests
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,19 @@
type = GenericExecutable
executable = python -m unittest test_heron.TestMissingSubnodes
[../]

[./TestEmptyCompSetsFolder]
type = GenericExecutable
executable = python -m unittest test_heron.TestEmptyCompSetsFolder
[../]

[./TestCompSetsFolderWithBadJSON]
type = GenericExecutable
executable = python -m unittest test_heron.TestCompSetsFolderWithBadJSON
[../]

[./TestCompSetsFolderMultFiles]
type = GenericExecutable
executable = python -m unittest test_heron.TestCompSetsFolderMultFiles
[../]
[]

0 comments on commit 2e4071b

Please sign in to comment.