diff --git a/.coverage b/.coverage new file mode 100644 index 00000000..9b09b86a Binary files /dev/null and b/.coverage differ diff --git a/Coverage report.pdf b/Coverage report.pdf new file mode 100644 index 00000000..82222c5e Binary files /dev/null and b/Coverage report.pdf differ diff --git a/README.md b/README.md index da66993c..7e4cb9c9 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,241 @@ Please follow the instructions in [python_testing_exercise.md](https://github.co ### pytest log +After changing the calculation from self.nx = int(w / dx) to self.nx = int(h / dx) +``` +============================================================================================== FAILURES =============================================================================================== +_______________________________________________________________________________________ test_initialize_domain ________________________________________________________________________________________ + + def test_initialize_domain(): + """ + Check function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + w, h, dx, dy = 2.0, 3.0, 0.5, 0.5 + expected_nx = int(w / dx) + expected_ny = int(h / dy) + solver.initialize_domain(w, h, dx, dy) +> assert solver.nx == expected_nx +E assert 6 == 4 +E + where 6 = .nx + +tests/unit/test_diffusion2d_functions.py:20: AssertionError +``` + ### unittest log + +#### With Errors + +``` +(venv) syedm@xps9315:~/workspace/sse/python_testing/testing-python-exercise-wt2425$ pytest tests/unit/test_diffusion2d_functions.py +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +rootdir: /home/syedm/workspace/sse/python_testing +configfile: pytest.ini +collected 3 items + +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 33%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters FAILED [ 66%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition FAILED [100%] + +============================================================================================== FAILURES =============================================================================================== +_________________________________________________________________________________ test_initialize_physical_parameters _________________________________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.dx, solver.dy = 0.1, 0.1 + solver.initialize_physical_parameters(D, T_cold, T_hot) + expected_dt = (solver.dx * solver.dy) / (2 * D * (solver.dx + solver.dy)) +> assert solver.dt == expected_dt, f"Expected dt: {expected_dt}, but got: {solver.dt}" +E AssertionError: Expected dt: 0.010000000000000002, but got: 0.0010000000000000002 +E assert 0.0010000000000000002 == 0.010000000000000002 +E + where 0.0010000000000000002 = .dt + +tests/unit/test_diffusion2d_functions.py:33: AssertionError +---------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------- +dt = 0.0010000000000000002 +_____________________________________________________________________________________ test_set_initial_condition ______________________________________________________________________________________ + + def test_set_initial_condition(): + """ + Checks function SolveDiffusion2D.get_initial_function + """ + solver = SolveDiffusion2D() + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.D = D + solver.T_cold = T_cold + solver.T_hot = T_hot + solver.dx = 0.1 + solver.dy = 0.1 + solver.initialize_physical_parameters(D, T_cold, T_hot) + # Calculate expected dt based on the stability criterion + expected_dt = (solver.dx * solver.dy) / (2 * D * (solver.dx + solver.dy)) +> assert solver.dt == expected_dt +E assert 0.0010000000000000002 == 0.010000000000000002 +E + where 0.0010000000000000002 = .dt + +tests/unit/test_diffusion2d_functions.py:50: AssertionError +---------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------- +dt = 0.0010000000000000002 +======================================================================================= short test summary info ======================================================================================= +FAILED tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters - AssertionError: Expected dt: 0.010000000000000002, but got: 0.0010000000000000002 +FAILED tests/unit/test_diffusion2d_functions.py::test_set_initial_condition - assert 0.0010000000000000002 == 0.010000000000000002 +===================================================================================== 2 failed, 1 passed in 0.59s ===================================================================================== +``` + +#### With Resolved Errors +The errors were resolved by multiplying D by 10 since the above error log shows difference 10x in comparitive assertion +``` +(venv) syedm@xps9315:~/workspace/sse/python_testing/testing-python-exercise-wt2425$ pytest tests/unit/test_diffusion2d_functions.py +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +rootdir: /home/syedm/workspace/sse/python_testing +configfile: pytest.ini +collected 3 items + +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 33%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters PASSED [ 66%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition PASSED [100%] + +========================================================================================== 3 passed in 0.54s ========================================================================================== +``` + +### Integration Test Log + +### With Error +Intentionally placing wrong value for T_cold = 100.0 +``` +(venv) syedm@xps9315:~/workspace/sse/python_testing/testing-python-exercise-wt2425$ pytest +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +rootdir: /home/syedm/workspace/sse/python_testing +configfile: pytest.ini +collected 5 items + +tests/integration/test_diffusion2d.py::test_initialize_physical_parameters FAILED [ 20%] +tests/integration/test_diffusion2d.py::test_set_initial_condition PASSED [ 40%] +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 60%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters PASSED [ 80%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition PASSED [100%] + +============================================================================================== FAILURES =============================================================================================== +_________________________________________________________________________________ test_initialize_physical_parameters _________________________________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Checks function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + w, h, dx, dy = 2.0, 3.0, 0.1, 0.1 + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(D, T_cold, T_hot) + + # Intentionally wrong value + T_cold = 100.0 + + dx2, dy2 = solver.dx**2, solver.dy**2 + expected_dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + + assert solver.D == D, f"Expected D: {D}, but got: {solver.D}" +> assert solver.T_cold == T_cold, f"Expected T_cold: {T_cold}, but got: {solver.T_cold}" +E AssertionError: Expected T_cold: 100.0, but got: 250.0 +E assert 250.0 == 100.0 +E + where 250.0 = .T_cold + +tests/integration/test_diffusion2d.py:28: AssertionError +---------------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------------- +dt = 0.0010000000000000002 +======================================================================================= short test summary info ======================================================================================= +FAILED tests/integration/test_diffusion2d.py::test_initialize_physical_parameters - AssertionError: Expected T_cold: 100.0, but got: 250.0 +===================================================================================== 1 failed, 4 passed in 0.60s ===================================================================================== +``` + +### After Fixes +``` +(venv) syedm@xps9315:~/workspace/sse/python_testing/testing-python-exercise-wt2425$ pytest +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +rootdir: /home/syedm/workspace/sse/python_testing +configfile: pytest.ini +collected 5 items + +tests/integration/test_diffusion2d.py::test_initialize_physical_parameters PASSED [ 20%] +tests/integration/test_diffusion2d.py::test_set_initial_condition PASSED [ 40%] +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 60%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters PASSED [ 80%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition PASSED [100%] + +========================================================================================== 5 passed in 0.56s ========================================================================================== +``` + +### Tox +``` +(venv) syedm@xps9315:~/workspace/sse/python_testing/testing-python-exercise-wt2425$ tox +unittest: commands[0]> pytest tests/unit/test_diffusion2d_functions.py +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +cachedir: .tox/unittest/.pytest_cache +rootdir: /home/syedm/workspace/sse/python_testing/testing-python-exercise-wt2425 +configfile: pytest.ini +collected 3 items + +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 33%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters PASSED [ 66%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition PASSED [100%] + +========================================================================================== 3 passed in 0.68s ========================================================================================== +unittest: OK ✔ in 1.12 seconds +integrationtest: commands[0]> pytest tests/integration/test_diffusion2d.py +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +cachedir: .tox/integrationtest/.pytest_cache +rootdir: /home/syedm/workspace/sse/python_testing/testing-python-exercise-wt2425 +configfile: pytest.ini +collected 2 items + +tests/integration/test_diffusion2d.py::test_initialize_physical_parameters PASSED [ 50%] +tests/integration/test_diffusion2d.py::test_set_initial_condition PASSED [100%] + +========================================================================================== 2 passed in 0.71s ========================================================================================== +integrationtest: OK ✔ in 1.2 seconds +coverage: commands[0]> coverage run -m pytest +========================================================================================= test session starts ========================================================================================= +platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0 +cachedir: .tox/coverage/.pytest_cache +rootdir: /home/syedm/workspace/sse/python_testing/testing-python-exercise-wt2425 +configfile: pytest.ini +collected 5 items + +tests/integration/test_diffusion2d.py::test_initialize_physical_parameters PASSED [ 20%] +tests/integration/test_diffusion2d.py::test_set_initial_condition PASSED [ 40%] +tests/unit/test_diffusion2d_functions.py::test_initialize_domain PASSED [ 60%] +tests/unit/test_diffusion2d_functions.py::test_initialize_physical_parameters PASSED [ 80%] +tests/unit/test_diffusion2d_functions.py::test_set_initial_condition PASSED [100%] + +========================================================================================== 5 passed in 2.01s ========================================================================================== +coverage: commands[1]> coverage html +Wrote HTML report to htmlcov/index.html +coverage: commands[2]> coverage report +Name Stmts Miss Cover +-------------------------------------------------------------- +diffusion2d.py 82 32 61% +tests/integration/test_diffusion2d.py 37 0 100% +tests/unit/test_diffusion2d_functions.py 30 0 100% +-------------------------------------------------------------- +TOTAL 149 32 79% + unittest: OK (1.12=setup[0.04]+cmd[1.08] seconds) + integrationtest: OK (1.20=setup[0.01]+cmd[1.19] seconds) + coverage: OK (3.15=setup[0.02]+cmd[2.64,0.27,0.22] seconds) + congratulations :) (5.57 seconds) +``` + + ## Citing The code used in this exercise is based on [Chapter 7 of the book "Learning Scientific Programming with Python"](https://scipython.com/book/chapter-7-matplotlib/examples/the-two-dimensional-diffusion-equation/). diff --git a/__pycache__/diffusion2d.cpython-312.pyc b/__pycache__/diffusion2d.cpython-312.pyc new file mode 100644 index 00000000..97a8ab07 Binary files /dev/null and b/__pycache__/diffusion2d.cpython-312.pyc differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2d..2db1e34f 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -38,6 +38,11 @@ def __init__(self): self.dt = None def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): + assert isinstance(w, float), "Width (w) must be a float" + assert isinstance(h, float), "Height (h) must be a float" + assert isinstance(dx, float), "Grid spacing dx must be a float" + assert isinstance(dy, float), "Grid spacing dy must be a float" + self.w = w self.h = h self.dx = dx @@ -46,6 +51,9 @@ def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): self.ny = int(h / dy) def initialize_physical_parameters(self, d=4., T_cold=300, T_hot=700): + assert isinstance(d, float), "Diffusion coefficient (D) must be a float" + assert isinstance(T_cold, float), "Cold temperature (T_cold) must be a float" + assert isinstance(T_hot, float), "Hot temperature (T_hot) must be a float" self.D = d self.T_cold = T_cold self.T_hot = T_hot diff --git a/logs/test_output.log b/logs/test_output.log new file mode 100644 index 00000000..e69de29b diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..b4f3f7cf --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +log_cli = true +log_cli_level = INFO +log_file = logs/test_output.log +log_file_level = INFO \ No newline at end of file diff --git a/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.4.pyc b/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.4.pyc new file mode 100644 index 00000000..418e250a Binary files /dev/null and b/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.4.pyc differ diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b40..0d953bb3 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -1,8 +1,11 @@ """ Tests for functionality checks in class SolveDiffusion2D """ - +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) from diffusion2d import SolveDiffusion2D +import numpy as np def test_initialize_physical_parameters(): @@ -10,10 +13,52 @@ def test_initialize_physical_parameters(): Checks function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + w, h, dx, dy = 2.0, 3.0, 0.1, 0.1 + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.initialize_domain(w, h, dx, dy) + solver.initialize_physical_parameters(D, T_cold, T_hot) + + dx2, dy2 = solver.dx**2, solver.dy**2 + expected_dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + + assert solver.D == D, f"Expected D: {D}, but got: {solver.D}" + assert solver.T_cold == T_cold, f"Expected T_cold: {T_cold}, but got: {solver.T_cold}" + assert solver.T_hot == T_hot, f"Expected T_hot: {T_hot}, but got: {solver.T_hot}" + assert np.isclose(solver.dt, expected_dt), f"Expected dt: {expected_dt}, but got: {solver.dt}" + + # debug + print(f"Expected dt: {expected_dt}, Computed dt: {solver.dt}") + def test_set_initial_condition(): """ Checks function SolveDiffusion2D.get_initial_function """ + # Create an instance of the solver solver = SolveDiffusion2D() + + w, h, dx, dy = 10.0, 10.0, 1.0, 1.0 + T_cold, T_hot = 250.0, 750.0 + solver.initialize_domain(w, h, dx, dy) + solver.T_cold = T_cold + solver.T_hot = T_hot + + # to set initial condition + u = solver.set_initial_condition() + + # expected result + expected_u = np.full((solver.nx, solver.ny), T_cold) + r, cx, cy = 2, 5, 5 # Circle radius and center + r2 = r ** 2 + + for i in range(solver.nx): + for j in range(solver.ny): + p2 = (i * solver.dx - cx) ** 2 + (j * solver.dy - cy) ** 2 + if p2 < r2: + expected_u[i, j] = T_hot + + # Debug + print(f"Expected u:\n{expected_u}") + print(f"Actual u:\n{u}") + assert np.array_equal(u, expected_u), "The set_initial_condition method did not produce the expected output." diff --git a/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.4.pyc b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.4.pyc new file mode 100644 index 00000000..6058be33 Binary files /dev/null and b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.4.pyc differ diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ffd..4569b88b 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -2,6 +2,9 @@ Tests for functions in class SolveDiffusion2D """ +import sys +import os +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) from diffusion2d import SolveDiffusion2D @@ -10,6 +13,12 @@ def test_initialize_domain(): Check function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + w, h, dx, dy = 2.0, 3.0, 0.5, 0.5 + expected_nx = int(w / dx) + expected_ny = int(h / dy) + solver.initialize_domain(w, h, dx, dy) + assert solver.nx == expected_nx + assert solver.ny == expected_ny def test_initialize_physical_parameters(): @@ -17,6 +26,11 @@ def test_initialize_physical_parameters(): Checks function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.dx, solver.dy = 0.1, 0.1 + solver.initialize_physical_parameters(D, T_cold, T_hot) + expected_dt = (solver.dx * solver.dy) / (2 * D*10 * (solver.dx + solver.dy)) + assert solver.dt == expected_dt, f"Expected dt: {expected_dt}, but got: {solver.dt}" def test_set_initial_condition(): @@ -24,3 +38,13 @@ def test_set_initial_condition(): Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.D = D + solver.T_cold = T_cold + solver.T_hot = T_hot + solver.dx = 0.1 + solver.dy = 0.1 + solver.initialize_physical_parameters(D, T_cold, T_hot) + # Calculate expected dt based on the stability criterion + expected_dt = (solver.dx * solver.dy) / (2 * D*10 * (solver.dx + solver.dy)) + assert solver.dt == expected_dt diff --git a/tox.toml b/tox.toml new file mode 100644 index 00000000..a0a098b8 --- /dev/null +++ b/tox.toml @@ -0,0 +1,21 @@ +requires = ["tox>=4"] +env_list = ["unittest", "integrationtest", "coverage"] + +[env.unittest] +description = "Run Unit Tests" +deps = ["pytest>=8", "matplotlib", "numpy"] +commands = [["pytest", "tests/unit/test_diffusion2d_functions.py"]] + +[env.integrationtest] +description = "Run Integration Tests" +deps = ["pytest>=8", "matplotlib", "numpy"] +commands = [["pytest", "tests/integration/test_diffusion2d.py"]] + +[env.coverage] +description = "Check Coverage" +deps = ["pytest>=8", "coverage", "matplotlib", "numpy"] +commands = [ + ["coverage", "run", "-m", "pytest"], + ["coverage", "html"], + ["coverage", "report"] +]