Agent skill
ocaml-testing
Testing strategies for OCaml libraries. Use when discussing tests, alcotest, eio mocks, test structure, test-driven development, or cram tests in OCaml projects.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/ocaml-testing
SKILL.md
OCaml Testing
Core Philosophy
- Unit Tests First: Prioritize unit tests for individual modules.
- 1:1 Test Coverage: Every
lib/*.mlshould havetest/test_*.ml. - Isolated Tests: Each test independent, no external state.
- Clear Names: Describe what is tested, not how.
Test Organization
project/
├── lib/
│ ├── user.ml
│ └── auth.ml
├── test/
│ ├── dune
│ ├── test.ml # Main runner
│ ├── test_user.ml # Tests for user.ml
│ └── test_auth.ml # Tests for auth.ml
└── third_party/ # Fetched sources for reference
Rules:
- Test file
test_foo.mltests library modulefoo.ml - Every test module exports
suite : string * unit Alcotest.test_case list - Main
test.mlaggregates all suites
Basic Test Structure
ocaml
(* test/test_user.ml *)
let test_create () =
let user = User.create ~name:"Alice" in
Alcotest.(check string) "name" "Alice" (User.name user)
let test_validate_empty () =
let result = User.create ~name:"" in
Alcotest.(check bool) "fails" true (Result.is_error result)
let suite = ("user", [
"create", `Quick, test_create;
"validate_empty", `Quick, test_validate_empty;
])
ocaml
(* test/test.ml *)
let () = Alcotest.run "MyProject" [
Test_user.suite;
Test_auth.suite;
]
Test Naming
- Suite names: lowercase, single word (
"user","auth") - Case names: lowercase with underscores (
"create","parse_error")
Dune Configuration
lisp
(test
(name test)
(libraries mylib alcotest))
For Eio-based libraries:
lisp
(test
(name test)
(libraries mylib alcotest eio_main eio.mock))
Testing with Eio Mocks
Prefer mocks for deterministic, fast tests.
ocaml
let test_with_mock_clock () =
Eio_mock.Backend.run @@ fun () ->
let clock = Eio_mock.Clock.make () in
Eio_mock.Clock.advance clock 1.0;
Alcotest.(check bool) "advanced" true true
let test_with_mock_flow () =
Eio_mock.Backend.run @@ fun () ->
let flow = Eio_mock.Flow.make "test" in
Eio_mock.Flow.on_read flow [
`Return "data";
`Raise End_of_file;
];
(* test with flow *)
Mock modules: Eio_mock.Backend, Eio_mock.Clock, Eio_mock.Flow, Eio_mock.Net, Eio_mock.Fs
Cram Tests (End-to-End)
For CLI/executable testing. Use directories ending in .t/.
test/
└── my_feature.t/
├── run.t # Test script
└── input.txt # Real test files (not cat << EOF)
Rules:
- Create actual files in directory, don't embed with
cat > file << EOF - Test the compiled executable behaviour
- Use for integration/CLI tests, not unit tests
Coverage Checklist
For each module:
- Test all public functions from
.mli - Test success cases
- Test error cases
- Test edge cases (empty, max values, invalid input)
Running Tests
bash
dune runtest # Run all tests
dune runtest --verbose # Verbose output
dune exec test/test.exe # Run specific test
dune test --instrument-with bisect_ppx # With coverage
Best Practices
- Prefer mocks over real I/O - Fast, deterministic
- Test edge cases - Empty, max, invalid
- One assertion per test when practical
- Clean up resources - Even in tests
- Keep integration tests minimal - Most should be unit tests
Didn't find tool you were looking for?