CtrlK
BlogDocsLog inGet started
Tessl Logo

wagneripjr/farley-score

Score test quality with Dave Farley's 8 Properties of Good Tests: a weighted 0-10 Farley Index, tautology-theatre and mock anti-pattern detection, prioritised recommendations, and a Socratic coach

85

1.00x
Quality

90%

Does it follow best practices?

Impact

65%

1.00x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

farley-properties-and-scoring.mdskills/farley-score/references/

name:
farley-properties-and-scoring
description:
Dave Farley's 8 Properties of Good Tests, scoring rubrics, Farley Index formula, rating scale, and aggregation methodology

Farley Properties and Scoring

Implementation note: The Farley Index formula and sigmoid normalization are implemented in scripts/cli_calculator.py (see calculator.md). The agent delegates all math to this deterministic Python CLI calculator (JSON in, JSON out) to ensure reproducible scores. Available commands: normalize-property, blend-scores, compute-farley, get-rating, aggregate-file, aggregate-suite, full-pipeline.

The 8 Properties of Good Tests

Source: Dave Farley, "TDD & The Properties of Good Tests"

CodePropertyDefinitionPrimary Load
UUnderstandableTests describe what they are testing so we can understand the goals of our software, focusing on system behavior rather than implementation detailsDocumentation value
MMaintainableTests act as a defence of our system, breaking when we want them to, remaining easy to modify as the codebase evolvesLong-term value
RRepeatableTests always pass or fail in the same way for a given version of the software, producing consistent results regardless of when or where they runReliability trust
AAtomicTests are isolated and focus on a single outcome, operating independently with no side-effectsIsolation guarantee
NNecessaryAvoid creating tests for test sake; write tests that actively guide development decisionsValue justification
GGranularTests are small, simple and focused, and assert a single outcome, providing clear pass/fail with obvious problem indicatorsDiagnostic precision
FFastSince developers will end up with lots of them, tests need execution efficiencyFeedback speed
TFirstIn TDD, write the test before writing code, strengthening all other propertiesDesign quality

Property interdependence: First (TDD) naturally produces Understandable and Granular tests. Atomic tests are inherently more Repeatable. Granular tests are naturally Fast. Maintainable tests remain Necessary longer. Improvement in one property often cascades; degradation often signals related degradation.

Farley Index Formula

Farley Index = (U*1.5 + M*1.5 + R*1.25 + A*1.0 + N*1.0 + G*1.0 + F*0.75 + T*1.0) / 9.0

Each property score ranges from 0.0 to 10.0. The divisor 9.0 is the sum of all weights (1.5+1.5+1.25+1.0+1.0+1.0+0.75+1.0 = 9.0), producing a final index on the 0-10 scale.

Weight Rationale

PropertyWeightRationaleCross-Framework Support
U (Understandable)1.5xTests as documentation is their primary long-term valueBeck's Readable; Meszaros's "Tests as Documentation"
M (Maintainable)1.5xImplementation-coupled tests become a liability rather than assetBeck's Behavioral + Structure-insensitive; Meszaros's "Tests as Safety Net"
R (Repeatable)1.25xA single flaky test erodes team confidence in all testsBeck's Deterministic; Meszaros's Repeatable
A (Atomic)1.0xIsolation is fundamental but well-understood; most developers achieve baselineBeck's Isolated
N (Necessary)1.0xRedundant tests waste maintenance effort but are less harmful than other violationsFarley-specific
G (Granular)1.0xSingle-outcome focus aids debugging; has valid exceptions (logical assertion groups)Beck's Specific
F (Fast)0.75xSpeed is optimizable after the fact; slow but correct tests beat fast but poorly designedBeck's Fast
T (First/TDD)1.0xHardest to detect statically; often inferred from other propertiesMeszaros's "Tests as Specification"

The relative ordering (U,M > R > A,N,G,T > F) is supported by cross-framework consensus. The exact magnitudes are subject to calibration against practitioner ratings.

Per-Property Scoring Rubrics (0-10 Scale)

U -- Understandable

ScoreCriteria
9-10Tests read like specifications; behavior crystal clear without reading implementation; descriptive names, display annotations, nested organization
7-8Tests clear with minor ambiguities; intent mostly obvious from names
5-6Tests require some code inspection to understand purpose
3-4Tests cryptic; heavy reliance on implementation details to understand
1-2Names like test1/test2; no organization; magic numbers throughout

M -- Maintainable

ScoreCriteria
9-10Proper abstractions; changes to implementation rarely break tests; verifies behavior not implementation
7-8Good separation of concerns; occasional brittleness
5-6Some coupling to implementation; moderate refactoring pain; some over-specified mock interactions
3-4Tightly coupled to implementation; tests break with minor changes; verify with exact counts and ordering; tests assert on internal details via captured arguments
1-2Reflection to access private fields; tests mirror implementation structure exactly; tests describe HOW software works rather than WHAT it achieves

Tautology theatre guidance for LLM assessment: When evaluating Maintainable, the LLM must specifically check for:

  • Over-specified mock interactions: Tests using verify() with exact call counts (times(1)), call ordering (InOrder), or verifyNoMoreInteractions. Ask: "Would a behaviour-preserving refactoring break this test?" If yes, the test is over-specified.
  • Testing internal details via captured arguments: Tests that use ArgumentCaptor / .call_args to inspect internal state of objects passed to mocks (e.g., asserting on captor.getValue().getInternalStatus()).
  • White-box mock expectations: Mock expectations that mirror internal if/else branches -- e.g., verify(service).getPremiumDiscount() paired with verify(service, never()).getStandardDiscount().
  • High verify-to-assert ratio: Tests with many verify() calls but few assertEquals() calls test interactions (implementation) rather than outcomes (behaviour).
  • Note: Simple verify(mock).method() without exact counts is legitimate for verifying side effects. Only flag when verification over-constrains implementation.

R -- Repeatable

ScoreCriteria
9-10Completely deterministic; no external dependencies; same result every time, anywhere
7-8Rarely flaky; minimal environmental dependencies
5-6Occasional flakiness; some timing or state dependencies
3-4File system, timing, or environment dependencies present
1-2Thread.sleep, file I/O, network calls, system time, random without seed

A -- Atomic

ScoreCriteria
9-10Completely isolated; no shared state; parallelizable
7-8Mostly isolated; minor shared setup that doesn't cause ordering issues
5-6Some shared state; test order sometimes matters
3-4Heavy interdependencies; tests must run in specific order
1-2Shared mutable static state; explicit ordering annotations; tests verify other tests ran

N -- Necessary

ScoreCriteria
9-10Every test adds unique value; no redundancy; parameterized tests for variations
7-8Most tests valuable; minor redundancy
5-6Some tests feel like checkbox exercises; moderate redundancy
3-4Several redundant tests; framework testing; trivial assertions; some mock tautologies
1-2Many tests add no value; assertTrue(true); disabled tests accumulating; tests that only verify mock return values; tests with no production code exercised

Tautology theatre guidance for LLM assessment: When evaluating Necessary, the LLM must specifically check for all four types of tautology theatre -- tests whose outcome is predetermined, independent of production code:

  • Mock tautology: Tests that configure a mock's return value and then assert that the mock returns that same value, with no production code in between. Logically equivalent to x = 5; assert x == 5.
  • Mock-only test: Tests where every object is a mock and no real class is instantiated. Ask: "If I deleted all production code, would this test still pass?" If yes, the test has zero value.
  • Trivial tautology: Assertions that are always true regardless of any code: assertTrue(true), assertEquals(1, 1), assertNotNull(new Object()).
  • Framework test: Tests that verify language or framework behavior, not application code: assertNotNull(mock(Foo.class)), assertTrue("hello".contains("ell")).
  • General test: Any test whose outcome is predetermined by its own setup, independent of production code behaviour.

G -- Granular

ScoreCriteria
9-10Each test verifies a single outcome; failures pinpoint exact issues
7-8Tests focused; occasional multiple assertions forming a logical group for one outcome
5-6Tests cover multiple behaviors; failure diagnosis takes effort
3-4Tests sprawling; multiple unrelated assertions
1-2Mega-tests with 20+ assertions; testEverything() methods

Note: "single outcome" is distinct from "single assertion statement." Multiple assertions verifying one outcome (e.g., status code and body of an HTTP response) are acceptable. Detect multiple unrelated assertions, not logical groups.

F -- Fast

ScoreCriteria
9-10Pure computation; no I/O; millisecond execution
7-8Tests quick; minor optimization opportunities
5-6Some slow tests; suite takes noticeable time
3-4File I/O or database calls present
1-2Thread.sleep, network calls, heavy setup/teardown

T -- First (TDD Evidence)

ScoreCriteria
9-10Clear evidence of test-first approach; tests drive design; behavior-focused names
7-8Likely test-first; good design influence
5-6Unclear if test-first; tests may be afterthoughts
3-4Test structure mirrors implementation; likely test-after; mock-heavy tests with weak assertions
1-2Tests clearly written after code; follow implementation structure; coverage patches; tests with no production code exercised

Tautology theatre guidance for LLM assessment: When evaluating First (TDD), the LLM should note that:

  • Tests with no production code exercised (all mocks, no real SUT) could never have been written test-first, since there was nothing to drive the design of.
  • Mock-heavy tests that only verify interactions suggest the developer wrote the production code first and then wrote tests to confirm what the code already does ("test-after verification"), not to drive its design.
  • TDD-driven tests naturally focus on observable outputs because the test is written before the implementation details exist.

Two-Phase Assessment

Phase 1: Static Analysis (deterministic)

  • Parse test files, count signals per property (see signal-detection-patterns.md)
  • Compute raw metrics: assertion counts, sleep presence, reflection usage, naming patterns
  • Normalize metrics to per-property sub-scores using sigmoid normalization
  • Produce a "static floor" score that is deterministic and reproducible

Phase 2: LLM Assessment (controlled non-determinism)

  • Read tests holistically: does the test suite feel understandable?
  • Assess naming quality semantically (beyond pattern matching)
  • Detect tautology theatre: identify all four types (mock tautologies, mock-only tests, trivial tautologies, framework tests) plus over-specified mock interactions and tests coupled to implementation details rather than behaviour (see per-property "Tautology theatre guidance" sections above)
  • Evaluate TDD evidence from design patterns static analysis cannot detect
  • Adjust static scores up or down based on contextual judgment
  • Produce a "judgment delta" for each property

Blending

final_property_score = 0.60 * static_score + 0.40 * llm_score

Per property, then aggregated via the Farley Index formula. The 60/40 split prioritizes deterministic measurement while incorporating semantic judgment.

LLM Reproducibility Protocol

  • Evaluate all test files if under 50 files; SHA-256 deterministic selection (30%) for larger suites
  • Structured rubric: evaluate each property on the 0-10 rubric above, providing specific code evidence
  • Per-file evaluation is optional: when used, score each property per file and aggregate with the calculator's aggregate-suite (LOC-weighted); otherwise count signals across the whole analysed suite and run full-pipeline once
  • Record the model identifier in the report for reproducibility tracking

Sigmoid Normalization

Each property uses sigmoid normalization to map raw signal counts to the 0-10 scale:

raw_negative_density = negative_signal_count / total_test_methods
raw_positive_density = positive_signal_count / total_test_methods

negative_component = (1 - sigmoid(raw_negative_density, midpoint_neg, steepness_neg)) * 10.0
positive_component = sigmoid(raw_positive_density, midpoint_pos, steepness_pos) * 10.0

property_score = neg_weight * negative_component + pos_weight * positive_component

Base score when no signals detected: 5.0 (conservative -- no-signal yields "Fair" rating, not "Good").

Rating Scale

Farley IndexRatingInterpretation
9.0 - 10.0ExemplaryModel for the industry; tests serve as living documentation
7.5 - 8.9ExcellentHigh quality with minor improvement opportunities
6.0 - 7.4GoodSolid foundation with clear areas for improvement
4.5 - 5.9FairFunctional but needs significant attention to test design
3.0 - 4.4PoorTests provide limited value; major refactoring needed
0.0 - 2.9CriticalTests may be harmful; consider rewriting from scratch

Aggregation Levels

Scores are produced at three levels:

  1. Per-test-method: Individual test quality assessment
  2. Per-test-file: Aggregated across all test methods in a file (mean for positive signals, P90 for negative signals -- worst offenders must surface)
  3. Per-test-suite: Aggregated across all test files in scope (LOC-weighted mean across files)

Properties Not Captured

Beck's Test Desiderata includes properties outside the Farley framework that are noted in reports as "dimensions not measured":

PropertyWhy Not Measured
PredictiveRequires integration/E2E context, not assessable from test code alone
InspiringSubjective and emergent; depends on team context
ComposableRelevant for integration testing strategy, not individual test quality
WritableRequires effort measurement, not assessable from final test code

tile.json