Test coverage detector — finds source files without tests, 9 languages
Project description
open-harness-testlens
Test coverage detector. Finds source files that don't have a corresponding test file, across 9 languages. Single native binary, zero runtime dependencies.
Part of the open-harness monorepo. Español abajo.
Same tool, other ecosystems: also available on npm (
@open_harness/testlens) and on Packagist (open-harness/testlens). Identical binary, identical config; pick the registry that matches your stack.
Install
pip install open-harness-testlens
pip picks the right native wheel for your platform automatically (Linux x86_64, macOS arm64, macOS x86_64, Windows x86_64). Each wheel embeds the Go binary — no runtime deps.
Usage
testlens check # auto-detect language and scan
testlens check --lang typescript # force a specific language
testlens check --dir ./src # scan a specific directory
testlens check --config my.json # use a specific config file
testlens check --fail # exit 1 if files without tests are found
testlens init # generate a default config
testlens version # print version
Supported languages
| Language | Source extensions | Test patterns |
|---|---|---|
| Go | .go |
*_test.go |
| TypeScript | .ts, .tsx |
*.test.ts, *.spec.ts, test_*.ts |
| JavaScript | .js, .jsx |
*.test.js, *.spec.js, test_*.js |
| Python | .py |
*_test.py, test_*.py |
| Ruby | .rb |
*_spec.rb, *_test.rb |
| Rust | .rs |
*_test.rs |
| Java | .java |
*Test.java |
| Kotlin | .kt, .kts |
*Test.kt |
| C# | .cs |
*Tests.cs |
Configuration
Place a testlens.json at the repo root:
{
"language": "auto",
"exclude": ["node_modules", ".git", "vendor", "dist", "build", "testdata"]
}
language—auto(default),go,typescript,javascript,python,ruby,rust,java,kotlin,csharp.exclude— directories to skip during the scan.
Alternative: configure inside pyproject.toml or the dedicated testlens.json
If you prefer not to keep a separate testlens.json, add a testlens key in your package.json with the same shape:
{
"name": "my-project",
"testlens": {
"language": "typescript",
"exclude": ["node_modules", "dist", "fixtures"]
}
}
Precedence: --config <path> > testlens.json > package.json key > built-in defaults. CLI flags win when passed explicitly (fs.Visit); if you don't pass --lang, the value from the config is used.
Supported test layouts
testlens recognises colocated, adjacent subdirs, mirror trees, and centralised test roots that mirror the source path:
| Pattern | Example |
|---|---|
| Colocated | src/foo.ts ↔ src/foo.test.ts |
| Adjacent subdir | src/foo.ts ↔ src/__tests__/foo.test.ts |
| Centralised tests root (F-016) | src/services/foo.ts ↔ src/__tests__/services/foo.test.ts |
| Mirror (Python) | src/auth/user.py ↔ tests/auth/test_user.py |
| Mirror (Maven) | src/main/java/com/X/Bar.java ↔ src/test/java/com/X/BarTest.java |
Per language defaults:
testlens looks for tests in these locations (per language):
| Language | Colocated | Subdir | Mirror |
|---|---|---|---|
| Go | foo.go ↔ foo_test.go |
— | — |
| TypeScript / JavaScript | foo.ts ↔ foo.test.ts |
__tests__/foo.test.ts, tests/foo.spec.ts |
— |
| Python | foo.py ↔ test_foo.py |
tests/test_foo.py |
src/foo.py ↔ tests/foo.py |
| Ruby | foo.rb ↔ foo_spec.rb |
spec/foo_spec.rb |
lib/foo.rb ↔ spec/foo.rb |
| Java / Kotlin | — | — | src/main/java/.../Bar.java ↔ src/test/java/.../BarTest.java |
| Rust | foo.rs ↔ foo_test.rs |
tests/foo_test.rs |
— |
testlens vs vitest --coverage, jest, pytest-cov
These have different goals — use both:
| testlens | Real coverage tools | |
|---|---|---|
| Speed | milliseconds (file walk) | seconds to minutes (compile + execute + instrument) |
| Detects files without tests | ✅ | partial (only files actually loaded by a test) |
| Detects untested lines | ❌ | ✅ |
| Detects untested branches | ❌ | ✅ |
| Works as pre-commit hook | ✅ | too slow |
testlens is a fast smoke test of test-file existence, intended as a pre-flight gate. For real functional coverage metrics, run Vitest, Jest, pytest-cov, etc. as a separate (slower) CI step.
Why this exists
Coverage tools tell you which lines are tested. testlens tells you which files have no test at all — a different and complementary check. It surfaces orphan modules early, when adding a first test is cheapest. Combine both for a complete picture.
Integrations
# Husky pre-commit
testlens check --fail
# GitHub Actions
- name: Detect source files without tests
run: npx @open_harness/testlens check --fail --lang typescript --dir src/
Exit codes
| Code | Meaning |
|---|---|
0 |
All source files have tests (or --fail not passed) |
1 |
Files without tests found and --fail was passed, or config error |
Español
Detector de cobertura de tests. Encuentra archivos fuente que no tienen un archivo de test correspondiente, en 9 lenguajes. Un solo binario nativo, cero dependencias.
Parte del monorepo open-harness.
Instalación
pip install open-harness-testlens
pip descarga automáticamente la wheel nativa correcta para tu plataforma.
Uso
testlens check # autodetecta lenguaje y escanea
testlens check --lang typescript # fuerza un lenguaje específico
testlens check --dir ./src # escanea un directorio específico
testlens check --config my.json # usa un archivo de config específico
testlens check --fail # exit 1 si hay archivos sin test
testlens init # genera la config por defecto
testlens version # imprime la versión
Lenguajes soportados
Go, TypeScript, JavaScript, Python, Ruby, Rust, Java, Kotlin, C#. Ver la tabla arriba para las extensiones y patrones de naming de test que reconoce cada uno.
Configuración
Colocá un testlens.json en la raíz del repo (ver ejemplo arriba).
language—auto(default),go,typescript,javascript,python,ruby,rust,java,kotlin,csharp.exclude— directorios a ignorar durante el escaneo.
Alternativa: configurar dentro de pyproject.toml o testlens.json
Si preferís no tener un testlens.json separado, agregá una key testlens en tu package.json con la misma forma. Precedencia: --config <path> > testlens.json > key en package.json > defaults. Los flags CLI ganan solo cuando se pasan explícitamente; si omitís --lang, se usa el valor de la config.
Layouts de test soportados
testlens busca tests en estas ubicaciones (por lenguaje):
| Lenguaje | Co-ubicado | Subdir | Mirror |
|---|---|---|---|
| Go | foo.go ↔ foo_test.go |
— | — |
| TypeScript / JavaScript | foo.ts ↔ foo.test.ts |
__tests__/foo.test.ts, tests/foo.spec.ts |
— |
| Python | foo.py ↔ test_foo.py |
tests/test_foo.py |
src/foo.py ↔ tests/foo.py |
| Ruby | foo.rb ↔ foo_spec.rb |
spec/foo_spec.rb |
lib/foo.rb ↔ spec/foo.rb |
| Java / Kotlin | — | — | src/main/java/.../Bar.java ↔ src/test/java/.../BarTest.java |
| Rust | foo.rs ↔ foo_test.rs |
tests/foo_test.rs |
— |
testlens vs vitest --coverage, jest, pytest-cov
Tienen finalidades distintas — usá los dos:
testlens es un smoke test ultra-rápido de existencia de archivos de test, pensado como gate de pre-commit. Para métricas funcionales de cobertura (líneas/branches ejercidas) corré Vitest, Jest, pytest-cov, etc. como step separado de CI. testlens corre en milisegundos; un coverage tool real toma segundos a minutos.
Por qué existe
Las herramientas tradicionales de cobertura te dicen qué líneas están testeadas. testlens te dice qué archivos no tienen ningún test — una verificación distinta y complementaria. Detecta módulos huérfanos temprano, cuando agregar el primer test es más barato. Combinalos para tener una vista completa.
Integraciones
Sirve con Husky, lefthook o GitHub Actions usando los snippets de la sección en inglés.
Códigos de salida
| Código | Significado |
|---|---|
0 |
Todos los archivos fuente tienen tests (o no se pasó --fail) |
1 |
Hay archivos sin test con --fail, o error de configuración |
License
MIT — see the main repository.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file open_harness_testlens-0.2.3-py3-none-win_amd64.whl.
File metadata
- Download URL: open_harness_testlens-0.2.3-py3-none-win_amd64.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f09995313added3704a6b507d7a680741cbe0a8380b2eb341e938821d38fb25
|
|
| MD5 |
766f1696e6d6e932c9f1f0ca66843480
|
|
| BLAKE2b-256 |
ad8528ac843d52d5da0ddf53e383c322adb8e139fbee95edd5a15451a985dfc9
|
File details
Details for the file open_harness_testlens-0.2.3-py3-none-manylinux2014_x86_64.whl.
File metadata
- Download URL: open_harness_testlens-0.2.3-py3-none-manylinux2014_x86_64.whl
- Upload date:
- Size: 840.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f089997d12df98056eeff9e5222ebb08397ee27d8d64057dc3c0c966ec197f9c
|
|
| MD5 |
7947d53475810eaa645759f54ef88f17
|
|
| BLAKE2b-256 |
4ccf38bfb1a186ed8ea57436a1465d76dc790b55831f4d78fee3168579b35ace
|
File details
Details for the file open_harness_testlens-0.2.3-py3-none-macosx_11_0_arm64.whl.
File metadata
- Download URL: open_harness_testlens-0.2.3-py3-none-macosx_11_0_arm64.whl
- Upload date:
- Size: 808.2 kB
- Tags: Python 3, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58d19a8745cbabb0962215de06c91a987f50033fcfc690ce4b75d3bd421691e5
|
|
| MD5 |
19efb5548b8fbfd7b1a5b7e01d649407
|
|
| BLAKE2b-256 |
b6e9e4e5bfb6e35697221d38f0766f962865ebda048bdefd25b10ad22c49a2bc
|
File details
Details for the file open_harness_testlens-0.2.3-py3-none-macosx_10_9_x86_64.whl.
File metadata
- Download URL: open_harness_testlens-0.2.3-py3-none-macosx_10_9_x86_64.whl
- Upload date:
- Size: 851.9 kB
- Tags: Python 3, macOS 10.9+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a861a1e3876060bd086a0792e4aa04482f0f65768eeb1327cae7ef83718d312f
|
|
| MD5 |
edf93f54f7069ebd90e6df62de425bfe
|
|
| BLAKE2b-256 |
5cfae396fa41a63f58ce27ed6b141d7b1e94897d0a96b94c9ca7704724bce657
|