Repository: huggingface/disaggregators (language: python)

Issue:
Add End-to-End test suite
I keep pushing broken code! Honestly, a set of simple end-to-end tests would fix that.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index f95e02f..7e38784 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,12 @@ ds = dataset.map(disaggregator)  # New boolean columns are added for she/her, he
 
 The resulting dataset can now be used for data exploration and disaggregated model evaluation.
 
+# Development
+
+Development requirements can be installed with `pip install .[dev]`. See the `Makefile` for useful targets, such as code quality and test running.
+
+To run tests locally across multiple Python versions (3.8, 3.9, and 3.10), ensure that you have all the Python versions available and then run `nox`. Note that this is quite slow, so it's only worth doing to double-check your code before you open a Pull Request.
+
 # Contact
 
 Nima Boscarino – `nima <at> huggingface <dot> co`
\ No newline at end of file
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 0000000..027ce21
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,10 @@
+import nox
+
+python_versions = ["3.8", "3.9", "3.10"]
+
+
+@nox.session(python=python_versions)
+def tests(session):
+    session.install(".[tests]")
+    session.run("spacy", "download", "en_core_web_lg")
+    session.run("make", "test")
diff --git a/pyproject.toml b/pyproject.toml
index dff6b32..594d658 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,4 +1,5 @@
 [tool.pytest.ini_options]
 markers = [
     "slow: marks tests as slow (deselect with '-m \"not slow\"')",
+    "integration: marks integration tests (deselect with '-m \"not integration\"')",
 ]
diff --git a/setup.py b/setup.py
index 10acc37..b7b7ea0 100644
--- a/setup.py
+++ b/setup.py
@@ -68,6 +68,8 @@
     "pytest-datadir",
     "pytest-xdist",
     "pytest-mock",
+    "nox",
+    "pandas",
 ]
 
 QUALITY_REQUIRE = ["black~=22.0", "flake8>=3.8.3", "isort>=5.0.0", "pyyaml>=5.3.1"]
@@ -103,11 +105,9 @@
         "License :: OSI Approved :: Apache Software License",
         "Operating System :: OS Independent",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
-        "Programming Language :: Python :: 3.11",
         "Topic :: Scientific/Engineering :: Artificial Intelligence",
     ],
     keywords="machine learning evaluate evaluation disaggregation",
diff --git a/src/disaggregators/disaggregation_modules/__init__.py b/src/disaggregators/disaggregation_modules/__init__.py
index 2290caf..292dcff 100644
--- a/src/disaggregators/disaggregation_modules/__init__.py
+++ b/src/disaggregators/disaggregation_modules/__init__.py
@@ -1,8 +1,9 @@
 from .age import Age
 from .disaggregation_module import DisaggregationModule, DisaggregationModuleFactory, DisaggregationModuleLabels
+from .gender import Gender
 from .pronoun import Pronoun
 
 
-AVAILABLE_MODULES = {"pronoun": Pronoun, "age": Age}
+AVAILABLE_MODULES = {"pronoun": Pronoun, "age": Age, "gender": Gender}
 
 __all__ = ["DisaggregationModule", "DisaggregationModuleFactory", "DisaggregationModuleLabels"]


Test patch:
diff --git a/conftest.py b/conftest.py
new file mode 100644
index 0000000..1e57ef4
--- /dev/null
+++ b/conftest.py
@@ -0,0 +1,18 @@
+import pytest
+import os
+
+
+def pytest_collection_modifyitems(items):
+    for item in items:
+        if "integration" in item.nodeid:
+            item.add_marker(pytest.mark.slow)
+            item.add_marker(pytest.mark.integration)
+
+
+def pytest_generate_tests(metafunc):
+    if metafunc.definition.name == "test_each_module":
+        metafunc.parametrize("module", [
+                x.name for x in os.scandir(
+                    metafunc.definition.config.rootdir / "src/disaggregators/disaggregation_modules"
+                ) if x.is_dir() and not x.name.startswith("__")
+        ])
diff --git a/tests/integration/test_disaggregation.py b/tests/integration/test_disaggregation.py
new file mode 100644
index 0000000..6950b85
--- /dev/null
+++ b/tests/integration/test_disaggregation.py
@@ -0,0 +1,48 @@
+import pandas as pd
+import pytest
+from datasets import Dataset
+
+from disaggregators import Disaggregator
+
+
+@pytest.fixture()
+def dataset():
+    return Dataset.from_dict({"text": ["Hello world!", "Fizz buzz."]})
+
+
+@pytest.fixture()
+def disaggregator():
+    return Disaggregator(["age", "gender"], column="text")
+
+
+@pytest.fixture()
+def expected_features():
+    return {
+        "age.child",
+        "age.youth",
+        "age.adult",
+        "age.senior",
+        "gender.male",
+        "gender.female",
+    }
+
+
+def test_datasets(dataset, disaggregator, expected_features):
+    ds_mapped = dataset.map(disaggregator)
+    assert expected_features.issubset(set(ds_mapped.features))
+
+
+def test_pandas(dataset, disaggregator, expected_features):
+    df = dataset.to_pandas()
+    new_cols = df.apply(disaggregator, axis=1)
+    df = pd.merge(df, pd.json_normalize(new_cols), left_index=True, right_index=True)
+    assert expected_features.issubset(set(df.columns))
+
+
+def test_each_module(dataset, module):
+    disaggregator = Disaggregator(module, column="text")
+    ds_mapped = dataset.map(disaggregator)
+
+    expected_features = disaggregator.fields
+
+    assert expected_features.issubset(set(ds_mapped.features))