diff --git a/.coverage b/.coverage new file mode 100644 index 00000000..a78b182c Binary files /dev/null and b/.coverage differ diff --git a/README.md b/README.md index da66993c..fdca49a3 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,174 @@ Please follow the instructions in [python_testing_exercise.md](https://github.co ## Test logs (for submission) ### pytest log +After changing self.nx = int(w / dx) to self.nx = int(h / dx) +PS D:\COMMAS\SSE\testing-python-exercise-wt2425> pytest tests/unit/test_diffusion2d_functions.py +================================================================ test session starts ================================================================= +platform win32 -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0 +rootdir: D:\COMMAS\SSE\testing-python-exercise-wt2425 +collected 3 items + +tests\unit\test_diffusion2d_functions.py F.. [100%] + +====================================================================== FAILURES====================================================================== +_______________________________________________________________ test_initialize_domain _______________________________________________________________ + + def test_initialize_domain(): + """ + Check function SolveDiffusion2D.initialize_domain + """ + solver = SolveDiffusion2D() + + solver.initialize_domain(w=4.0, h=6.0, dx=0.2, dy=0.3) + + # Assert the values of the domain parameters +> assert solver.nx == 20 # 4.0 / 0.2 +E assert 30 == 20 +E + where 30 = .nx + +tests\unit\test_diffusion2d_functions.py:21: AssertionError +============================================================== short test summary info=============================================================== +FAILED tests/unit/test_diffusion2d_functions.py::test_initialize_domain - assert 30 == 20 +============================================================ 1 failed, 2 passed in 0.59s============================================================= ### unittest log +- Failure: + +.dt = 0.0010000000000000002 +F. +====================================================================== +FAIL: test_initialize_physical_parameters (__main__.TestSolveDiffusion2D.test_initialize_physical_parameters) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\COMMAS\SSE\testing-python-exercise-wt2425\tests\unit\test_diffusion2d_functions_unittest.py", line 31, in test_initialize_physical_parameters + self.assertAlmostEqual(self.solver.dt, expected_dt, places=6, msg="dt calculation is incorrect") +AssertionError: 0.0010000000000000002 != 0.010000000000000002 within 6 places (0.009000000000000001 difference) : dt calculation is incorrect + +---------------------------------------------------------------------- +Ran 3 tests in 0.004s + +FAILED (failures=1) +Backend tkagg is interactive backend. Turning interactive mode on. + + +- After updating formula for dt + +.dt = 0.0010000000000000002 +.. +---------------------------------------------------------------------- +Ran 3 tests in 0.001s + +OK + +- with self.nx = int(h / dx) an intentional bug +Fdt = 0.0010000000000000002 +.. +====================================================================== +FAIL: test_initialize_domain (__main__.TestSolveDiffusion2D.test_initialize_domain) +---------------------------------------------------------------------- +Traceback (most recent call last): + File "D:\COMMAS\SSE\testing-python-exercise-wt2425\tests\unit\test_diffusion2d_functions_unittest.py", line 22, in test_initialize_domain + self.assertEqual(self.solver.nx, 20, "nx calculation is incorrect") +AssertionError: 30 != 20 : nx calculation is incorrect + +---------------------------------------------------------------------- +Ran 3 tests in 0.003s + +FAILED (failures=1) +Backend tkagg is interactive backend. Turning interactive mode on. + +### integration_test log +- with intensional bug in initialize_physical_parameters and set_initial_condition + +================================================================ test session starts================================================================= +platform win32 -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0 +rootdir: D:\COMMAS\SSE\testing-python-exercise-wt2425 +collected 2 items + +tests\integration\test_diffusion2d.py F. [100%] + +====================================================================== FAILURES====================================================================== +________________________________________________________ test_initialize_physical_parameters _________________________________________________________ + + def test_initialize_physical_parameters(): + """ + Integration test for initialize_physical_parameters and initialize_domain + """ + solver = SolveDiffusion2D() + + # Initialize the domain + solver.initialize_domain(w=10.0, h=10.0, dx=0.1, dy=0.1) + + # Initialize physical parameters + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.initialize_physical_parameters(d=D, T_cold=T_cold, T_hot=T_hot) + + # Manually compute expected dt + dx2, dy2 = solver.dx ** 2, solver.dy ** 2 + expected_dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + + # Assert dt is correctly calculated +> assert np.isclose(solver.dt, expected_dt, atol=1e-6), f"Expected dt: {expected_dt}, but got: {solver.dt}" +E AssertionError: Expected dt: 0.0010000000000000002, but got: 0.1 +E assert np.False_ +E + where np.False_ = (0.1, 0.0010000000000000002, atol=1e-06) +E + where = np.isclose +E + and 0.1 = .dt + +tests\integration\test_diffusion2d.py:31: AssertionError +---------------------------------------------------------------- Captured stdout call---------------------------------------------------------------- +dt = 0.1 +============================================================== short test summary info=============================================================== +FAILED tests/integration/test_diffusion2d.py::test_initialize_physical_parameters - AssertionError: Expected dt: 0.0010000000000000002, but got: 0.1 +============================================================ 1 failed, 1 passed in 0.58s============================================================= + +================================================================ test session starts ================================================================= +platform win32 -- Python 3.12.6, pytest-8.3.3, pluggy-1.5.0 +rootdir: D:\COMMAS\SSE\testing-python-exercise-wt2425 +collected 2 items + +tests\integration\test_diffusion2d.py .F [100%] + +====================================================================== FAILURES====================================================================== +_____________________________________________________________ test_set_initial_condition _____________________________________________________________ + + def test_set_initial_condition(): + """ + Integration test for set_initial_condition and initialize_domain + """ + solver = SolveDiffusion2D() + + # Initialize the domain + solver.initialize_domain(w=10.0, h=10.0, dx=1.0, dy=1.0) + + # Initialize physical parameters + solver.T_cold = 300.0 + solver.T_hot = 700.0 + + # Set initial conditions + u = solver.set_initial_condition() + + # Manually compute the expected initial condition array + expected_u = np.full((10, 10), 300.0) # Initialize with T_cold + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + for i in range(10): + for j in range(10): + p2 = (i - cx) ** 2 + (j - cy) ** 2 + if p2 < r2: + expected_u[i, j] = 700.0 # Set T_hot for points inside the circle + + # Assert the computed field matches the expected field +> assert np.array_equal(u, expected_u), "The initial condition array is incorrect" +E AssertionError: The initial condition array is incorrect +E assert False +E + where False = (array([[700., 700., 700., 700., 700., 700., 700., 700., 700., 700.],\n [700., 700., 700., 700., 700., 700., 700.,... 700., 700., 700., 700., 700., 700., 700., 700.],\n [700., 700., 700., 700., 700., 700., 700., 700., 700., 700.]]), array([[300., 300., 300., 300., 300., 300., 300., 300., 300., 300.],\n [300., 300., 300., 300., 300., 300., 300.,... 300., 300., 300., 300., 300., 300., 300., 300.],\n [300., 300., 300., 300., 300., 300., 300., 300., 300., 300.]])) +E + where = np.array_equal +tests\integration\test_diffusion2d.py:61: AssertionError +============================================================== short test summary info=============================================================== +FAILED tests/integration/test_diffusion2d.py::test_set_initial_condition - AssertionError: The initial condition array is incorrect +============================================================ 1 failed, 1 passed in 0.57s============================================================= ## 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..ba3f32d7 Binary files /dev/null and b/__pycache__/diffusion2d.cpython-312.pyc differ diff --git a/coverage-report.pdf b/coverage-report.pdf new file mode 100644 index 00000000..c1428d02 Binary files /dev/null and b/coverage-report.pdf differ diff --git a/diffusion2d.py b/diffusion2d.py index 51a07f2d..3e588a41 100644 --- a/diffusion2d.py +++ b/diffusion2d.py @@ -38,6 +38,10 @@ 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), "dx must be a float" + assert isinstance(dy, float), "dy must be a float" self.w = w self.h = h self.dx = dx @@ -45,7 +49,10 @@ def initialize_domain(self, w=10., h=10., dx=0.1, dy=0.1): self.nx = int(w / dx) self.ny = int(h / dy) - def initialize_physical_parameters(self, d=4., T_cold=300, T_hot=700): + def initialize_physical_parameters(self, d=4., T_cold=300.0, T_hot=700.0): + 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 @@ -68,6 +75,7 @@ def set_initial_condition(self): if p2 < r2: u[i, j] = self.T_hot + return u.copy() def do_timestep(self, u_nm1): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..0145fcae --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# This file marks the directory as a Python package. diff --git a/tests/__pycache__/__init__.cpython-312.pyc b/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..a05ff2e7 Binary files /dev/null and b/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..0145fcae --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +# This file marks the directory as a Python package. diff --git a/tests/integration/__pycache__/__init__.cpython-312.pyc b/tests/integration/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..40edfc81 Binary files /dev/null and b/tests/integration/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.3.pyc b/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.3.pyc new file mode 100644 index 00000000..eb90fae5 Binary files /dev/null and b/tests/integration/__pycache__/test_diffusion2d.cpython-312-pytest-8.3.3.pyc differ diff --git a/tests/integration/__pycache__/test_diffusion2d.cpython-312.pyc b/tests/integration/__pycache__/test_diffusion2d.cpython-312.pyc new file mode 100644 index 00000000..31cd1b5e Binary files /dev/null and b/tests/integration/__pycache__/test_diffusion2d.cpython-312.pyc differ diff --git a/tests/integration/test_diffusion2d.py b/tests/integration/test_diffusion2d.py index fd026b40..9e8e8a6f 100644 --- a/tests/integration/test_diffusion2d.py +++ b/tests/integration/test_diffusion2d.py @@ -1,19 +1,61 @@ """ Tests for functionality checks in class SolveDiffusion2D """ +import sys +import os +# Add the root directory to the Python path +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(): """ - Checks function SolveDiffusion2D.initialize_domain + Integration test for initialize_physical_parameters and initialize_domain """ solver = SolveDiffusion2D() + + # Initialize the domain + solver.initialize_domain(w=10.0, h=10.0, dx=0.1, dy=0.1) + + # Initialize physical parameters + D, T_cold, T_hot = 2.5, 250.0, 750.0 + solver.initialize_physical_parameters(d=D, T_cold=T_cold, T_hot=T_hot) + + # Manually compute expected dt + dx2, dy2 = solver.dx ** 2, solver.dy ** 2 + expected_dt = dx2 * dy2 / (2 * D * (dx2 + dy2)) + + # Assert dt is correctly calculated + assert np.isclose(solver.dt, expected_dt, atol=1e-6), f"Expected dt: {expected_dt}, but got: {solver.dt}" def test_set_initial_condition(): """ - Checks function SolveDiffusion2D.get_initial_function + Integration test for set_initial_condition and initialize_domain """ solver = SolveDiffusion2D() + + # Initialize the domain + solver.initialize_domain(w=10.0, h=10.0, dx=1.0, dy=1.0) + + # Initialize physical parameters + solver.T_cold = 300.0 + solver.T_hot = 700.0 + + # Set initial conditions + u = solver.set_initial_condition() + + # Manually compute the expected initial condition array + expected_u = np.full((10, 10), 300.0) # Initialize with T_cold + r, cx, cy = 2, 5, 5 + r2 = r ** 2 + for i in range(10): + for j in range(10): + p2 = (i - cx) ** 2 + (j - cy) ** 2 + if p2 < r2: + expected_u[i, j] = 700.0 # Set T_hot for points inside the circle + + # Assert the computed field matches the expected field + assert np.array_equal(u, expected_u), "The initial condition array is incorrect" diff --git a/tests/tox.toml b/tests/tox.toml new file mode 100644 index 00000000..5dd905e7 --- /dev/null +++ b/tests/tox.toml @@ -0,0 +1,22 @@ +requires = ["tox>=4"] +env_list = ["unittest", "integrationtest", "coverage"] + +[testenv] +description = "Base environment for running tests" +deps = ["pytest>=8", "matplotlib", "numpy", "coverage"] + +[env.unittest] +description = "Run Unit Tests" +commands = [["pytest", "tests/unit/test_diffusion2d_functions.py"]] + +[env.integrationtest] +description = "Run Integration Tests" +commands = [["pytest", "tests/integration/test_diffusion2d.py"]] + +[env.coverage] +description = "Check Coverage" +commands = [ + ["coverage", "run", "-m", "pytest", "tests/"], + ["coverage", "html", "-d", "coverage-html"], + ["coverage", "report", "-m"] +] diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..0145fcae --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +# This file marks the directory as a Python package. diff --git a/tests/unit/__pycache__/__init__.cpython-312.pyc b/tests/unit/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..4fbcdf52 Binary files /dev/null and b/tests/unit/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.3.pyc b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.3.pyc new file mode 100644 index 00000000..4c854a84 Binary files /dev/null and b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312-pytest-8.3.3.pyc differ diff --git a/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312.pyc b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312.pyc new file mode 100644 index 00000000..18a6e807 Binary files /dev/null and b/tests/unit/__pycache__/test_diffusion2d_functions.cpython-312.pyc differ diff --git a/tests/unit/__pycache__/test_diffusion2d_functions_unittest.cpython-312.pyc b/tests/unit/__pycache__/test_diffusion2d_functions_unittest.cpython-312.pyc new file mode 100644 index 00000000..10031e79 Binary files /dev/null and b/tests/unit/__pycache__/test_diffusion2d_functions_unittest.cpython-312.pyc differ diff --git a/tests/unit/test_diffusion2d_functions.py b/tests/unit/test_diffusion2d_functions.py index c4277ffd..d4053be7 100644 --- a/tests/unit/test_diffusion2d_functions.py +++ b/tests/unit/test_diffusion2d_functions.py @@ -1,6 +1,10 @@ """ -Tests for functions in class SolveDiffusion2D +Tests for functions in class SolveDiffusion2D using pytest """ +import sys +import os + +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) from diffusion2d import SolveDiffusion2D @@ -11,12 +15,30 @@ def test_initialize_domain(): """ solver = SolveDiffusion2D() + solver.initialize_domain(w=4.0, h=6.0, dx=0.2, dy=0.3) + + # Assert the values of the domain parameters + assert solver.nx == 20 # 4.0 / 0.2 + assert solver.ny == 20 # 6.0 / 0.3 + def test_initialize_physical_parameters(): """ Checks function SolveDiffusion2D.initialize_domain """ solver = SolveDiffusion2D() + solver.initialize_domain(w=10.0, h=10.0, dx=0.5, dy=0.5) + solver.initialize_physical_parameters(d=1.0, T_cold=300.0, T_hot=700.0) + + # Assert the values of the physical parameters + assert solver.D == 1.0 + assert solver.T_cold == 300.0 + assert solver.T_hot == 700.0 + + # Assert the computed time step + dx2, dy2 = solver.dx ** 2, solver.dy ** 2 + expected_dt = dx2 * dy2 / (2 * solver.D * (dx2 + dy2)) + assert solver.dt == expected_dt def test_set_initial_condition(): @@ -24,3 +46,13 @@ def test_set_initial_condition(): Checks function SolveDiffusion2D.get_initial_function """ solver = SolveDiffusion2D() + solver.initialize_domain(w=10.0, h=10.0, dx=1.0, dy=1.0) + solver.initialize_physical_parameters(d=1.0, T_cold=300.0, T_hot=700.0) + u = solver.set_initial_condition() + + # Assert the shape of the initial condition array + assert u.shape == (10, 10) + + # Assert the initial temperature values in the domain + assert (u == 300.0).sum() > 0 + assert (u == 700.0).sum() > 0 diff --git a/tests/unit/test_diffusion2d_functions_unittest.py b/tests/unit/test_diffusion2d_functions_unittest.py new file mode 100644 index 00000000..0fae459d --- /dev/null +++ b/tests/unit/test_diffusion2d_functions_unittest.py @@ -0,0 +1,47 @@ +""" +Tests for functions in class SolveDiffusion2D using unittest +""" +import sys +import os + +# Add the root directory to the Python path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))) + +import unittest +from diffusion2d import SolveDiffusion2D + + +class TestSolveDiffusion2D(unittest.TestCase): + def setUp(self): + self.solver = SolveDiffusion2D() + + def test_initialize_domain(self): + self.solver.initialize_domain(w=4.0, h=6.0, dx=0.2, dy=0.3) + + # Verify calculated nx and ny + self.assertEqual(self.solver.nx, 20, "nx calculation is incorrect") + self.assertEqual(self.solver.ny, 20, "ny calculation is incorrect") + + def test_initialize_physical_parameters(self): + self.solver.dx = 0.1 + self.solver.dy = 0.1 + self.solver.initialize_physical_parameters(d=2.5, T_cold=250.0, T_hot=750.0) + + expected_dt = (self.solver.dx ** 2) * (self.solver.dy ** 2) / (2 * 2.5 * ((self.solver.dx ** 2) + (self.solver.dy ** 2))) + self.assertAlmostEqual(self.solver.dt, expected_dt, places=6, msg="dt calculation is incorrect") + + def test_set_initial_condition(self): + self.solver.initialize_domain(w=10.0, h=10.0, dx=1.0, dy=1.0) + self.solver.T_cold = 300.0 + self.solver.T_hot = 700.0 + + u = self.solver.set_initial_condition() + + # Verify shape and temperature values + self.assertEqual(u.shape, (10, 10), "Initial condition array shape is incorrect") + self.assertGreater((u == 300.0).sum(), 0, "T_cold is not set correctly") + self.assertGreater((u == 700.0).sum(), 0, "T_hot is not set correctly") + + +if __name__ == "__main__": + unittest.main()