mirror of
https://github.com/mims-harvard/ToolUniverse.git
synced 2026-09-19 07:31:47 +08:00
6.0 KiB
6.0 KiB
Test Writing Guide for ToolUniverse
Response Structure Handling
When writing tests for ToolUniverse tools, be aware that tool responses may have data in either a flat or nested structure depending on the tool implementation.
Response Formats
Nested Structure (New)
{
"status": "success",
"data": {
"num_results": 5,
"results": [...]
}
}
Flat Structure (Legacy)
{
"status": "success",
"num_results": 5,
"results": [...]
}
Writing Compatible Tests
Always write tests that handle both formats:
Bad (Only checks flat structure)
def test_search(self, tu):
result = tu.tools.MyTool_search(**{"query": "test"})
assert result["status"] == "success"
assert "results" in result # Will fail if nested!
Good (Handles both structures)
def test_search(self, tu):
result = tu.tools.MyTool_search(**{"query": "test"})
assert result["status"] == "success"
# Check if data is nested or flat
data = result.get("data", result)
assert "results" in data
Pattern Examples
Pattern 1: Simple Field Check
if result["status"] == "success":
data = result.get("data", result)
assert "field_name" in data
Pattern 2: Multiple Fields
if result["status"] == "success":
data = result.get("data", result)
expected_fields = ["field1", "field2", "field3"]
for field in expected_fields:
assert field in data, f"Missing field: {field}"
Pattern 3: Checking Lists
if result["status"] == "success":
data = result.get("data", result)
assert "results" in data
assert isinstance(data["results"], list)
Pattern 4: Backwards Compatible Access
if result["status"] == "success":
# Try nested first, fall back to flat
results = result.get("data", {}).get("results", result.get("results"))
assert results is not None
assert isinstance(results, list)
Testing Error Responses
Error responses are always at the top level:
if result["status"] == "error":
assert "error" in result
assert isinstance(result["error"], str)
Status Checks
Always check status first:
def test_operation(self, tu):
result = tu.tools.MyTool_operation(**params)
# Always check status exists
assert "status" in result
assert result["status"] in ["success", "error", "warning"]
# Then handle accordingly
if result["status"] == "success":
data = result.get("data", result)
# ... check data fields
elif result["status"] == "error":
assert "error" in result
Real-World Examples
From fixed test files:
Example 1: FourDN Search
def test_search_data_basic(self, tu):
result = tu.tools.FourDN_search_data(**{
"operation": "search",
"query": "*",
"limit": 5
})
assert "status" in result
if result["status"] == "success":
assert "data" in result
assert "num_results" in result["data"] or "num_results" in result
else:
assert "error" in result
Example 2: GTEx Dataset Info
def test_get_dataset_info(self, tu):
result = tu.tools.GTEx_get_dataset_info(**{
"operation": "get_dataset_info"
})
assert result.get("status") == "success" or "error" in result
if result.get("status") == "success":
# Check if data is nested or flat
datasets = result.get("data", result.get("datasets"))
assert datasets is not None
assert isinstance(datasets, list)
Example 3: Rfam Alignment
def test_get_alignment(self, tu):
result = tu.tools.Rfam_get_alignment(**{
"operation": "get_alignment",
"family_id": "RF00360",
"format": "fasta"
})
assert result.get("status") == "success" or "error" in result
if result.get("status") == "success":
data = result.get("data", result)
assert "alignment" in data
assert "format" in data
Best Practices
- Always check
statusfirst - Every response should have a status field - Use
.get()for optional fields - Prevents KeyError - Handle both nested and flat - Use
result.get("data", result)pattern - Test both success and error cases - Don't assume success
- Use descriptive assertion messages - Help debugging with
assert field in data, f"Missing {field}" - Mock slow APIs - Use
@patchfor unit tests, mark integration tests appropriately
Testing Checklist
- Test checks for
statusfield - Test handles both nested and flat response structures
- Test handles error responses gracefully
- Test has clear assertion messages
- Test doesn't assume API will succeed
- Test uses appropriate timeout for integration tests
- Test is properly categorized (unit/integration)
Common Pitfalls
Assuming Flat Structure
assert "results" in result # May fail!
Not Checking Status
data = result["data"] # KeyError if status is "error"!
Hard-coded Field Paths
assert result["data"]["results"] # Breaks if structure changes!
Correct Approach
assert "status" in result
if result["status"] == "success":
data = result.get("data", result)
assert "results" in data
Migration Guide
If you have existing tests that fail with "field not in result" errors:
- Find the failing assertion (e.g.,
assert "field" in result) - Add compatibility check:
data = result.get("data", result) - Update assertion:
assert "field" in data - Re-run test to verify
Example migration:
if result["status"] == "success":
- assert "results" in result
- assert isinstance(result["results"], list)
+ data = result.get("data", result)
+ assert "results" in data
+ assert isinstance(data["results"], list)
Questions?
- Check
TEST_FIXES_SUMMARY.mdfor examples of recent fixes - Look at
test_biostudies_tool.pyortest_ensembl_tool.pyfor well-structured tests - See fixed tests in
test_fourdn_tool.pyfor before/after examples