botserver/docs/src/chapter-13-community/testing.md

423 lines
7.4 KiB
Markdown
Raw Normal View History

2025-11-24 13:02:30 -03:00
# 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
```bash
# 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
```bash
# Install tarpaulin
cargo install cargo-tarpaulin
# Generate coverage report
cargo tarpaulin --out Html
# View coverage
open tarpaulin-report.html
```
## Unit Testing
### Basic Test Structure
```rust
#[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
```rust
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
```
## Integration Testing
### Database Tests
```rust
#[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
```rust
#[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
```rust
#[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
```rust
#[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
```rust
// 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
```rust
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
```rust
#![feature(test)]
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
```bash
# 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
```rust
// 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
```rust
// 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
```rust
// 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
```yaml
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
```rust
/// 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
```rust
#[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
```rust
#[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.