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- Implementationmodule.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.