Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing

BotServer follows comprehensive testing practices to ensure reliability, performance, and maintainability of the codebase.

Overview

Testing in BotServer covers:

  • Unit tests for individual functions
  • Integration tests for components
  • End-to-end tests for workflows
  • Performance benchmarks
  • BASIC script testing

Test Organization

Directory Structure

src/
├── module/
│   ├── mod.rs         # Module code
│   └── mod.test.rs    # Module tests
├── basic/keywords/
│   ├── keyword.rs     # Keyword implementation
│   └── keyword.test.rs # Keyword tests
tests/
├── integration/       # Integration tests
└── e2e/              # End-to-end tests

Test Files

Tests are colocated with source code:

  • module.rs - Implementation
  • module.test.rs - Tests
  • Or inline #[cfg(test)] modules

Running Tests

All Tests

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_name

# Run tests in module
cargo test module_name::

Test Coverage

# Install tarpaulin
cargo install cargo-tarpaulin

# Generate coverage report
cargo tarpaulin --out Html

# View coverage
open tarpaulin-report.html

Unit Testing

Basic Test Structure

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_function_success() {
        // Arrange
        let input = "test";
        
        // Act
        let result = function_under_test(input);
        
        // Assert
        assert_eq!(result, expected);
    }

    #[test]
    #[should_panic(expected = "error message")]
    fn test_function_failure() {
        function_that_panics();
    }
}
}

Async Tests

#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_async_function() {
    let result = async_function().await;
    assert!(result.is_ok());
}
}

Integration Testing

Database Tests

#![allow(unused)]
fn main() {
#[test]
fn test_database_operation() {
    // Use test database
    let conn = establish_test_connection();
    
    // Run migrations
    run_pending_migrations(&conn).unwrap();
    
    // Test operation
    let result = create_user(&conn, "test_user");
    assert!(result.is_ok());
    
    // Cleanup
    rollback_transaction(&conn);
}
}

API Tests

#![allow(unused)]
fn main() {
#[tokio::test]
async fn test_api_endpoint() {
    // Create test app
    let app = create_test_app().await;
    
    // Make request
    let response = app
        .oneshot(
            Request::builder()
                .uri("/api/health")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    
    // Assert response
    assert_eq!(response.status(), StatusCode::OK);
}
}

BASIC Script Testing

Testing Keywords

#![allow(unused)]
fn main() {
#[test]
fn test_custom_keyword() {
    let mut engine = Engine::new();
    let state = create_test_state();
    
    // Register keyword
    register_keyword(&state, &mut engine);
    
    // Execute script
    let script = r#"
        let result = MY_KEYWORD("input");
        result
    "#;
    
    let result: String = engine.eval(script).unwrap();
    assert_eq!(result, "expected output");
}
}

Testing Script Compilation

#![allow(unused)]
fn main() {
#[test]
fn test_script_compilation() {
    let compiler = BasicCompiler::new(test_state(), test_bot_id());
    
    let script_path = "test.bas";
    let result = compiler.compile_file(script_path, "work_dir");
    
    assert!(result.is_ok());
    assert!(result.unwrap().mcp_tool.is_some());
}
}

Test Utilities

Test Fixtures

#![allow(unused)]
fn main() {
// test_utils.rs
pub fn create_test_state() -> Arc<AppState> {
    Arc::new(AppState {
        conn: create_test_pool(),
        config: test_config(),
        // ... other fields
    })
}

pub fn create_test_user() -> User {
    User {
        id: Uuid::new_v4(),
        username: "test_user".to_string(),
        email: "test@example.com".to_string(),
        // ...
    }
}
}

Mock Objects

#![allow(unused)]
fn main() {
use mockall::*;

#[automock]
trait EmailService {
    fn send_email(&self, to: &str, subject: &str, body: &str) -> Result<()>;
}

#[test]
fn test_with_mock() {
    let mut mock = MockEmailService::new();
    mock.expect_send_email()
        .times(1)
        .returning(|_, _, _| Ok(()));
    
    // Use mock in test
}
}

Performance Testing

Benchmarks

#![allow(unused)]
#![feature(test)]
fn main() {
extern crate test;

#[cfg(test)]
mod bench {
    use super::*;
    use test::Bencher;

    #[bench]
    fn bench_function(b: &mut Bencher) {
        b.iter(|| {
            function_to_benchmark()
        });
    }
}
}

Load Testing

# Using cargo-stress
cargo install cargo-stress
cargo stress --test load_test

# Custom load test
#[test]
#[ignore] // Run with --ignored flag
fn test_high_load() {
    let handles: Vec<_> = (0..100)
        .map(|_| {
            thread::spawn(|| {
                // Simulate load
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Test Best Practices

Test Naming

#![allow(unused)]
fn main() {
// Good: Descriptive names
#[test]
fn test_user_creation_with_valid_email_succeeds() {}

#[test]
fn test_user_creation_with_invalid_email_fails() {}

// Bad: Generic names
#[test]
fn test1() {}
}

Test Independence

#![allow(unused)]
fn main() {
// Each test should be independent
#[test]
fn test_independent_1() {
    let state = create_fresh_state();
    // Test logic
}

#[test]
fn test_independent_2() {
    let state = create_fresh_state(); // Fresh state
    // Test logic
}
}

Test Data

#![allow(unused)]
fn main() {
// Use builders for test data
struct UserBuilder {
    username: String,
    email: String,
}

impl UserBuilder {
    fn new() -> Self {
        Self {
            username: "test_user".to_string(),
            email: "test@example.com".to_string(),
        }
    }
    
    fn with_username(mut self, username: &str) -> Self {
        self.username = username.to_string();
        self
    }
    
    fn build(self) -> User {
        User {
            username: self.username,
            email: self.email,
            // ...
        }
    }
}
}

Continuous Integration

GitHub Actions

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
      - run: cargo test --all-features
      - run: cargo clippy -- -D warnings
      - run: cargo fmt -- --check

Test Documentation

Document Test Purpose

#![allow(unused)]
fn main() {
/// Tests that user creation fails when email is invalid.
/// 
/// This test ensures that the email validation logic
/// properly rejects malformed email addresses.
#[test]
fn test_invalid_email_rejection() {
    // Test implementation
}
}

Common Testing Patterns

Arrange-Act-Assert

#![allow(unused)]
fn main() {
#[test]
fn test_pattern() {
    // Arrange
    let input = prepare_test_data();
    let expected = "expected result";
    
    // Act
    let result = function_under_test(input);
    
    // Assert
    assert_eq!(result, expected);
}
}

Given-When-Then

#![allow(unused)]
fn main() {
#[test]
fn test_user_story() {
    // Given: A user with valid credentials
    let user = create_valid_user();
    
    // When: The user attempts to login
    let result = login(user.username, user.password);
    
    // Then: The login should succeed
    assert!(result.is_ok());
}
}

Summary

Comprehensive testing ensures BotServer’s reliability and makes refactoring safe. Focus on writing clear, independent tests that cover both success and failure cases, and maintain good test coverage across the codebase.