Core Principles
Mandatory Requirement: All new features, bug fixes, and refactoring must achieve over 80% test coverage。
RED-GREEN-REFACTOR Cycle
1. RED (Write a test, the test fails)
Write the test first, verify that the test fails (because the feature is not yet implemented).
2. GREEN (Implement the code, the test passes)
Write the minimum code to make the test pass.
3. REFACTOR (Refactor the code, keep the tests passing)
Optimize the code and keep all tests passing.
Workflow
Step 1: Write user stories
作为用户,我希望能够创建新的饲料配方。
验收标准:
- 配方名称必填,长度 2-50 个字符
- 品种代码必填,必须是有效的品种
- 创建成功后返回配方 ID
Step 2: Generate test cases
Rust tests:
#[tokio::test]
async fn test_create_formula_success() { }
#[tokio::test]
async fn test_create_formula_empty_name() { }
#[tokio::test]
async fn test_create_formula_name_too_long() { }
#[tokio::test]
async fn test_create_formula_invalid_species() { }
TypeScript tests:
describe('FormulaForm', () => {
it('should create formula successfully', async () => { });
it('should show error when name is empty', async () => { });
it('should show error when name is too long', async () => { });
});
Step 3: Run the tests (RED)
cargo test # Rust
npm test # TypeScript
Expected result: The tests fail.
Step 4: Implement the code (GREEN)
pub async fn create_formula(&self, dto: CreateFormulaDto) -> Result<i64> {
if dto.name.is_empty() {
return Err(anyhow!("配方名称不能为空"));
}
let formula_id = sqlx::query!(
"INSERT INTO formulas (name, species_code) VALUES (?, ?)",
dto.name, dto.species_code
)
.execute(&self.pool)
.await?
.last_insert_rowid();
Ok(formula_id)
}
Step 5: Run the tests again
cargo test
npm test
Expected result: All tests pass.
Step 6: Refactor (REFACTOR)
// 提取验证逻辑
fn validate_formula_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(anyhow!("配方名称不能为空"));
}
Ok(())
}
pub async fn create_formula(&self, dto: CreateFormulaDto) -> Result<i64> {
validate_formula_name(&dto.name)?;
// ...
}
Step 7: Verify coverage
cargo tarpaulin --out Html # Rust
npm run test:coverage # TypeScript
Requirement: Coverage >= 80%.
Test Types
1. Unit tests
#[test]
fn test_validate_formula_name() {
assert!(validate_formula_name("测试配方").is_ok());
assert!(validate_formula_name("").is_err());
}
2. Integration tests
#[tokio::test]
async fn test_create_formula_integration() {
let pool = setup_test_db().await;
let repo = FormulaRepository::new(Arc::new(pool));
let dto = CreateFormulaDto {
name: "测试配方".to_string(),
species_code: "PIG".to_string(),
};
let result = repo.create(dto).await;
assert!(result.is_ok());
cleanup_test_db().await;
}
Mock Examples
Mock Tauri commands (TypeScript)
import { vi } from 'vitest';
vi.mock('../bindings', () => ({
commands: {
createFormula: vi.fn(),
},
}));
it('should create formula', async () => {
vi.mocked(commands.createFormula).mockResolvedValue({
success: true,
data: { id: 1 },
});
const result = await createFormula(dto);
expect(result.success).toBe(true);
});
Mock database (Rust)
use mockall::mock;
mock! {
pub FormulaRepository {
async fn create(&self, dto: CreateFormulaDto) -> Result<i64>;
}
}
#[tokio::test]
async fn test_with_mock() {
let mut mock_repo = MockFormulaRepository::new();
mock_repo.expect_create().returning(|_| Ok(1));
let service = FormulaService::new(Arc::new(mock_repo));
let result = service.create_formula(dto).await;
assert!(result.is_ok());
}
Best Practices
- Test user behavior, not implementation → Test "show a success message after clicking submit"
- Keep tests fast → Unit tests < 100ms
- Mock external dependencies → Mock databases, APIs, file systems
- Test isolation → Each test runs independently
- Test error paths → Test both normal and error cases
- Descriptive test names →
test_validate_formula_name_rejects_empty_string
Pre-commit Checklist
- All tests pass
- Coverage >= 80%
- No skipped tests
- Clear test naming
- Boundary cases tested
- Error paths tested
- Reasonable use of mocks
- Tests run independently
Common Pitfalls
- Testing implementation details → Should test user-visible behavior
- Dependencies between tests → Set up data independently for each test
- Over-mocking → Only mock external dependencies
- Ignoring async issues → Handle all asynchronous operations correctly