add unit tests
saveload fixes
This commit is contained in:
220
tests/helpers/TestHelper.gd
Normal file
220
tests/helpers/TestHelper.gd
Normal file
@@ -0,0 +1,220 @@
|
||||
extends RefCounted
|
||||
class_name TestHelper
|
||||
|
||||
## Common test utilities and assertions for Skelly project testing
|
||||
##
|
||||
## Provides standardized testing functions, assertions, and utilities
|
||||
## to ensure consistent test behavior across all test files.
|
||||
|
||||
## Test result tracking
|
||||
static var tests_run := 0
|
||||
static var tests_passed := 0
|
||||
static var tests_failed := 0
|
||||
|
||||
## Performance tracking
|
||||
static var test_start_time := 0.0
|
||||
static var performance_data := {}
|
||||
|
||||
## Print test section header with consistent formatting
|
||||
static func print_test_header(test_name: String):
|
||||
print("\n=== Testing %s ===" % test_name)
|
||||
tests_run = 0
|
||||
tests_passed = 0
|
||||
tests_failed = 0
|
||||
test_start_time = Time.get_unix_time_from_system()
|
||||
|
||||
## Print test section footer with results summary
|
||||
static func print_test_footer(test_name: String):
|
||||
var end_time = Time.get_unix_time_from_system()
|
||||
var duration = end_time - test_start_time
|
||||
|
||||
print("\n--- %s Results ---" % test_name)
|
||||
print("Tests Run: %d" % tests_run)
|
||||
print("Passed: %d" % tests_passed)
|
||||
print("Failed: %d" % tests_failed)
|
||||
print("Duration: %.3f seconds" % duration)
|
||||
|
||||
if tests_failed == 0:
|
||||
print("✅ All tests PASSED")
|
||||
else:
|
||||
print("❌ %d tests FAILED" % tests_failed)
|
||||
|
||||
print("=== %s Complete ===" % test_name)
|
||||
|
||||
## Assert that a condition is true
|
||||
static func assert_true(condition: bool, message: String = ""):
|
||||
tests_run += 1
|
||||
if condition:
|
||||
tests_passed += 1
|
||||
print("✅ PASS: %s" % message)
|
||||
else:
|
||||
tests_failed += 1
|
||||
print("❌ FAIL: %s" % message)
|
||||
|
||||
## Assert that a condition is false
|
||||
static func assert_false(condition: bool, message: String = ""):
|
||||
assert_true(not condition, message)
|
||||
|
||||
## Assert that two values are equal
|
||||
static func assert_equal(expected, actual, message: String = ""):
|
||||
var condition = expected == actual
|
||||
var full_message = message
|
||||
if not full_message.is_empty():
|
||||
full_message += " "
|
||||
full_message += "(Expected: %s, Got: %s)" % [str(expected), str(actual)]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that two values are not equal
|
||||
static func assert_not_equal(expected, actual, message: String = ""):
|
||||
var condition = expected != actual
|
||||
var full_message = message
|
||||
if not full_message.is_empty():
|
||||
full_message += " "
|
||||
full_message += "(Should not equal: %s, Got: %s)" % [str(expected), str(actual)]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that a value is null
|
||||
static func assert_null(value, message: String = ""):
|
||||
assert_true(value == null, message + " (Should be null, got: %s)" % str(value))
|
||||
|
||||
## Assert that a value is not null
|
||||
static func assert_not_null(value, message: String = ""):
|
||||
assert_true(value != null, message + " (Should not be null)")
|
||||
|
||||
## Assert that a value is within a range
|
||||
static func assert_in_range(value: float, min_val: float, max_val: float, message: String = ""):
|
||||
var condition = value >= min_val and value <= max_val
|
||||
var full_message = "%s (Value: %f, Range: %f-%f)" % [message, value, min_val, max_val]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that two floating-point values are approximately equal (with tolerance)
|
||||
static func assert_float_equal(expected: float, actual: float, tolerance: float = 0.0001, message: String = ""):
|
||||
# Handle special cases: both infinity, both negative infinity, both NaN
|
||||
if is_inf(expected) and is_inf(actual):
|
||||
var condition = (expected > 0) == (actual > 0) # Same sign of infinity
|
||||
var full_message = "%s (Both infinity values: Expected: %f, Got: %f)" % [message, expected, actual]
|
||||
assert_true(condition, full_message)
|
||||
return
|
||||
|
||||
if is_nan(expected) and is_nan(actual):
|
||||
var full_message = "%s (Both NaN values: Expected: %f, Got: %f)" % [message, expected, actual]
|
||||
assert_true(true, full_message) # Both NaN is considered equal
|
||||
return
|
||||
|
||||
# Normal floating-point comparison
|
||||
var difference = abs(expected - actual)
|
||||
var condition = difference <= tolerance
|
||||
var full_message = "%s (Expected: %f, Got: %f, Difference: %f, Tolerance: %f)" % [message, expected, actual, difference, tolerance]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that an array contains a specific value
|
||||
static func assert_contains(array: Array, value, message: String = ""):
|
||||
var condition = value in array
|
||||
var full_message = "%s (Array: %s, Looking for: %s)" % [message, str(array), str(value)]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that an array does not contain a specific value
|
||||
static func assert_not_contains(array: Array, value, message: String = ""):
|
||||
var condition = not (value in array)
|
||||
var full_message = "%s (Array: %s, Should not contain: %s)" % [message, str(array), str(value)]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that a dictionary has a specific key
|
||||
static func assert_has_key(dict: Dictionary, key, message: String = ""):
|
||||
var condition = dict.has(key)
|
||||
var full_message = "%s (Dictionary keys: %s, Looking for: %s)" % [message, str(dict.keys()), str(key)]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that a file exists
|
||||
static func assert_file_exists(path: String, message: String = ""):
|
||||
var condition = FileAccess.file_exists(path)
|
||||
var full_message = "%s (Path: %s)" % [message, path]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Assert that a file does not exist
|
||||
static func assert_file_not_exists(path: String, message: String = ""):
|
||||
var condition = not FileAccess.file_exists(path)
|
||||
var full_message = "%s (Path: %s)" % [message, path]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Performance testing - start timing
|
||||
static func start_performance_test(test_id: String):
|
||||
performance_data[test_id] = Time.get_unix_time_from_system()
|
||||
|
||||
## Performance testing - end timing and validate
|
||||
static func end_performance_test(test_id: String, max_duration_ms: float, message: String = ""):
|
||||
if not performance_data.has(test_id):
|
||||
assert_true(false, "Performance test '%s' was not started" % test_id)
|
||||
return
|
||||
|
||||
var start_time = performance_data[test_id]
|
||||
var end_time = Time.get_unix_time_from_system()
|
||||
var duration_ms = (end_time - start_time) * 1000.0
|
||||
|
||||
var condition = duration_ms <= max_duration_ms
|
||||
var full_message = "%s (Duration: %.2fms, Max: %.2fms)" % [message, duration_ms, max_duration_ms]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
performance_data.erase(test_id)
|
||||
|
||||
## Create a temporary test file with content
|
||||
static func create_temp_file(filename: String, content: String = "") -> String:
|
||||
var temp_path = "user://test_" + filename
|
||||
var file = FileAccess.open(temp_path, FileAccess.WRITE)
|
||||
if file:
|
||||
file.store_string(content)
|
||||
file.close()
|
||||
return temp_path
|
||||
|
||||
## Clean up temporary test file
|
||||
static func cleanup_temp_file(path: String):
|
||||
if FileAccess.file_exists(path):
|
||||
DirAccess.remove_absolute(path)
|
||||
|
||||
## Create invalid JSON content for testing
|
||||
static func create_invalid_json() -> String:
|
||||
return '{"invalid": json, missing_quotes: true, trailing_comma: true,}'
|
||||
|
||||
## Create valid test JSON content
|
||||
static func create_valid_json() -> String:
|
||||
return '{"test_key": "test_value", "test_number": 42, "test_bool": true}'
|
||||
|
||||
## Wait for a specific number of frames
|
||||
static func wait_frames(frames: int, node: Node):
|
||||
for i in range(frames):
|
||||
await node.get_tree().process_frame
|
||||
|
||||
## Mock a simple function call counter
|
||||
class MockCallCounter:
|
||||
var call_count := 0
|
||||
var last_args := []
|
||||
|
||||
func call_function(args: Array = []):
|
||||
call_count += 1
|
||||
last_args = args.duplicate()
|
||||
|
||||
func reset():
|
||||
call_count = 0
|
||||
last_args.clear()
|
||||
|
||||
## Create a mock call counter for testing
|
||||
static func create_mock_counter() -> MockCallCounter:
|
||||
return MockCallCounter.new()
|
||||
|
||||
## Validate that an object has expected properties
|
||||
static func assert_has_properties(object: Object, properties: Array, message: String = ""):
|
||||
for property in properties:
|
||||
var condition = property in object
|
||||
var full_message = "%s - Missing property: %s" % [message, property]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Validate that an object has expected methods
|
||||
static func assert_has_methods(object: Object, methods: Array, message: String = ""):
|
||||
for method in methods:
|
||||
var condition = object.has_method(method)
|
||||
var full_message = "%s - Missing method: %s" % [message, method]
|
||||
assert_true(condition, full_message)
|
||||
|
||||
## Print a test step with consistent formatting
|
||||
static func print_step(step_name: String):
|
||||
print("\n--- Test: %s ---" % step_name)
|
||||
1
tests/helpers/TestHelper.gd.uid
Normal file
1
tests/helpers/TestHelper.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://du7jq8rtegu8o
|
||||
Reference in New Issue
Block a user