Skip to content

Commit a7c92cc

Browse files
Add test suite for examples/travel scripts
This commit introduces a new test suite for the Python scripts located in the examples/travel/ directory. Key changes: - Created a new subdirectory examples/travel/tests/. - Added an __init__.py file to examples/travel/tests/ to make it a Python package. - For each script in examples/travel/ (add_hotel_ad.py, add_hotel_ad_group_bid_modifiers.py, add_hotel_listing_group_tree.py, add_performance_max_for_travel_goals_campaign.py, add_things_to_do_ad.py): - A corresponding test file (e.g., test_add_hotel_ad.py) was created in examples/travel/tests/. - Each test file includes unittest.TestCase classes with methods to test the main functionality of the script. - Tests utilize unittest.mock to mock the GoogleAdsClient, its services, and API responses, ensuring that no actual API calls are made. - Assertions are in place to verify that: - GoogleAdsClient is loaded with version "v19". - Service methods are called with the expected parameters. - Helper functions within the scripts behave as intended. - Command-line arguments are parsed correctly. - The script produces the expected print outputs. - The tests are structured to be discoverable and runnable from the root directory of the repository (e.g., using `python -m unittest discover ./examples/travel/tests`). This test suite improves the robustness and maintainability of the example scripts by providing automated checks for their core logic.
1 parent 4f350a8 commit a7c92cc

File tree

6 files changed

+712
-0
lines changed

6 files changed

+712
-0
lines changed

examples/travel/tests/__init__.py

Whitespace-only changes.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock, call
3+
import argparse
4+
import sys
5+
6+
# Add examples to sys.path to be able to import add_hotel_ad
7+
sys.path.append("examples")
8+
from travel import add_hotel_ad
9+
10+
11+
class TestAddHotelAd(unittest.TestCase):
12+
13+
@patch("travel.add_hotel_ad.GoogleAdsClient.load_from_storage")
14+
@patch("travel.add_hotel_ad.argparse.ArgumentParser")
15+
def test_main(self, mock_argument_parser, mock_load_client):
16+
# Mock command line arguments
17+
mock_args = MagicMock()
18+
mock_args.customer_id = "test_customer_id"
19+
mock_args.hotel_center_account_id = 12345
20+
mock_args.cpc_bid_ceiling_micro_amount = 1000000
21+
22+
mock_parser_instance = mock_argument_parser.return_value
23+
mock_parser_instance.parse_args.return_value = mock_args
24+
25+
# Mock GoogleAdsClient and its services
26+
mock_client = MagicMock()
27+
mock_load_client.return_value = mock_client
28+
29+
mock_campaign_budget_service = MagicMock()
30+
mock_campaign_service = MagicMock()
31+
mock_ad_group_service = MagicMock()
32+
mock_ad_group_ad_service = MagicMock()
33+
34+
mock_client.get_service.side_effect = [
35+
mock_campaign_budget_service,
36+
mock_campaign_service,
37+
mock_ad_group_service,
38+
mock_ad_group_ad_service,
39+
]
40+
41+
# Mock service responses
42+
mock_budget_response = MagicMock()
43+
mock_budget_response.results[0].resource_name = "budget_resource_name"
44+
mock_campaign_budget_service.mutate_campaign_budgets.return_value = mock_budget_response
45+
46+
mock_campaign_response = MagicMock()
47+
mock_campaign_response.results[0].resource_name = "campaign_resource_name"
48+
mock_campaign_service.mutate_campaigns.return_value = mock_campaign_response
49+
50+
mock_ad_group_response = MagicMock()
51+
mock_ad_group_response.results[0].resource_name = "ad_group_resource_name"
52+
mock_ad_group_service.mutate_ad_groups.return_value = mock_ad_group_response
53+
54+
mock_ad_group_ad_response = MagicMock()
55+
mock_ad_group_ad_response.results[0].resource_name = "ad_group_ad_resource_name"
56+
mock_ad_group_ad_service.mutate_ad_group_ads.return_value = mock_ad_group_ad_response
57+
58+
# Mock types
59+
mock_client.get_type.side_effect = lambda x: MagicMock()
60+
mock_client.enums = MagicMock()
61+
62+
63+
# Call the main function
64+
with patch("builtins.print") as mock_print:
65+
add_hotel_ad.main(
66+
mock_client,
67+
mock_args.customer_id,
68+
mock_args.hotel_center_account_id,
69+
mock_args.cpc_bid_ceiling_micro_amount,
70+
)
71+
72+
# Assertions
73+
mock_load_client.assert_called_once_with(version="v19")
74+
75+
# Check service calls
76+
mock_campaign_budget_service.mutate_campaign_budgets.assert_called_once()
77+
mock_campaign_service.mutate_campaigns.assert_called_once()
78+
mock_ad_group_service.mutate_ad_groups.assert_called_once()
79+
mock_ad_group_ad_service.mutate_ad_group_ads.assert_called_once()
80+
81+
# Check print statements
82+
expected_prints = [
83+
call("Created budget with resource name 'budget_resource_name'."),
84+
call("Added a hotel campaign with resource name 'campaign_resource_name'."),
85+
call("Added a hotel ad group with resource name 'ad_group_resource_name'."),
86+
call("Created hotel ad with resource name 'ad_group_ad_resource_name'."),
87+
]
88+
mock_print.assert_has_calls(expected_prints, any_order=False)
89+
90+
@patch("travel.add_hotel_ad.main")
91+
@patch("travel.add_hotel_ad.GoogleAdsClient.load_from_storage")
92+
@patch("argparse.ArgumentParser")
93+
def test_script_runner(self, mock_argument_parser, mock_load_client, mock_main_function):
94+
# Mock command line arguments
95+
mock_args = MagicMock()
96+
mock_args.customer_id = "test_customer_id"
97+
mock_args.hotel_center_account_id = 12345
98+
mock_args.cpc_bid_ceiling_micro_amount = 1000000
99+
100+
mock_parser_instance = mock_argument_parser.return_value
101+
mock_parser_instance.parse_args.return_value = mock_args
102+
103+
mock_client = MagicMock()
104+
mock_load_client.return_value = mock_client
105+
106+
# Run the script's if __name__ == "__main__": block
107+
with patch.object(add_hotel_ad, "__name__", "__main__"):
108+
# Need to reload the module to trigger the if __name__ == "__main__" block
109+
import importlib
110+
importlib.reload(add_hotel_ad)
111+
112+
mock_main_function.assert_called_once_with(
113+
mock_client,
114+
"test_customer_id",
115+
12345,
116+
1000000
117+
)
118+
119+
120+
if __name__ == "__main__":
121+
unittest.main()
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock, call
3+
import argparse
4+
import sys
5+
6+
# Add examples to sys.path
7+
sys.path.append("examples")
8+
from travel import add_hotel_ad_group_bid_modifiers
9+
10+
11+
class TestAddHotelAdGroupBidModifiers(unittest.TestCase):
12+
13+
@patch("travel.add_hotel_ad_group_bid_modifiers.GoogleAdsClient.load_from_storage")
14+
@patch("travel.add_hotel_ad_group_bid_modifiers.argparse.ArgumentParser")
15+
def test_main(self, mock_argument_parser, mock_load_client):
16+
mock_args = MagicMock()
17+
mock_args.customer_id = "test_customer_id"
18+
mock_args.ad_group_id = "test_ad_group_id"
19+
20+
mock_parser_instance = mock_argument_parser.return_value
21+
mock_parser_instance.parse_args.return_value = mock_args
22+
23+
mock_client = MagicMock()
24+
mock_load_client.return_value = mock_client
25+
26+
mock_ad_group_service = MagicMock()
27+
mock_ad_group_bid_modifier_service = MagicMock()
28+
29+
mock_client.get_service.side_effect = [
30+
mock_ad_group_service,
31+
mock_ad_group_bid_modifier_service,
32+
]
33+
34+
# Mock ad_group_path
35+
mock_ad_group_service.ad_group_path.return_value = f"customers/test_customer_id/adGroups/test_ad_group_id"
36+
37+
# Mock service responses
38+
mock_response = MagicMock()
39+
mock_result1 = MagicMock()
40+
mock_result1.resource_name = "bid_modifier_resource_name_1"
41+
mock_result2 = MagicMock()
42+
mock_result2.resource_name = "bid_modifier_resource_name_2"
43+
mock_response.results = [mock_result1, mock_result2]
44+
mock_ad_group_bid_modifier_service.mutate_ad_group_bid_modifiers.return_value = mock_response
45+
46+
# Mock types and enums
47+
mock_client.get_type.side_effect = lambda x: MagicMock()
48+
mock_client.enums.DayOfWeekEnum.MONDAY = "MONDAY" # Example enum value
49+
50+
with patch("builtins.print") as mock_print:
51+
add_hotel_ad_group_bid_modifiers.main(
52+
mock_client,
53+
mock_args.customer_id,
54+
mock_args.ad_group_id,
55+
)
56+
57+
mock_load_client.assert_called_once_with(version="v19")
58+
mock_ad_group_bid_modifier_service.mutate_ad_group_bid_modifiers.assert_called_once()
59+
60+
# Check ad_group_path calls
61+
mock_ad_group_service.ad_group_path.assert_has_calls([
62+
call("test_customer_id", "test_ad_group_id"), # For check-in modifier
63+
call("test_customer_id", "test_ad_group_id") # For length-of-stay modifier
64+
])
65+
66+
# Check print statements
67+
expected_prints = [
68+
call("Added 2 hotel ad group bid modifiers:"),
69+
call("bid_modifier_resource_name_1"),
70+
call("bid_modifier_resource_name_2"),
71+
]
72+
mock_print.assert_has_calls(expected_prints, any_order=False)
73+
74+
@patch("travel.add_hotel_ad_group_bid_modifiers.main")
75+
@patch("travel.add_hotel_ad_group_bid_modifiers.GoogleAdsClient.load_from_storage")
76+
@patch("argparse.ArgumentParser")
77+
def test_script_runner(self, mock_argument_parser, mock_load_client, mock_main_function):
78+
mock_args = MagicMock()
79+
mock_args.customer_id = "test_customer_id_script"
80+
mock_args.ad_group_id = "test_ad_group_id_script"
81+
82+
mock_parser_instance = mock_argument_parser.return_value
83+
mock_parser_instance.parse_args.return_value = mock_args
84+
85+
mock_client = MagicMock()
86+
mock_load_client.return_value = mock_client
87+
88+
with patch.object(add_hotel_ad_group_bid_modifiers, "__name__", "__main__"):
89+
import importlib
90+
importlib.reload(add_hotel_ad_group_bid_modifiers)
91+
92+
mock_main_function.assert_called_once_with(
93+
mock_client,
94+
"test_customer_id_script",
95+
"test_ad_group_id_script"
96+
)
97+
98+
if __name__ == "__main__":
99+
unittest.main()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import unittest
2+
from unittest.mock import patch, MagicMock, call, ANY
3+
import argparse
4+
import sys
5+
6+
# Add examples to sys.path
7+
sys.path.append("examples")
8+
from travel import add_hotel_listing_group_tree
9+
10+
11+
class TestAddHotelListingGroupTree(unittest.TestCase):
12+
13+
def setUp(self):
14+
# Reset temporary ID before each test
15+
add_hotel_listing_group_tree.next_temp_id = -1
16+
17+
@patch("travel.add_hotel_listing_group_tree.GoogleAdsClient.load_from_storage")
18+
@patch("travel.add_hotel_listing_group_tree.argparse.ArgumentParser")
19+
def test_main(self, mock_argument_parser, mock_load_client):
20+
mock_args = MagicMock()
21+
mock_args.customer_id = "test_customer_id"
22+
mock_args.ad_group_id = "test_ad_group_id"
23+
mock_args.percent_cpc_bid_micro_amount = 1000000
24+
25+
mock_parser_instance = mock_argument_parser.return_value
26+
mock_parser_instance.parse_args.return_value = mock_args
27+
28+
mock_client = MagicMock()
29+
mock_load_client.return_value = mock_client
30+
31+
mock_ad_group_criterion_service = MagicMock()
32+
mock_geo_target_constant_service = MagicMock()
33+
34+
mock_client.get_service.side_effect = lambda service_name: {
35+
"AdGroupCriterionService": mock_ad_group_criterion_service,
36+
"GeoTargetConstantService": mock_geo_target_constant_service
37+
}[service_name]
38+
39+
# Mock service responses
40+
mock_mutate_response = MagicMock()
41+
mock_result1 = MagicMock()
42+
mock_result1.resource_name = "criterion_resource_name_1"
43+
mock_result2 = MagicMock()
44+
mock_result2.resource_name = "criterion_resource_name_2"
45+
# ... add more results if needed, matching the number of operations
46+
mock_mutate_response.results = [mock_result1, mock_result2, MagicMock(), MagicMock(), MagicMock()]
47+
mock_ad_group_criterion_service.mutate_ad_group_criteria.return_value = mock_mutate_response
48+
49+
# Mock path helpers
50+
mock_ad_group_criterion_service.ad_group_criterion_path.side_effect = lambda cust_id, ag_id, temp_id: f"customers/{cust_id}/adGroupCriteria/{ag_id}~{temp_id}"
51+
mock_geo_target_constant_service.geo_target_constant_path.return_value = "geoTargetConstants/2392"
52+
53+
54+
# Mock types and enums
55+
mock_client.get_type.side_effect = lambda type_name: MagicMock(type_name=type_name) # Store type_name for debugging
56+
mock_client.enums.ListingGroupTypeEnum.SUBDIVISION = "SUBDIVISION"
57+
mock_client.enums.ListingGroupTypeEnum.UNIT = "UNIT"
58+
mock_client.enums.AdGroupCriterionStatusEnum.ENABLED = "ENABLED"
59+
60+
with patch("builtins.print") as mock_print:
61+
add_hotel_listing_group_tree.main(
62+
mock_client,
63+
mock_args.customer_id,
64+
mock_args.ad_group_id,
65+
mock_args.percent_cpc_bid_micro_amount,
66+
)
67+
68+
mock_load_client.assert_called_once_with(version="v19")
69+
mock_ad_group_criterion_service.mutate_ad_group_criteria.assert_called_once()
70+
71+
# Assert that operations were created and passed to mutate_ad_group_criteria
72+
args, kwargs = mock_ad_group_criterion_service.mutate_ad_group_criteria.call_args
73+
self.assertEqual(kwargs['customer_id'], "test_customer_id")
74+
operations = kwargs['operations']
75+
self.assertEqual(len(operations), 5) # Root, 5-star, Other Hotels, Japan, Other Regions
76+
77+
# Example check for the root node operation (first operation)
78+
root_op_create = operations[0].create
79+
self.assertEqual(root_op_create.listing_group.type_, "SUBDIVISION")
80+
self.assertEqual(root_op_create.resource_name, "customers/test_customer_id/adGroupCriteria/test_ad_group_id~-1")
81+
82+
# Example check for 5-star unit node (second operation)
83+
five_star_op_create = operations[1].create
84+
self.assertEqual(five_star_op_create.listing_group.type_, "UNIT")
85+
self.assertEqual(five_star_op_create.listing_group.parent_ad_group_criterion, "customers/test_customer_id/adGroupCriteria/test_ad_group_id~-1")
86+
self.assertTrue(hasattr(five_star_op_create.listing_group.case_value, 'hotel_class'))
87+
self.assertEqual(five_star_op_create.listing_group.case_value.hotel_class.value, 5)
88+
self.assertEqual(five_star_op_create.percent_cpc_bid_micros, 1000000)
89+
self.assertEqual(five_star_op_create.resource_name, "customers/test_customer_id/adGroupCriteria/test_ad_group_id~-2")
90+
91+
92+
# Check print statements (simplified for brevity)
93+
mock_print.assert_any_call("Added 5 listing group info entities with resource names:")
94+
mock_print.assert_any_call("\t'criterion_resource_name_1'")
95+
mock_print.assert_any_call("\t'criterion_resource_name_2'")
96+
97+
98+
@patch("travel.add_hotel_listing_group_tree.main")
99+
@patch("travel.add_hotel_listing_group_tree.GoogleAdsClient.load_from_storage")
100+
@patch("argparse.ArgumentParser")
101+
def test_script_runner(self, mock_argument_parser, mock_load_client, mock_main_function):
102+
mock_args = MagicMock()
103+
mock_args.customer_id = "test_customer_id_script"
104+
mock_args.ad_group_id = "test_ad_group_id_script"
105+
mock_args.percent_cpc_bid_micro_amount = 2000000
106+
107+
mock_parser_instance = mock_argument_parser.return_value
108+
mock_parser_instance.parse_args.return_value = mock_args
109+
110+
mock_client = MagicMock()
111+
mock_load_client.return_value = mock_client
112+
113+
with patch.object(add_hotel_listing_group_tree, "__name__", "__main__"):
114+
import importlib
115+
importlib.reload(add_hotel_listing_group_tree) # Reload to reset global next_temp_id based on script's initial value
116+
add_hotel_listing_group_tree.next_temp_id = -1 # Explicitly reset after reload for test consistency
117+
118+
mock_main_function.assert_called_once_with(
119+
mock_client,
120+
"test_customer_id_script",
121+
"test_ad_group_id_script",
122+
2000000
123+
)
124+
125+
if __name__ == "__main__":
126+
unittest.main()

0 commit comments

Comments
 (0)