Skip to content

Reorganize tests into a pytest package (and fix bugs it exposed)#165

Merged
derek73 merged 30 commits into
masterfrom
test-reorg-pytest
Jun 18, 2026
Merged

Reorganize tests into a pytest package (and fix bugs it exposed)#165
derek73 merged 30 commits into
masterfrom
test-reorg-pytest

Conversation

@derek73

@derek73 derek73 commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

Splits the single 97KB tests.py (344 tests in one file, unittest) into a tests/ pytest package — one file per concern — and runs everything under pytest. Along the way, the migration exposed and fixed two real defects.

Test reorganization

  • 13 test classes → 13 tests/test_*.py files (one per concern); tests/base.py holds a plain HumanNameTestBase with the m() assert helper + thin assert* shims, so test bodies moved over verbatim.
  • tests/conftest.py reproduces the old dual run (every test under empty_attribute_default = '' and None) as a clean parametrized autouse fixture — reported counts are doubled (692 results from 346 methods).
  • @unittest.expectedFailure@pytest.mark.xfail (10 methods), @unittest.skipUnless(dill,…)@pytest.mark.skipif, self.assertRaisespytest.raises.
  • ConstantsCustomizationConstantsCustomizationTests so it matches python_classes = ["*Tests","*TestCase"].
  • Debug CLI moved out of tests.py into nameparser/__main__.py: python -m nameparser "Some Name".
  • Docs + CI (AGENTS.md, CONTRIBUTING.md, GitHub Actions, MANIFEST.in) updated to pytest.

Bug fixes the migration surfaced

  • initials() rendered the literal "None" for empty name parts when empty_attribute_default=None (e.g. "J. None D."). Now empty parts render as '' and a fully-empty result falls back to empty_attribute_default, consistent with the first/title accessors. (Behavior change for empty_attribute_default=None users.) Pinned by dedicated regression tests in tests/test_initials.py (they fail against the old parser.py).
  • Test-isolation leak: several tests mutate global CONSTANTS without restoring it; the old suite only passed because unittest runs methods alphabetically. pytest runs in definition order, which exposed it. The fixture now snapshots/restores both the scalar and collection config per test (including the global SetManager/TupleManager constants and the lazy _pst cache), so even a test that mutates a global collection and fails before its own cleanup can't leak — tests are order-independent.

Review follow-ups

  • Extended the conftest snapshot to cover collection constants (above), removing the last manual, non-exception-safe cleanup.
  • Added explicit regression tests pinning the initials()/None fix.
  • nameparser/__main__.py: dropped the inert encoding=sys.stdout.encoding argument and switched the Initials: line to comma-style printing (no TypeError when initials() returns None).
  • Enabled xfail_strict so an xfail that starts passing fails the suite instead of silently becoming an xpass.
  • Fixed the stale python -m nameparser repr example in CONTRIBUTING.md (lowercase field labels + nickname).

Test Plan

  • pytest672 passed, 20 xfailed, zero failures
  • ruff check clean; mypy clean
  • Order-independence verified (reversed file order, randomized orderings)
  • sphinx-build and sdist packaging unaffected
  • python -m nameparser "Dr. Juan Q. Xavier de la Vega III" parses correctly

🤖 Generated with Claude Code

derek73 and others added 30 commits June 14, 2026 11:35
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When empty_attribute_default=None, initials() interpolated the literal
string 'None' for missing name parts (e.g. 'J. None D.' for 'John Doe').
Render empty parts as '' so str.format produces clean output, and fall
back to empty_attribute_default only for a fully-empty result, matching
the other attribute accessors (e.g. first/title). Exposed by running the
test suite with empty_attribute_default=None under isolated state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Several tests mutate scalar config on the global CONSTANTS singleton
(capitalize_name, force_mixed_case_capitalization, string_format, etc.)
without restoring it. The original suite only survived because unittest
runs methods alphabetically, so a sibling test happened to reset the
value; pytest runs in definition order, leaking state across files. The
autouse fixture now snapshots and restores these scalars around every
test, making tests order-independent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Revert an injected conditional pytest.xfail() in test_initials_simple_name
back to the verbatim original (the underlying initials() bug is now fixed).
Also remove test_custom_regex_constant, a test that was fabricated during
migration and added to both tests.py and test_constants.py; it is not part
of the original suite. tests.py restored to its original 344 methods.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The autouse fixture only snapshotted scalar config attrs, so tests that
mutate a global collection (e.g. CONSTANTS.titles) relied on manual,
non-exception-safe cleanup. If such a test failed before its cleanup ran,
the mutation leaked into later tests — reintroducing the order-dependence
the fixture was meant to eliminate.

Snapshot and restore the collection managers (SetManager / TupleManager /
RegexTupleManager) too, via a small type-aware clone (deepcopy can't
round-trip RegexTupleManager's compiled patterns), and reset the lazy
_pst cache so it recomputes from the restored collections. The manual
cleanup in test_can_change_global_constants is now redundant and removed.

Verified order-independence across randomized seeds and via an injected
test that mutates a global collection then fails before cleanup: the
following test still sees pristine config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add explicit regression tests for the bug this reorg fixed: with
empty_attribute_default=None, initials() used to interpolate the literal
"None" for empty name parts ("John Doe" -> "J. None D.") and for a fully
-empty name ("None None None"). The tests assert empty parts render as ''
and a fully-empty result falls back to empty_attribute_default, matching
the first/last accessors.

Both fail against master's parser.py and pass on this branch, so they
pin the fix rather than relying on the parametrized fixture happening to
exercise a pre-existing test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
encoding=sys.stdout.encoding was never consumed (input comes from argv as
str, and encoding only affects bytes input), and sys.stdout.encoding can
be None under redirection — misleading dead config. Also switch the
Initials line from string concatenation to a comma so it prints harmlessly
when initials() returns empty_attribute_default (which may be None) instead
of raising TypeError.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CONTRIBUTING.md `python -m nameparser` example showed capitalized
field labels (Title:/First:/…) and omitted the nickname field; the actual
repr uses lowercase labels and includes nickname. Update it to match.

Also set xfail_strict in pytest config so an xfail test that starts
passing fails the suite instead of silently becoming an xpass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ials fix

The usage.rst initials doctests had unterminated strings, wrong expected
output (Python list repr, leaked global initials_format), and a missing
blank line after a doctest directive. Fix them to pass under the doctest
builder and reset CONSTANTS.initials_format so they don't leak state.
Add an Unreleased release_log entry for the initials() None fix, the
python -m nameparser CLI, and the pytest test reorg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The implementation plan was a working artifact for this PR and isn't part
of the published docs or package; drop it before merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@derek73 derek73 merged commit 08635e0 into master Jun 18, 2026
8 checks passed
@derek73 derek73 deleted the test-reorg-pytest branch June 18, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant