Unit Testing Guide [AIR-3][AIS-3][AIT-3][RES-3]¶
Comprehensive unit testing strategies for Anya-core extensions, ensuring component-level reliability and BIP compliance.
Overview¶
Unit tests form the foundation of the Anya-core testing pyramid, providing fast feedback on individual components. Each module must maintain comprehensive unit test coverage with focus on Bitcoin protocol compliance, Web5 integration, and ML system validation.
Testing Framework¶
Core Dependencies¶
[dev-dependencies]
tokio-test = "0.4"
mockall = "0.11"
proptest = "1.0"
bitcoin-test-utils = "0.1"
web5-mock = "0.2"
ml-test-kit = "0.3"
serial_test = "0.9"
Test Structure¶
#[cfg(test)]
mod tests {
use super::*;
use mockall::predicate::*;
use proptest::prelude::*;
use tokio_test;
// Test module organization
mod bitcoin_tests;
mod web5_tests;
mod ml_tests;
mod integration_tests;
}
Bitcoin Unit Testing¶
Transaction Validation Tests¶
#[cfg(test)]
mod bitcoin_transaction_tests {
use super::*;
use bitcoin::{Transaction, TxIn, TxOut, OutPoint, Script, Witness};
use bitcoin::secp256k1::{Secp256k1, SecretKey};
#[test]
fn test_valid_p2pkh_transaction() {
let secp = Secp256k1::new();
let secret_key = SecretKey::from_slice(&[1; 32]).unwrap();
let public_key = secret_key.public_key(&secp);
let tx = Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime::ZERO,
input: vec![create_test_input()],
output: vec![create_p2pkh_output(&public_key)],
};
let validator = TransactionValidator::new();
assert!(validator.validate_structure(&tx).is_ok());
assert!(validator.validate_signatures(&tx).is_ok());
}
#[test]
fn test_invalid_transaction_negative_value() {
let tx = Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime::ZERO,
input: vec![create_test_input()],
output: vec![TxOut {
value: -1, // Invalid negative value
script_pubkey: Script::new(),
}],
};
let validator = TransactionValidator::new();
assert!(validator.validate_structure(&tx).is_err());
}
#[test]
fn test_bip141_witness_validation() {
let witness_tx = create_segwit_transaction();
let validator = TransactionValidator::new();
// Test witness structure
assert!(validator.validate_witness(&witness_tx).is_ok());
// Test witness commitment
assert!(validator.validate_witness_commitment(&witness_tx).is_ok());
}
}
Script Testing¶
#[cfg(test)]
mod script_tests {
use super::*;
use bitcoin::script::{Builder, Instruction};
#[test]
fn test_p2pkh_script_execution() {
let pubkey_hash = [0u8; 20];
let script = Builder::new()
.push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(&pubkey_hash)
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script();
let interpreter = ScriptInterpreter::new();
let result = interpreter.execute(&script, &create_test_stack());
assert!(result.is_ok());
}
#[test]
fn test_multisig_script_validation() {
let pubkeys = vec![
create_test_pubkey(1),
create_test_pubkey(2),
create_test_pubkey(3),
];
let multisig_script = Builder::new()
.push_int(2) // 2-of-3 multisig
.push_slice(&pubkeys[0].serialize())
.push_slice(&pubkeys[1].serialize())
.push_slice(&pubkeys[2].serialize())
.push_int(3)
.push_opcode(opcodes::all::OP_CHECKMULTISIG)
.into_script();
let validator = ScriptValidator::new();
assert!(validator.validate_multisig(&multisig_script, 2, 3).is_ok());
}
}
Web5 Unit Testing¶
DID Testing¶
#[cfg(test)]
mod web5_did_tests {
use super::*;
use web5::{DID, DidDocument, VerificationMethod};
#[test]
fn test_did_creation_and_validation() {
let did = DID::new("web5", "example.com", "alice").unwrap();
assert_eq!(did.method(), "web5");
assert_eq!(did.method_specific_id(), "example.com:alice");
assert!(did.is_valid());
}
#[test]
fn test_did_document_resolution() {
let did = DID::parse("did:web5:example.com:alice").unwrap();
let document = DidDocument::builder()
.id(did.clone())
.verification_method(VerificationMethod::new(
format!("{}#key-1", did),
"Ed25519VerificationKey2020",
did.clone(),
"z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
))
.build();
let resolver = DidResolver::new();
let resolved = resolver.resolve(&did).unwrap();
assert_eq!(resolved.id(), &did);
}
#[test]
fn test_verifiable_credential_creation() {
let credential = VerifiableCredential::builder()
.issuer("did:web5:issuer.com")
.subject("did:web5:subject.com")
.credential_type("UniversityDegree")
.claim("degree", "Bachelor of Science")
.build()
.unwrap();
assert!(credential.verify().is_ok());
assert_eq!(credential.get_claim("degree"), Some("Bachelor of Science"));
}
}
Web5 Protocol Testing¶
#[cfg(test)]
mod web5_protocol_tests {
use super::*;
use web5::{DWN, Message, Protocol};
#[test]
fn test_dwn_message_creation() {
let message = Message::builder()
.record_id("bafyreigvp...")
.data_cid("bafyreihash...")
.date_created(Utc::now())
.protocol("https://example.com/protocol")
.build()
.unwrap();
assert!(message.is_valid());
assert_eq!(message.protocol(), Some("https://example.com/protocol"));
}
#[test]
fn test_protocol_validation() {
let protocol = Protocol::new("https://schema.org/SocialMediaPosting");
let message = create_test_message();
assert!(protocol.validate_message(&message).is_ok());
}
}
ML Unit Testing¶
Model Validation Tests¶
#[cfg(test)]
mod ml_model_tests {
use super::*;
use ml::{Model, Prediction, TrainingData};
#[test]
fn test_model_inference() {
let model = Model::load_from_path("./test-models/simple_classifier.json").unwrap();
let input = vec![1.0, 2.0, 3.0, 4.0];
let prediction = model.predict(&input).unwrap();
assert!(prediction.confidence() > 0.0);
assert!(prediction.confidence() <= 1.0);
}
#[test]
fn test_model_training_validation() {
let training_data = TrainingData::new(
vec![vec![1.0, 2.0], vec![3.0, 4.0]],
vec![0, 1]
);
let mut model = Model::new_classifier(2, 2);
let result = model.train(&training_data);
assert!(result.is_ok());
assert!(model.accuracy() > 0.0);
}
#[test]
fn test_model_serialization() {
let model = create_test_model();
let serialized = model.serialize().unwrap();
let deserialized = Model::deserialize(&serialized).unwrap();
assert_eq!(model.get_weights(), deserialized.get_weights());
assert_eq!(model.get_bias(), deserialized.get_bias());
}
}
Data Processing Tests¶
#[cfg(test)]
mod ml_data_tests {
use super::*;
use ml::{DataProcessor, Feature, Dataset};
#[test]
fn test_feature_extraction() {
let processor = DataProcessor::new();
let raw_data = "Sample text for feature extraction";
let features = processor.extract_features(raw_data).unwrap();
assert!(!features.is_empty());
assert!(features.iter().all(|f| f.value().is_finite()));
}
#[test]
fn test_data_normalization() {
let dataset = Dataset::new(vec![
vec![1.0, 100.0, 0.1],
vec![2.0, 200.0, 0.2],
vec![3.0, 300.0, 0.3],
]);
let normalized = dataset.normalize().unwrap();
// Check mean is approximately 0
let mean = normalized.mean();
assert!((mean[0]).abs() < 0.1);
assert!((mean[1]).abs() < 0.1);
assert!((mean[2]).abs() < 0.1);
}
}
Property-Based Testing¶
Bitcoin Property Tests¶
use proptest::prelude::*;
proptest! {
#[test]
fn test_transaction_serialization_roundtrip(
version in any::<i32>(),
lock_time in any::<u32>(),
) {
let tx = Transaction {
version,
lock_time: bitcoin::PackedLockTime(lock_time),
input: vec![],
output: vec![],
};
let serialized = bitcoin::consensus::serialize(&tx);
let deserialized: Transaction = bitcoin::consensus::deserialize(&serialized).unwrap();
prop_assert_eq!(tx, deserialized);
}
#[test]
fn test_script_push_data_invariant(data in prop::collection::vec(any::<u8>(), 0..520)) {
let script = Builder::new().push_slice(&data).into_script();
let instructions: Vec<_> = script.instructions().collect();
prop_assert_eq!(instructions.len(), 1);
if let Ok(Instruction::PushBytes(pushed_data)) = &instructions[0] {
prop_assert_eq!(pushed_data.as_bytes(), &data);
}
}
}
Mock and Stub Testing¶
Bitcoin Network Mocking¶
use mockall::mock;
mock! {
BitcoinClient {
fn get_block_height(&self) -> Result<u64, Error>;
fn get_transaction(&self, txid: &Txid) -> Result<Transaction, Error>;
fn broadcast_transaction(&self, tx: &Transaction) -> Result<Txid, Error>;
}
}
#[test]
fn test_transaction_processor_with_mock() {
let mut mock_client = MockBitcoinClient::new();
mock_client
.expect_get_block_height()
.returning(|| Ok(700000));
mock_client
.expect_broadcast_transaction()
.returning(|_| Ok(Txid::all_zeros()));
let processor = TransactionProcessor::new(Box::new(mock_client));
let result = processor.process_transaction(&create_test_tx());
assert!(result.is_ok());
}
Performance Unit Tests¶
Benchmark Testing¶
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn benchmark_signature_verification(c: &mut Criterion) {
let tx = create_test_transaction();
let validator = TransactionValidator::new();
c.bench_function("signature_verification", |b| {
b.iter(|| validator.verify_signature(black_box(&tx)))
});
}
fn benchmark_script_execution(c: &mut Criterion) {
let script = create_complex_script();
let interpreter = ScriptInterpreter::new();
c.bench_function("script_execution", |b| {
b.iter(|| interpreter.execute(black_box(&script), &create_test_stack()))
});
}
criterion_group!(benches, benchmark_signature_verification, benchmark_script_execution);
criterion_main!(benches);
Test Organization Best Practices¶
Module Structure¶
// src/lib.rs
#[cfg(test)]
mod tests {
mod unit {
mod bitcoin;
mod web5;
mod ml;
}
mod helpers;
mod fixtures;
mod mocks;
}
Test Helpers¶
// tests/helpers/mod.rs
pub fn create_test_transaction() -> Transaction {
Transaction {
version: 2,
lock_time: bitcoin::PackedLockTime::ZERO,
input: vec![create_test_input()],
output: vec![create_test_output()],
}
}
pub fn create_test_private_key() -> PrivateKey {
PrivateKey::from_wif("cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA").unwrap()
}
pub fn setup_test_environment() -> TestEnvironment {
TestEnvironment {
temp_dir: tempfile::tempdir().unwrap(),
mock_network: MockNetwork::new(),
test_data: load_test_fixtures(),
}
}
Error Testing¶
Error Condition Coverage¶
#[test]
fn test_insufficient_funds_error() {
let wallet = Wallet::new_with_balance(1000);
let result = wallet.send_transaction(2000, &Address::from_str("...").unwrap());
match result {
Err(WalletError::InsufficientFunds { available, required }) => {
assert_eq!(available, 1000);
assert_eq!(required, 2000);
}
_ => panic!("Expected InsufficientFunds error"),
}
}
#[test]
fn test_network_timeout_recovery() {
let mut client = BitcoinClient::new_with_timeout(Duration::from_millis(1));
let result = client.get_block_height();
assert!(matches!(result, Err(NetworkError::Timeout)));
}
Continuous Integration¶
CI Test Configuration¶
# Run unit tests with coverage
cargo test --lib --bins --tests --benches
# Generate coverage report
cargo tarpaulin --out Html --output-dir target/coverage/
# Run property tests
cargo test --features proptest-config
Test Quality Metrics¶
- Coverage: Minimum 90% line coverage
- Performance: Unit tests under 100ms each
- Reliability: Zero flaky tests allowed
- Maintainability: Clear test names and documentation
Resources¶
- Rust Testing Documentation
- Proptest Guide
- Mockall Documentation
- Bitcoin Testing Best Practices
- Integration Testing Guide
- Performance Testing Guide
Last updated: June 7, 2025