Testing with Utah Scripts
Learn how to do testing with Utah scripts effectively with unit tests, integration tests, and best practices.
Test Structure
Basic Test Layout
// test/math.test.shx
import "../src/math.shx";
// Test addition function
function testAddition(): void {
let result: number = add(2, 3);
assert(result == 5, "Addition should return 5");
}
// Test subtraction function
function testSubtraction(): void {
let result: number = subtract(5, 3);
assert(result == 2, "Subtraction should return 2");
}
// Run tests
testAddition();
testSubtraction();
console.log("All tests passed!");
Test Organization
// test/utils.test.shx
import "../src/utils.shx";
// Test utilities
function assert(condition: boolean, message: string): void {
if (!condition) {
console.log("FAIL: ${message}");
script.exit(1);
} else {
console.log("PASS: ${message}");
}
}
function setupTest(): void {
console.log("Setting up test environment");
// Create test directory
"$(mkdir -p /tmp/test-data)";
}
function teardownTest(): void {
// Cleanup test environment
fs.delete("/tmp/test-data");
}
Unit Testing
Testing Functions
// src/calculator.shx
function multiply(a: number, b: number): number {
return a * b;
}
function divide(a: number, b: number): number {
if (b == 0) {
throw new Error("Division by zero");
}
return a / b;
}
// test/calculator.test.shx
import "../src/calculator.shx";
function testMultiply(): void {
assert(multiply(3, 4) == 12, "3 * 4 should equal 12");
assert(multiply(0, 5) == 0, "0 * 5 should equal 0");
assert(multiply(-2, 3) == -6, "-2 * 3 should equal -6");
}
function testDivide(): void {
assert(divide(10, 2) == 5, "10 / 2 should equal 5");
assert(divide(7, 3) == 2, "7 / 3 should equal 2 (integer division)");
// Test error handling
try {
divide(5, 0);
assert(false, "Division by zero should throw error");
} catch (error) {
assert(true, "Division by zero correctly throws error");
}
}
Testing File Operations
// test/file-operations.test.shx
function testFileOperations(): void {
let testFile: string = "/tmp/test.txt";
// Create file
fs.writeFile(testFile, "Hello, Utah!");
assert(fs.exists(testFile), "File should be created");
// Read file
let content: string = fs.readFile(testFile);
assert(content == "Hello, Utah!", "File content should match");
// Delete file
fs.delete(testFile);
assert(!fs.exists(testFile), "File should be deleted");
}
Integration Testing
Testing API Interactions
// test/api-integration.test.shx
function testApiIntegration(): void {
// Test API endpoint
let response: string = web.get("https://api.github.com/repos/polatengin/utah");
let data: object = json.parse(response);
assert(data.name == "utah", "Repository name should be 'utah'");
assert(data.full_name == "polatengin/utah", "Full name should match");
}
function testWebhookHandler(): void {
// Test webhook processing
let payload: string = "{\"action\": \"push\", \"repository\": {\"name\": \"utah\"}}";
let result: boolean = processWebhook(payload);
assert(result == true, "Webhook should be processed successfully");
}
Testing System Integration
// test/system-integration.test.shx
function testSystemIntegration(): void {
// Test system commands
let result: string = system.execute("echo 'test'");
assert(result.trim() == "test", "System command should return 'test'");
// Test environment variables
system.setEnv("TEST_VAR", "test_value");
let value: string = system.env("TEST_VAR");
assert(value == "test_value", "Environment variable should be set");
}
Test Automation
Test Runner Script
// test/run-tests.shx
let testFiles: string[] = [
"test/math.test.shx",
"test/calculator.test.shx",
"test/file-operations.test.shx",
"test/api-integration.test.shx"
];
let passed: number = 0;
let failed: number = 0;
for (let testFile of testFiles) {
console.log("Running ${testFile}...");
try {
system.execute("utah ${testFile}");
console.log("✅ ${testFile} passed");
passed++;
} catch (error) {
console.log("❌ ${testFile} failed");
failed++;
}
}
console.log("\nTest Results: ${passed} passed, ${failed} failed");
if (failed > 0) {
script.exit(1);
}
Continuous Integration
# .github/workflows/test.yml
name: Test Utah Scripts
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Utah
run: |
curl -sL https://utahshx.com/install.sh | sudo bash
- name: Run Tests
run: |
utah test/run-tests.shx
Mocking and Stubbing
Mocking External Dependencies
// test/mocks.shx
let mockApiResponse: string = "{\"status\": \"success\", \"data\": {\"id\": 1}}";
function mockWebGet(url: string): string {
console.log("Mock: GET ${url}");
return mockApiResponse;
}
// Override web.get for testing
web.get = mockWebGet;
Stubbing System Commands
// test/stubs.shx
function stubSystemExecute(command: string): string {
if (command == "git status") {
return "On branch main\nnothing to commit, working tree clean";
}
return "Command not found";
}
// Override system.execute for testing
system.execute = stubSystemExecute;
Performance Testing
Benchmarking Functions
// test/performance.test.shx
function benchmarkFunction(func: Function, iterations: number): number {
let startTime: number = utility.timestamp();
for (let i: number = 0; i < iterations; i++) {
func();
}
let endTime: number = utility.timestamp();
return endTime - startTime;
}
function testPerformance(): void {
let executionTime: number = benchmarkFunction(() => {
// Function to benchmark
utility.hash("test string");
}, 1000);
console.log("Execution time: ${executionTime}ms");
assert(executionTime < 5000, "Function should execute in under 5 seconds");
}
Best Practices
Test File Organization
- Keep tests in a separate
test/
directory - Use descriptive test function names
- Group related tests together
- Use setup and teardown functions for common operations
Test Coverage
- Test normal cases and edge cases
- Test error conditions and exception handling
- Test boundary conditions
- Test integration points
Test Data Management
- Use temporary files for file system tests
- Clean up test data after each test
- Use consistent test data across tests
- Avoid dependencies on external services in unit tests
Assertions and Reporting
- Use clear assertion messages
- Provide detailed error information
- Log test progress and results
- Exit with appropriate codes for CI/CD integration