152 lines
No EOL
5.5 KiB
Python
152 lines
No EOL
5.5 KiB
Python
"""Tests for keyword extraction and coverage."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from matching.keywords import extract_keywords, coverage
|
|
|
|
|
|
class TestExtractKeywords:
|
|
def test_basic_extraction(self):
|
|
text = "Python developer with Fast API experience and PostgreSQL database skills."
|
|
kws = extract_keywords(text)
|
|
assert "python" in kws
|
|
assert "fastapi" in kws
|
|
assert "postgresql" in kws
|
|
|
|
def test_stopwords_removed(self):
|
|
text = "We are looking for a developer with experience in Python."
|
|
kws = extract_keywords(text)
|
|
assert "we" not in kws
|
|
assert "are" not in kws
|
|
assert "for" not in kws
|
|
assert "a" not in kws
|
|
assert "in" not in kws
|
|
assert "python" in kws
|
|
assert "developer" in kws
|
|
|
|
def test_swedish_stopwords_removed(self):
|
|
text = "Vi letar efter en Python utvecklare med erfarenhet av Docker."
|
|
kws = extract_keywords(text)
|
|
assert "vi" not in kws
|
|
assert "en" not in kws
|
|
assert "av" not in kws
|
|
assert "python" in kws
|
|
assert "docker" in kws
|
|
|
|
def test_top_n_limit(self):
|
|
text = "python python python docker docker docker kubernetes kubernetes kubernetes react react react"
|
|
kws = extract_keywords(text, top_n=2)
|
|
assert len(kws) == 2
|
|
|
|
def test_empty_text(self):
|
|
assert extract_keywords("") == []
|
|
|
|
def test_whitespace_only(self):
|
|
assert extract_keywords(" ") == []
|
|
|
|
def test_multiword_fastapi(self):
|
|
text = "Experience with fast api framework for building REST APIs."
|
|
kws = extract_keywords(text)
|
|
assert "fastapi" in kws
|
|
|
|
def test_multiword_machine_learning(self):
|
|
text = "machine learning models for predictive analytics."
|
|
kws = extract_keywords(text)
|
|
assert "machine-learning" in kws
|
|
|
|
def test_frequency_ordering(self):
|
|
text = "python python python docker docker kubernetes"
|
|
kws = extract_keywords(text, top_n=3)
|
|
assert kws[0] == "python"
|
|
assert kws[1] == "docker"
|
|
assert kws[2] == "kubernetes"
|
|
|
|
def test_deterministic_tie_breaking(self):
|
|
"""Ties in frequency should be broken alphabetically."""
|
|
text = "docker kubernetes"
|
|
kws = extract_keywords(text)
|
|
# Both have frequency 1, so alphabetical: docker < kubernetes
|
|
assert kws[0] == "docker"
|
|
assert kws[1] == "kubernetes"
|
|
|
|
def test_min_token_length(self):
|
|
text = "x y z aa bb cc developer"
|
|
kws = extract_keywords(text)
|
|
assert "x" not in kws
|
|
assert "y" not in kws
|
|
assert "z" not in kws
|
|
assert "developer" in kws
|
|
|
|
def test_tech_terms_preserved(self):
|
|
text = "Node.js and React Native for mobile development."
|
|
kws = extract_keywords(text)
|
|
assert "nodejs" in kws
|
|
assert "react-native" in kws
|
|
|
|
|
|
class TestCoverage:
|
|
def test_full_coverage(self):
|
|
cv = "Python developer with Fast API PostgreSQL Docker AWS Kubernetes"
|
|
posting = "Python developer with Fast API PostgreSQL Docker AWS Kubernetes"
|
|
result = coverage(cv, posting)
|
|
assert result["ratio"] == 1.0
|
|
assert len(result["missing"]) == 0
|
|
|
|
def test_partial_coverage(self):
|
|
cv = "Python developer with PostgreSQL and Docker experience"
|
|
posting = "Python developer with Fast API PostgreSQL Docker AWS Kubernetes React"
|
|
result = coverage(cv, posting)
|
|
assert 0.0 < result["ratio"] < 1.0
|
|
assert "python" in result["matched"]
|
|
assert "postgresql" in result["matched"]
|
|
assert "docker" in result["matched"]
|
|
assert "fastapi" in result["missing"]
|
|
assert "kubernetes" in result["missing"]
|
|
|
|
def test_zero_coverage(self):
|
|
cv = "Chef with experience in French cuisine and menu planning"
|
|
posting = "Python developer with Fast API PostgreSQL Docker"
|
|
result = coverage(cv, posting)
|
|
assert result["ratio"] == 0.0
|
|
assert len(result["matched"]) == 0
|
|
assert len(result["missing"]) > 0
|
|
|
|
def test_empty_posting(self):
|
|
result = coverage("Python developer", "")
|
|
assert result == {"matched": [], "missing": [], "ratio": 0.0}
|
|
|
|
def test_empty_cv(self):
|
|
result = coverage("", "Python developer with Docker")
|
|
assert result["ratio"] == 0.0
|
|
assert len(result["matched"]) == 0
|
|
assert len(result["missing"]) > 0
|
|
|
|
def test_both_empty(self):
|
|
result = coverage("", "")
|
|
assert result == {"matched": [], "missing": [], "ratio": 0.0}
|
|
|
|
def test_ratio_calculation(self):
|
|
cv = "python docker postgresql"
|
|
posting = "python docker postgresql kubernetes"
|
|
result = coverage(cv, posting)
|
|
# 3 of 4 matched (approx, depends on stopword filtering).
|
|
assert result["ratio"] > 0.5
|
|
assert result["ratio"] <= 1.0
|
|
|
|
def test_matched_and_missing_lists(self):
|
|
cv = "python docker"
|
|
posting = "python docker kubernetes react"
|
|
result = coverage(cv, posting)
|
|
assert "python" in result["matched"]
|
|
assert "docker" in result["matched"]
|
|
assert "kubernetes" in result["missing"]
|
|
assert "react" in result["missing"]
|
|
|
|
def test_coverage_returns_dict_keys(self):
|
|
result = coverage("python", "python docker")
|
|
assert "matched" in result
|
|
assert "missing" in result
|
|
assert "ratio" in result
|
|
assert isinstance(result["matched"], list)
|
|
assert isinstance(result["missing"], list)
|
|
assert isinstance(result["ratio"], float) |