<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.dura-lex.org/index.php?action=history&amp;feed=atom&amp;title=Development%2FTesting</id>
	<title>Development/Testing - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.dura-lex.org/index.php?action=history&amp;feed=atom&amp;title=Development%2FTesting"/>
	<link rel="alternate" type="text/html" href="https://wiki.dura-lex.org/index.php?title=Development/Testing&amp;action=history"/>
	<updated>2026-04-23T05:44:27Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.45.3</generator>
	<entry>
		<id>https://wiki.dura-lex.org/index.php?title=Development/Testing&amp;diff=37&amp;oldid=prev</id>
		<title>Nicolas: Create Testing conventions page from coding-conventions/TESTING.md (via create-page on MediaWiki MCP Server)</title>
		<link rel="alternate" type="text/html" href="https://wiki.dura-lex.org/index.php?title=Development/Testing&amp;diff=37&amp;oldid=prev"/>
		<updated>2026-04-23T02:04:53Z</updated>

		<summary type="html">&lt;p&gt;Create Testing conventions page from coding-conventions/TESTING.md (via create-page on MediaWiki MCP Server)&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= Testing Conventions =&lt;br /&gt;
&lt;br /&gt;
All duralex-* packages follow these conventions for test organization, naming, and execution.&lt;br /&gt;
&lt;br /&gt;
== Directory structure ==&lt;br /&gt;
&lt;br /&gt;
Tests mirror the &amp;lt;code&amp;gt;src/duralex/&amp;lt;/code&amp;gt; structure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tests/&lt;br /&gt;
├── conftest.py                      # shared fixtures for this repo&lt;br /&gt;
├── data/                            # mirrors src/duralex/data/&lt;br /&gt;
│   ├── test_models.py&lt;br /&gt;
│   └── test_schema.py&lt;br /&gt;
├── temporal/                        # mirrors src/duralex/temporal/&lt;br /&gt;
│   └── test_temporal_version.py&lt;br /&gt;
├── store/                           # mirrors src/duralex/corpus/store/&lt;br /&gt;
│   ├── test_full_text_search_configuration.py&lt;br /&gt;
│   └── test_full_text_search_query_builder.py&lt;br /&gt;
├── integration/                     # tests requiring external services&lt;br /&gt;
│   └── test_postgres_document_store.py&lt;br /&gt;
└── fixtures/                        # real data files for tests&lt;br /&gt;
    ├── fr.legiarti000006436298.xml&lt;br /&gt;
    ├── fr.cetatext000046783006.xml&lt;br /&gt;
    └── 5fdcdddd994f0448aad44c0b.json&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
No &amp;lt;code&amp;gt;__init__.py&amp;lt;/code&amp;gt; in test directories. Use &amp;lt;code&amp;gt;--import-mode=importlib&amp;lt;/code&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
== Test naming ==&lt;br /&gt;
&lt;br /&gt;
Pattern: &amp;lt;code&amp;gt;test_{what}_{scenario}&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
# What is being tested + specific scenario&lt;br /&gt;
def test_resolve_legal_reference_article_of_code():&lt;br /&gt;
def test_resolve_legal_reference_empty_string():&lt;br /&gt;
def test_resolve_legal_reference_unknown_pattern():&lt;br /&gt;
def test_resolve_legal_reference_pourvoi_number():&lt;br /&gt;
&lt;br /&gt;
def test_parse_legislation_article_basic_fields():&lt;br /&gt;
def test_parse_legislation_article_missing_content():&lt;br /&gt;
def test_parse_legislation_article_multiple_versions():&lt;br /&gt;
&lt;br /&gt;
def test_sanitize_html_content_strips_script_tags():&lt;br /&gt;
def test_sanitize_html_content_preserves_table_structure():&lt;br /&gt;
def test_sanitize_html_content_empty_input():&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The test name alone must tell an auditor what is being verified, without reading the body.&lt;br /&gt;
&lt;br /&gt;
== Test body structure ==&lt;br /&gt;
&lt;br /&gt;
Each test follows a clear three-section structure. Sections are separated by blank lines, not comments.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
def test_resolve_legal_reference_article_of_code():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&amp;#039;article 1240 du code civil&amp;#039; resolves to fr.law.code.civil.article-1240.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    resolver = FrenchLegalReferenceResolver()&lt;br /&gt;
&lt;br /&gt;
    results = resolver.resolve_legal_reference(&amp;quot;article 1240 du code civil&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    assert len(results) == 1&lt;br /&gt;
    assert results[0].uri == &amp;quot;fr.law.code.civil.article-1240&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Line 1:&amp;#039;&amp;#039;&amp;#039; Setup (create objects, prepare inputs)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Line 2:&amp;#039;&amp;#039;&amp;#039; Act (call the function under test)&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Line 3:&amp;#039;&amp;#039;&amp;#039; Assert (verify the result)&lt;br /&gt;
&lt;br /&gt;
One assertion per concept. Multiple &amp;lt;code&amp;gt;assert&amp;lt;/code&amp;gt; statements are fine when they verify different aspects of the same result.&lt;br /&gt;
&lt;br /&gt;
== Fixtures ==&lt;br /&gt;
&lt;br /&gt;
=== Factory fixtures for data objects ===&lt;br /&gt;
&lt;br /&gt;
When tests need multiple instances with variations, use factory fixtures:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
@pytest.fixture&lt;br /&gt;
def make_legislation_article():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Factory: creates LegislationArticle instances with sensible defaults.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    def _make(&lt;br /&gt;
        article_id: str = &amp;quot;fr.legiarti000006436298&amp;quot;,&lt;br /&gt;
        article_number: str = &amp;quot;1240&amp;quot;,&lt;br /&gt;
        code_name: str = &amp;quot;code civil&amp;quot;,&lt;br /&gt;
        is_in_force: bool = True,&lt;br /&gt;
        **overrides,&lt;br /&gt;
    ) -&amp;gt; LegislationArticle:&lt;br /&gt;
        defaults = {&lt;br /&gt;
            &amp;quot;article_id&amp;quot;: article_id,&lt;br /&gt;
            &amp;quot;article_number&amp;quot;: article_number,&lt;br /&gt;
            &amp;quot;code_name&amp;quot;: code_name,&lt;br /&gt;
            &amp;quot;is_in_force&amp;quot;: is_in_force,&lt;br /&gt;
            &amp;quot;html_content&amp;quot;: &amp;quot;&amp;lt;p&amp;gt;Tout fait quelconque de l&amp;#039;homme...&amp;lt;/p&amp;gt;&amp;quot;,&lt;br /&gt;
            &amp;quot;plain_text_content&amp;quot;: &amp;quot;Tout fait quelconque de l&amp;#039;homme...&amp;quot;,&lt;br /&gt;
        }&lt;br /&gt;
        defaults.update(overrides)&lt;br /&gt;
        return LegislationArticle(**defaults)&lt;br /&gt;
    return _make&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Real data fixtures ===&lt;br /&gt;
&lt;br /&gt;
Test fixtures use &amp;#039;&amp;#039;&amp;#039;real files&amp;#039;&amp;#039;&amp;#039; from public institutional data sources (DILA, Judilibre, EUR-Lex). Not invented mocks. This is consistent with the project&amp;#039;s commitment to transparency and auditability.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
FIXTURES_DIR = Path(__file__).parent / &amp;quot;fixtures&amp;quot;&lt;br /&gt;
&lt;br /&gt;
@pytest.fixture&lt;br /&gt;
def sample_legi_article_path() -&amp;gt; Path:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Real LEGI XML article: article 1240 du code civil.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return FIXTURES_DIR / &amp;quot;fr.legiarti000006436298.xml&amp;quot;&lt;br /&gt;
&lt;br /&gt;
@pytest.fixture&lt;br /&gt;
def sample_jade_decision_path() -&amp;gt; Path:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Real JADE XML decision: CE, 13 dec 2022, n 462274.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    return FIXTURES_DIR / &amp;quot;fr.cetatext000046783006.xml&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Session-scoped fixtures for expensive resources ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
@pytest.fixture(scope=&amp;quot;session&amp;quot;)&lt;br /&gt;
def database_connection():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;PostgreSQL connection via testcontainers. Shared across all tests in session.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    with PostgresContainer(&amp;quot;postgres:17&amp;quot;) as postgres:&lt;br /&gt;
        pool = ConnectionPool(postgres.get_connection_url())&lt;br /&gt;
        yield pool&lt;br /&gt;
        pool.close()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Markers ==&lt;br /&gt;
&lt;br /&gt;
Two standard markers, defined in every repo&amp;#039;s &amp;lt;code&amp;gt;pyproject.toml&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
@pytest.mark.slow&lt;br /&gt;
def test_parse_all_code_civil_articles():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Parse all 2000+ articles of Code civil. Verifies no crash.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
@pytest.mark.integration&lt;br /&gt;
def test_full_text_search_returns_ranked_results(database_connection):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;FTS with real PostgreSQL, real French stemming, real data.&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Default &amp;lt;code&amp;gt;pytest&amp;lt;/code&amp;gt; invocation excludes both:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
pytest                         # unit tests only (fast)&lt;br /&gt;
pytest -m integration          # integration only&lt;br /&gt;
pytest -m &amp;quot;not slow&amp;quot;           # everything except slow&lt;br /&gt;
pytest -m &amp;quot;&amp;quot;                   # everything including slow + integration&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Parametrize for data-driven tests ==&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;@pytest.mark.parametrize&amp;lt;/code&amp;gt; for testing multiple inputs against the same logic:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&lt;br /&gt;
@pytest.mark.parametrize(&amp;quot;raw_input, expected_uri&amp;quot;, [&lt;br /&gt;
    (&amp;quot;article 1240 du code civil&amp;quot;, &amp;quot;fr.law.code.civil.article-1240&amp;quot;),&lt;br /&gt;
    (&amp;quot;article L. 442-1 du code de commerce&amp;quot;, &amp;quot;fr.law.code.commerce.article-l442-1&amp;quot;),&lt;br /&gt;
    (&amp;quot;loi n° 85-677&amp;quot;, &amp;quot;fr.law.loi.85-677&amp;quot;),&lt;br /&gt;
    (&amp;quot;24-14.340&amp;quot;, &amp;quot;fr.court.pourvoi.24-14.340&amp;quot;),&lt;br /&gt;
])&lt;br /&gt;
def test_resolve_legal_reference_known_patterns(raw_input, expected_uri):&lt;br /&gt;
    resolver = FrenchLegalReferenceResolver()&lt;br /&gt;
    results = resolver.resolve_legal_reference(raw_input)&lt;br /&gt;
    assert results[0].uri == expected_uri&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== What NOT to test ==&lt;br /&gt;
&lt;br /&gt;
* Private implementation details (functions starting with &amp;lt;code&amp;gt;_&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Third-party library behavior (lxml, psycopg, nh3)&lt;br /&gt;
* Configuration values&lt;br /&gt;
&lt;br /&gt;
== pyproject.toml reference ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;toml&amp;quot;&amp;gt;&lt;br /&gt;
[tool.pytest.ini_options]&lt;br /&gt;
testpaths = [&amp;quot;tests&amp;quot;]&lt;br /&gt;
addopts = [&lt;br /&gt;
    &amp;quot;--strict-config&amp;quot;,&lt;br /&gt;
    &amp;quot;--strict-markers&amp;quot;,&lt;br /&gt;
    &amp;quot;--import-mode=importlib&amp;quot;,&lt;br /&gt;
    &amp;quot;-m not slow and not integration&amp;quot;,&lt;br /&gt;
]&lt;br /&gt;
xfail_strict = true&lt;br /&gt;
consider_namespace_packages = true&lt;br /&gt;
filterwarnings = [&amp;quot;error&amp;quot;]&lt;br /&gt;
markers = [&lt;br /&gt;
    &amp;quot;slow: tests with extended runtime&amp;quot;,&lt;br /&gt;
    &amp;quot;integration: requires PostgreSQL (testcontainers)&amp;quot;,&lt;br /&gt;
]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Development]]&lt;/div&gt;</summary>
		<author><name>Nicolas</name></author>
	</entry>
</feed>