# TEO **TEO** (Test of Expected Output) is a unit testing framework designed for TDD (Test-Driven Development) and post-refactor validation. TEO allows you to: - Validate functions after modifications by ensuring outputs match expected results across different test cases - Design behavior dynamics before implementing internal logic - Create declarative tests using JSON and custom evaluators --- ## Installation ```bash npm install teo ``` --- ## Basic Usage ```js import test from 'teo'; const rutineResult = await test('/path/to/_teo', 'rutine-name-id'); console.log(rutineResult); ``` **Parameters:** - `path`: Path to the test folder (usually `_teo`) - `rutineKey`: Name of the rutine folder in `_teo/rutines/{rutine-name-id}/` --- ## Directory Structure ``` _teo/ ├── rutines/ │ └── {rutine-name-id}/ │ └── index.json ├── cases/ │ └── {case-name-id}/ │ └── index.json ├── evals/ │ └── {eval-name-id}/ │ └── index.mjs ├── asserts/ │ └── {assert-name-id}/ │ └── index.mjs └── resources/ └── * (any files) ``` --- ## Schemas ### Rutine **File:** `_teo/rutines/{rutine-name-id}/index.json` ```json { "title": "Rutine 01", "onFail": "CONTINUE", "cases": [ { "caseKey": "buy", "onFail": "BREAK" }, { "caseKey": "sell" } ] } ``` **Fields:** - `title` (string): Descriptive title for the rutine - `onFail` (string): Behavior when a case fails - `"CONTINUE"` (default): Continue to next case - `"BREAK"`: Stop entire rutine execution - `cases` (array): List of case references - `caseKey` (string): References a case in `_teo/cases/{caseKey}/` - `onFail` (string, optional): Overrides rutine-level `onFail` for this case --- ### Case **File:** `_teo/cases/{case-name-id}/index.json` ```json { "eval": "buy-validity", "input": { "apples": 4 }, "expected_output": { "apples": 10 } } ``` **Fields:** - `eval` (string): References an evaluator in `_teo/evals/{eval}/` - `input` (any): Input data to pass to the evaluator - `expected_output` (any, optional): Expected output for validation - If not provided, TEO will try to use the `OutputGenerator` from the evaluator - If neither exists, the case will execute without validation (status: `RESOLVED`) --- ### Eval **File:** `_teo/evals/{eval-name-id}/index.mjs` ```js async function Evaluator(input) { // Process the input and return the actual output // This output will be compared with expected_output return { apples: input.apples * 2 }; } async function OutputGenerator(input) { // Optional: Generate expected_output dynamically from input // Only called if case doesn't define expected_output return { apples: 10 }; } const ASSERT_KEY = ""; // Empty string (default): Use strict equality (===) // Or specify a custom assert: "deep-equal", "less", etc. export { Evaluator, OutputGenerator, ASSERT_KEY }; ``` **Exports:** - `Evaluator` (required): Function that processes input and returns actual output - `OutputGenerator` (optional): Function that generates expected output if not defined in case - `ASSERT_KEY` (optional): Specifies which assert to use for validation - Empty string `""` or omitted: Uses strict equality (`===`) - Built-in asserts: `"less"`, `"lessOrEqual"`, `"greater"`, `"greaterOrEqual"` - Custom asserts: References `_teo/asserts/{ASSERT_KEY}/index.mjs` --- ### Assert **File:** `_teo/asserts/{assert-name-id}/index.mjs` ```js export default async function(actualOutput, expectedOutput) { // Compare actualOutput with expectedOutput // Return true if validation passes, false otherwise return actualOutput < expectedOutput; } ``` **Signature:** - `actualOutput` (any): Output returned by the Evaluator - `expectedOutput` (any): Expected output from case or OutputGenerator - **Returns:** `boolean` - `true` if validation passes, `false` if it fails --- ## Built-in Asserts TEO includes the following asserts automatically: - `less`: `actualOutput < expectedOutput` - `lessOrEqual`: `actualOutput <= expectedOutput` - `greater`: `actualOutput > expectedOutput` - `greaterOrEqual`: `actualOutput >= expectedOutput` To use them, set `ASSERT_KEY` in your evaluator: ```js const ASSERT_KEY = "less"; export { Evaluator, ASSERT_KEY }; ``` --- ## Execution Workflow When you run `test(path, rutineKey)`, TEO executes the following steps: 1. **Load Rutine**: Read `_teo/rutines/{rutineKey}/index.json` 2. **For each case** (in sequential order): 1. Load case data from `_teo/cases/{caseKey}/index.json` 2. Load evaluator from `_teo/evals/{eval}/index.mjs` 3. **Generate expected output** (if needed): - If case has `expected_output`, use it - Else if evaluator has `OutputGenerator`, call `OutputGenerator(input)` - Else, proceed without validation 4. **Execute evaluator**: Call `Evaluator(input)` to get `actualOutput` 5. **Validate** (if expected output exists): - If `ASSERT_KEY` is empty: Compare with strict equality (`===`) - Else: Load and execute assert from `_teo/asserts/{ASSERT_KEY}/index.mjs` 6. **Handle result**: - **PASS**: Validation succeeded - **FAIL**: Validation failed (assert returned false) - **ERROR**: Exception occurred during execution - **RESOLVED**: Executed without validation 7. **Check onFail**: - If case failed/errored and `onFail: "BREAK"`, stop rutine execution - Else continue to next case 3. **Return RutineResult** --- ## Result Structures ### RutineResult ```js { rutineKey: 'rutine-01', title: 'Rutine 01', totalCases: 5, passed: 2, // Cases that passed validation failed: 1, // Cases that failed validation resolved: 2, // Cases executed without validation status: 'COMPLETED', // 'COMPLETED' or 'BROKEN' (if BREAK occurred) failCase: 'buy', // caseKey of first failed case, or null cases: [ // Array of CaseResult objects ] } ``` --- ### CaseResult #### PASS ```js { caseKey: 'buy', status: 'PASS', input: { apples: 4 }, expectedOutput: { apples: 10 }, actualOutput: { apples: 10 } } ``` #### FAIL ```js { caseKey: 'sell', status: 'FAIL', input: { apples: 5 }, expectedOutput: { apples: 20 }, actualOutput: { apples: 15 } } ``` #### ERROR ```js { caseKey: 'delete', status: 'ERROR', input: { id: 123 }, error: 'Database connection failed', stack: '...' } ``` #### RESOLVED ```js { caseKey: 'update', status: 'RESOLVED', input: { id: 456 }, actualOutput: { success: true } } ``` --- ## Error Handling ### Exceptions During Execution If an exception occurs in `Evaluator`, `OutputGenerator`, or `Assert`: - The case is marked with status `ERROR` - The error message and stack trace are captured in the `CaseResult` - If `onFail: "BREAK"`, the rutine stops immediately ### Validation Failures If the assert returns `false`: - The case is marked with status `FAIL` - Both `expectedOutput` and `actualOutput` are included in the `CaseResult` - If `onFail: "BREAK"`, the rutine stops immediately ### Missing References If a referenced file doesn't exist: - Case references non-existent `caseKey` - Case references non-existent `eval` - Eval uses non-existent `ASSERT_KEY` **TEO will throw an error and stop execution immediately.** --- ## Complete Example ### Directory Structure ``` my-project/ ├── _teo/ │ ├── rutines/ │ │ └── shopping-tests/ │ │ └── index.json │ ├── cases/ │ │ ├── buy-apples/ │ │ │ └── index.json │ │ └── sell-apples/ │ │ └── index.json │ └── evals/ │ └── apple-calculator/ │ └── index.mjs └── test.js ``` ### Rutine **`_teo/rutines/shopping-tests/index.json`** ```json { "title": "Shopping Tests", "onFail": "CONTINUE", "cases": [ { "caseKey": "buy-apples" }, { "caseKey": "sell-apples" } ] } ``` ### Cases **`_teo/cases/buy-apples/index.json`** ```json { "eval": "apple-calculator", "input": { "apples": 4 }, "expected_output": { "apples": 8 } } ``` **`_teo/cases/sell-apples/index.json`** ```json { "eval": "apple-calculator", "input": { "apples": 10 }, "expected_output": { "apples": 5 } } ``` ### Evaluator **`_teo/evals/apple-calculator/index.mjs`** ```js async function Evaluator(input) { return { apples: input.apples * 2 }; } const ASSERT_KEY = ""; export { Evaluator, ASSERT_KEY }; ``` ### Test Runner **`test.js`** ```js import test from 'teo'; const result = await test('./_teo', 'shopping-tests'); console.log(result); /* { rutineKey: 'shopping-tests', title: 'Shopping Tests', totalCases: 2, passed: 1, failed: 1, resolved: 0, status: 'COMPLETED', failCase: 'sell-apples', cases: [ { caseKey: 'buy-apples', status: 'PASS', input: { apples: 4 }, expectedOutput: { apples: 8 }, actualOutput: { apples: 8 } }, { caseKey: 'sell-apples', status: 'FAIL', input: { apples: 10 }, expectedOutput: { apples: 5 }, actualOutput: { apples: 20 } } ] } */ ``` --- ## Resources The `_teo/resources/` folder can contain any files (JSON, text, images, etc.) that your tests might need. Currently, TEO doesn't provide a specific API to access resources. You can use standard Node.js methods like `import` or `fs` to load them from your evaluators. --- ## License MIT