From ce4c68037338023f72544137ceca03cade9074ee Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 15 Sep 2021 13:28:59 +0530 Subject: [PATCH 1/2] test: automated test for running all stock reports These test do not assert correctness, they just check that "execute" function is working with sane filters. --- erpnext/stock/report/test_reports.py | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 erpnext/stock/report/test_reports.py diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py new file mode 100644 index 000000000000..c4b6cdca1505 --- /dev/null +++ b/erpnext/stock/report/test_reports.py @@ -0,0 +1,59 @@ +import unittest + +import frappe +from frappe.core.doctype.report.report import get_report_module_dotted_path + +MODULE = "Stock" + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", +} + +REPORT_FILTER_MAP = { + "Stock Ledger": {"_optional": True}, + "Stock Balance": {"_optional": True}, + "Stock Projected Qty": {"_optional": True}, + "Batch-Wise Balance History": {}, + "Itemwise Recommended Reorder Level": {"item_group": "All Item Groups"}, + "COGS By Item Group": {}, + "Stock Qty vs Serial No Count": {"warehouse": "_Test Warehouse - _TC"}, + "Stock and Account Value Comparison": { + "company": "_Test Company with perpetual inventory", + "account": "Stock In Hand - TCP1", + "as_on_date": "2021-01-01", + }, + "Product Bundle Balance": {"date": "2022-01-01", "_optional": True}, + "Stock Analytics": { + "from_date": "2021-01-01", + "to_date": "2021-12-31", + "value_quantity": "Quantity", + "_optional": True, + }, + "Warehouse wise Item Balance Age and Value": {"_optional": True}, + "Item Variant Details": {"item": "_Test Variant Item",}, + "Total Stock Summary": {"group_by": "warehouse",}, + "Batch Item Expiry Status": {}, + "Stock Ageing": {"range1": 30, "range2": 60, "range3": 90, "_optional": True}, +} + +# When _opional is set to True, these filters are added one at a time to default filters in test. +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestReports(unittest.TestCase): + def test_execute_all_stock_reports(self): + for report, filter in REPORT_FILTER_MAP.items(): + filter = frappe._dict(DEFAULT_FILTERS.copy()).update(filter) + report_execute_fn = get_report_module_dotted_path(MODULE, report) + ".execute" + frappe.get_attr(report_execute_fn)(filter) + + # try each optional filter + if filter.get("_optional"): + for key, value in OPTIONAL_FILTERS.items(): + frappe.get_attr(report_execute_fn)(filter.copy().update({key: value})) From 8ce725533374b8080550db5a07ba9e74840b815a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 15 Sep 2021 18:19:53 +0530 Subject: [PATCH 2/2] test: make report execution test modular --- erpnext/stock/report/test_reports.py | 84 +++++++++++++++------------- erpnext/tests/utils.py | 41 ++++++++++++++ 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index c4b6cdca1505..d7fb5b2bf3fa 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -1,9 +1,7 @@ import unittest +from typing import List, Tuple -import frappe -from frappe.core.doctype.report.report import get_report_module_dotted_path - -MODULE = "Stock" +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report DEFAULT_FILTERS = { "company": "_Test Company", @@ -11,34 +9,40 @@ "to_date": "2030-01-01", } -REPORT_FILTER_MAP = { - "Stock Ledger": {"_optional": True}, - "Stock Balance": {"_optional": True}, - "Stock Projected Qty": {"_optional": True}, - "Batch-Wise Balance History": {}, - "Itemwise Recommended Reorder Level": {"item_group": "All Item Groups"}, - "COGS By Item Group": {}, - "Stock Qty vs Serial No Count": {"warehouse": "_Test Warehouse - _TC"}, - "Stock and Account Value Comparison": { - "company": "_Test Company with perpetual inventory", - "account": "Stock In Hand - TCP1", - "as_on_date": "2021-01-01", - }, - "Product Bundle Balance": {"date": "2022-01-01", "_optional": True}, - "Stock Analytics": { - "from_date": "2021-01-01", - "to_date": "2021-12-31", - "value_quantity": "Quantity", - "_optional": True, - }, - "Warehouse wise Item Balance Age and Value": {"_optional": True}, - "Item Variant Details": {"item": "_Test Variant Item",}, - "Total Stock Summary": {"group_by": "warehouse",}, - "Batch Item Expiry Status": {}, - "Stock Ageing": {"range1": 30, "range2": 60, "range3": 90, "_optional": True}, -} -# When _opional is set to True, these filters are added one at a time to default filters in test. +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("Stock Ledger", {"_optional": True}), + ("Stock Balance", {"_optional": True}), + ("Stock Projected Qty", {"_optional": True}), + ("Batch-Wise Balance History", {}), + ("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}), + ("COGS By Item Group", {}), + ("Stock Qty vs Serial No Count", {"warehouse": "_Test Warehouse - _TC"}), + ( + "Stock and Account Value Comparison", + { + "company": "_Test Company with perpetual inventory", + "account": "Stock In Hand - TCP1", + "as_on_date": "2021-01-01", + }, + ), + ("Product Bundle Balance", {"date": "2022-01-01", "_optional": True}), + ( + "Stock Analytics", + { + "from_date": "2021-01-01", + "to_date": "2021-12-31", + "value_quantity": "Quantity", + "_optional": True, + }, + ), + ("Warehouse wise Item Balance Age and Value", {"_optional": True}), + ("Item Variant Details", {"item": "_Test Variant Item",}), + ("Total Stock Summary", {"group_by": "warehouse",}), + ("Batch Item Expiry Status", {}), + ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), +] + OPTIONAL_FILTERS = { "warehouse": "_Test Warehouse - _TC", "item": "_Test Item", @@ -48,12 +52,12 @@ class TestReports(unittest.TestCase): def test_execute_all_stock_reports(self): - for report, filter in REPORT_FILTER_MAP.items(): - filter = frappe._dict(DEFAULT_FILTERS.copy()).update(filter) - report_execute_fn = get_report_module_dotted_path(MODULE, report) + ".execute" - frappe.get_attr(report_execute_fn)(filter) - - # try each optional filter - if filter.get("_optional"): - for key, value in OPTIONAL_FILTERS.items(): - frappe.get_attr(report_execute_fn)(filter.copy().update({key: value})) + """Test that all script report in stock modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Stock", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 2156bd51a4a9..a3cab4b59da7 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -3,8 +3,13 @@ import copy from contextlib import contextmanager +from typing import Any, Dict, NewType, Optional import frappe +from frappe.core.doctype.report.report import get_report_module_dotted_path + +ReportFilters = Dict[str, Any] +ReportName = NewType("ReportName", str) def create_test_contact_and_address(): @@ -78,3 +83,39 @@ def test_case(self): for key, value in previous_settings.items(): setattr(settings, key, value) settings.save() + + +def execute_script_report( + report_name: ReportName, + module: str, + filters: ReportFilters, + default_filters: Optional[ReportFilters] = None, + optional_filters: Optional[ReportFilters] = None + ): + """Util for testing execution of a report with specified filters. + + Tests the execution of report with default_filters + filters. + Tests the execution using optional_filters one at a time. + + Args: + report_name: Human readable name of report (unscrubbed) + module: module to which report belongs to + filters: specific values for filters + default_filters: default values for filters such as company name. + optional_filters: filters which should be tested one at a time in addition to default filters. + """ + + if default_filters is None: + default_filters = {} + + report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute") + report_filters = frappe._dict(default_filters).copy().update(filters) + + report_data = report_execute_fn(report_filters) + + if optional_filters: + for key, value in optional_filters.items(): + filter_with_optional_param = report_filters.copy().update({key: value}) + report_execute_fn(filter_with_optional_param) + + return report_data