-
Notifications
You must be signed in to change notification settings - Fork 3
Add test format validation, error codes framework, and $divide tests #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
eerxuan
wants to merge
4
commits into
documentdb:main
Choose a base branch
from
eerxuan:format-validation-divide-tests-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8df5b7e
Add test format validation, error codes framework, and $divide tests
2753727
Apply black, isort formatting and fix flake8 lint errors
fd6fba0
Improve coverage and format wording.
3eacce6
Fix mypy type errors and exclude unit tests from format validator
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| # Test Format Guide | ||
|
|
||
| ## Test Structure | ||
|
|
||
| Every API test follows: Setup → Execute → Assert. | ||
|
|
||
| ```python | ||
| from documentdb_tests.framework.assertions import assertSuccess | ||
| from documentdb_tests.framework.executor import execute_command | ||
|
|
||
| def test_descriptive_name(collection): | ||
| """Clear description of what this test validates.""" | ||
| # Setup (insert documents if needed) | ||
| collection.insert_many([{"_id": 0, "a": 1}, {"_id": 1, "a": 2}]) | ||
|
|
||
| # Execute — always use runCommand format | ||
| result = execute_command(collection, { | ||
| "find": collection.name, | ||
| "filter": {"a": 1} | ||
| }) | ||
|
|
||
| # Assert — use framework assertion helpers | ||
| assertSuccess(result, [{"_id": 0, "a": 1}]) | ||
| ``` | ||
|
|
||
| ## Naming | ||
|
|
||
| **Files:** `test_<feature_area>.py` — files in feature subfolders must include the feature name. | ||
| ``` | ||
| ✅ /tests/aggregate/unwind/test_unwind_path.py | ||
| ❌ /tests/aggregate/unwind/test_path.py | ||
| ``` | ||
|
|
||
| **Functions:** `test_<what_is_being_tested>` — descriptive, self-documenting. | ||
| ``` | ||
| ✅ test_find_with_gt_operator, test_unwind_preserves_null_arrays | ||
| ❌ test_1, test_query, test_edge_case | ||
| ``` | ||
|
|
||
| ## Assertions | ||
|
|
||
| Use helpers from `framework.assertions`, not plain `assert`: | ||
|
|
||
| ```python | ||
| # assertResult — parametrized tests mixing success and error cases | ||
| assertResult(result, expected=5) # checks cursor.firstBatch == [{"result": 5}] | ||
| assertResult(result, error_code=16555) # checks error code only | ||
|
|
||
| # assertSuccess — raw command output | ||
| assertSuccess(result, [{"_id": 0, "a": 1}]) | ||
| assertSuccess(result, expected, ignore_order=True) | ||
|
|
||
| # assertFailureCode — error cases (only check code, not message) | ||
| assertFailureCode(result, 14) | ||
eerxuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ``` | ||
|
|
||
| **One assertion per test function.** Split multiple assertions into separate tests. | ||
eerxuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## Fixtures | ||
|
|
||
| - `collection` — most common. Auto cleanup after test. Insert documents in test body. | ||
| - `database_client` — when you need multiple collections or database-level ops. Auto dropped after test. | ||
eerxuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - `engine_client` — raw client access. | ||
|
|
||
| ## Execute Command | ||
|
|
||
| Always use `execute_command()` with runCommand format to get test result, not driver methods. Setups can use methods. | ||
|
|
||
| ```python | ||
| # ✅ runCommand format | ||
| result = execute_command(collection, {"find": collection.name, "filter": {"a": 1}}) | ||
|
|
||
| # ❌ Driver methods | ||
| result = collection.find({"a": 1}) | ||
| ``` | ||
|
|
||
| ## Helper Functions | ||
|
|
||
| Avoid deep helper function chains. One layer of abstraction on top of `execute_command()` is acceptable, don't add more abstraction layers unless justified. | ||
|
|
||
| ```python | ||
| # ✅ Good: execute_expression wraps execute_command with aggregate pipeline boilerplate | ||
| result = execute_expression(collection, {"$add": [1, 2]}) | ||
|
|
||
| # ❌ Bad: trivial wrappers that just save a few characters add indirection for no clarity gain | ||
| # result = execute_operator(collection, "$add", [1, 2]) | ||
| ``` | ||
|
|
||
| Keep helpers in `utils/` at each test level. Helpers should reduce meaningful boilerplate (e.g., building an aggregate pipeline), not just shorten a single line. | ||
|
|
||
| Minimize helper scope — one helper should do one thing. If a helper has many if/else branches handling different cases, split it into separate helpers at a lower folder level. | ||
|
|
||
| ## Parametrized Tests | ||
|
|
||
| Use `@pytest.mark.parametrize` with dataclasses for operators with many test cases. Dataclasses give named fields instead of anonymous tuples, so `test.dividend` is readable where `test[0]` is not. They also provide automatic `__repr__` for clear test IDs in output, and `frozen=True` prevents accidental mutation between test runs: | ||
|
|
||
| ```python | ||
| @dataclass(frozen=True) | ||
eerxuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| class DivideTest(BaseTestCase): | ||
| dividend: Any = None | ||
| divisor: Any = None | ||
|
|
||
| DIVIDE_TESTS: list[DivideTest] = [ | ||
| DivideTest("int32", dividend=10, divisor=2, expected=5.0, msg="Should divide int32 values"), | ||
| DivideTest("null_divisor", dividend=10, divisor=None, expected=None, msg="Should return null when divisor is null"), | ||
| DivideTest("string_err", dividend=10, divisor="string", error_code=TYPE_MISMATCH_ERROR, msg="Should reject string"), | ||
| ] | ||
|
|
||
| @pytest.mark.parametrize("test", DIVIDE_TESTS, ids=lambda t: t.id) | ||
| def test_divide(collection, test): | ||
| """Test $divide operator.""" | ||
| result = execute_expression(collection, {"$divide": [test.dividend, test.divisor]}) | ||
| assertResult(result, expected=test.expected, error_code=test.error_code, msg=test.msg) | ||
| ``` | ||
|
|
||
| - `BaseTestCase` (from `framework.test_case`) provides `id`, `expected`, `error_code`, `msg` — extend it per operator | ||
| - Shared helpers/dataclasses live in `utils/` at each level | ||
eerxuan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - `msg` is **required** — describes expected behavior, not input | ||
| - Use constants from `framework.test_constants` (`INT32_MAX`, `FLOAT_NAN`, etc.) and `framework.error_codes` (`TYPE_MISMATCH_ERROR`, etc.) | ||
|
|
||
| ## Validation | ||
|
|
||
| A pytest hook auto-validates during collection: | ||
| - Files must match `test_*.py` (except `__init__.py`) | ||
| - Test functions must have docstrings | ||
| - Must use assertion helpers, not plain `assert` | ||
| - One assertion per test function | ||
| - Must use `execute_command()` or helpers from utils | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.