diff --git a/news/pytest-test-refactor.rst b/news/pytest-test-refactor.rst new file mode 100644 index 00000000..08d3819b --- /dev/null +++ b/news/pytest-test-refactor.rst @@ -0,0 +1,23 @@ +**Added:** + +* No news added: refactoring tests that have been mentioned in previous news + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/tests/test_diffraction_objects.py b/tests/test_diffraction_objects.py index 4576b257..63f349eb 100644 --- a/tests/test_diffraction_objects.py +++ b/tests/test_diffraction_objects.py @@ -186,7 +186,7 @@ def test_init_invalid_xtype(): "org_do_args, target_do_args, scale_inputs, expected", [ # Test that scale_to() scales to the correct values - # Case 1: same x-array and y-array, check offset + # C1: Same x-array and y-array, check offset ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -208,7 +208,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([4.1, 5.1, 6.1, 7.1, 8.1, 9.1])}, ), - # Case 2: same length x-arrays with exact x-value match + # C2: Same length x-arrays with exact x-value match ( { "xarray": np.array([10, 15, 25, 30, 60, 140]), @@ -230,7 +230,7 @@ def test_init_invalid_xtype(): }, {"xtype": "tth", "yarray": np.array([1, 2, 2.5, 3, 6, 10])}, ), - # Case 3: same length x-arrays with approximate x-value match + # C3: Same length x-arrays with approximate x-value match ( { "xarray": np.array([0.12, 0.24, 0.31, 0.4]), @@ -252,7 +252,7 @@ def test_init_invalid_xtype(): }, {"xtype": "q", "yarray": np.array([1, 2, 4, 6])}, ), - # Case 4: different x-array lengths with approximate x-value match + # C4: Different x-array lengths with approximate x-value match ( { "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), @@ -272,7 +272,7 @@ def test_init_invalid_xtype(): "d": None, "offset": 0, }, - # Case 5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) + # C5: Scaling factor is calculated at index = 4 (tth=61) for self and index = 5 for target (tth=62) {"xtype": "tth", "yarray": np.array([1, 2, 3, 4, 5, 6, 10])}, ), ], @@ -287,76 +287,81 @@ def test_scale_to(org_do_args, target_do_args, scale_inputs, expected): assert np.allclose(scaled_do.on_xtype(expected["xtype"])[1], expected["yarray"]) -params_scale_to_bad = [ - # UC1: user did not specify anything - ( - { - "xarray": np.array([0.1, 0.2, 0.3]), - "yarray": np.array([1, 2, 3]), - "xtype": "q", - "wavelength": 2 * np.pi, - "target_xarray": np.array([0.05, 0.1, 0.2, 0.3]), - "target_yarray": np.array([5, 10, 20, 30]), - "target_xtype": "q", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": None, - "d": None, - "offset": 0, - } - ), - # UC2: user specified more than one of q, tth, and d - ( - { - "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), - "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), - "xtype": "tth", - "wavelength": 2 * np.pi, - "target_xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), - "target_yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), - "target_xtype": "tth", - "target_wavelength": 2 * np.pi, - "q": None, - "tth": 60, - "d": 10, - "offset": 0, - } - ), -] - - -@pytest.mark.parametrize("inputs", params_scale_to_bad) -def test_scale_to_bad(inputs): - orig_diff_object = DiffractionObject( - xarray=inputs["xarray"], yarray=inputs["yarray"], xtype=inputs["xtype"], wavelength=inputs["wavelength"] - ) - target_diff_object = DiffractionObject( - xarray=inputs["target_xarray"], - yarray=inputs["target_yarray"], - xtype=inputs["target_xtype"], - wavelength=inputs["target_wavelength"], - ) +@pytest.mark.parametrize( + "org_do_args, target_do_args, scale_inputs", + [ + # UC1: User did not specify anything + ( + { + "xarray": np.array([0.1, 0.2, 0.3]), + "yarray": np.array([1, 2, 3]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([0.05, 0.1, 0.2, 0.3]), + "yarray": np.array([5, 10, 20, 30]), + "xtype": "q", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": None, + "d": None, + "offset": 0, + }, + ), + # UC2: User specified more than one of q, tth, and d + ( + { + "xarray": np.array([10, 25, 30.1, 40.2, 61, 120, 140]), + "yarray": np.array([10, 20, 30, 40, 50, 60, 100]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "xarray": np.array([20, 25.5, 32, 45, 50, 62, 100, 125, 140]), + "yarray": np.array([1.1, 2, 3, 3.5, 4, 5, 10, 12, 13]), + "xtype": "tth", + "wavelength": 2 * np.pi, + }, + { + "q": None, + "tth": 60, + "d": 10, + "offset": 0, + }, + ), + ], +) +def test_scale_to_bad(org_do_args, target_do_args, scale_inputs): + original_do = DiffractionObject(**org_do_args) + target_do = DiffractionObject(**target_do_args) with pytest.raises( ValueError, match="You must specify exactly one of 'q', 'tth', or 'd'. Please rerun specifying only one." ): - orig_diff_object.scale_to( - target_diff_object, q=inputs["q"], tth=inputs["tth"], d=inputs["d"], offset=inputs["offset"] + original_do.scale_to( + target_do, + q=scale_inputs["q"], + tth=scale_inputs["tth"], + d=scale_inputs["d"], + offset=scale_inputs["offset"], ) -params_index = [ - # UC1: exact match - (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), - # UC2: target value lies in the array, returns the (first) closest index - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), - # UC3: target value out of the range, returns the closest index - (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), - (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), -] - - -@pytest.mark.parametrize("wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", params_index) +@pytest.mark.parametrize( + "wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index", + [ + # UC1: Exact match + (4 * np.pi, np.array([30.005, 60]), np.array([1, 2]), "tth", "tth", 30.005, [0]), + # UC2: Target value lies in the array, returns the (first) closest index + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 45, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "q", 0.25, [0]), + # UC3: Target value out of the range, returns the closest index + (4 * np.pi, np.array([0.25, 0.5, 0.71]), np.array([1, 2, 3]), "q", "q", 0.1, [0]), + (4 * np.pi, np.array([30, 60]), np.array([1, 2]), "tth", "tth", 63, [1]), + ], +) def test_get_array_index(wavelength, xarray, yarray, xtype_1, xtype_2, value, expected_index): do = DiffractionObject(wavelength=wavelength, xarray=xarray, yarray=yarray, xtype=xtype_1) actual_index = do.get_array_index(value=value, xtype=xtype_2) @@ -406,7 +411,7 @@ def test_dump(tmp_path, mocker): @pytest.mark.parametrize( "do_init_args, expected_do_dict, divide_by_zero_warning_expected", [ - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([0.0, 90.0, 180.0]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -435,7 +440,7 @@ def test_dump(tmp_path, mocker): }, True, ), - ( # instantiate just array attributes + ( # Instantiate just array attributes { "xarray": np.array([np.inf, 2 * np.sqrt(2) * np.pi, 2 * np.pi]), "yarray": np.array([1.0, 2.0, 3.0]), @@ -482,11 +487,11 @@ def test_init_valid(do_init_args, expected_do_dict, divide_by_zero_warning_expec @pytest.mark.parametrize( "do_init_args, expected_error_msg", [ - ( # Case 1: no arguments provided + ( # C1: No arguments provided {}, "missing 3 required positional arguments: 'xarray', 'yarray', and 'xtype'", ), - ( # Case 2: only xarray and yarray provided + ( # C2: Only xarray and yarray provided {"xarray": np.array([0.0, 90.0]), "yarray": np.array([0.0, 90.0])}, "missing 1 required positional argument: 'xtype'", ), diff --git a/tests/test_transforms.py b/tests/test_transforms.py index 65f437ac..2a40805d 100644 --- a/tests/test_transforms.py +++ b/tests/test_transforms.py @@ -9,20 +9,20 @@ @pytest.mark.parametrize( "wavelength, q, expected_tth", [ - # Case 1: Allow empty arrays for q - # 1. Empty q values, no wavelength, return empty arrays - (None, np.empty((0)), np.empty((0))), - # 2. Empty q values, wavelength specified, return empty arrays + # Test conversion of q to tth with q and wavelength + # C1: Allow empty array q to compute tth with or without wavelength + # 1. Wavelength provided, expect empty array of tth (4 * np.pi, np.empty((0)), np.empty(0)), - # Case 2: Allow wavelength to be missing. - # Valid q values, no wavelength, return index array + # 2. No wavelength provided, expected empty array of tth and wavelength UserWarning + (None, np.empty((0)), np.empty((0))), + # C2: Use non-empty q values to compute tth with or without wavelength + # 1. No wavelength provided, expect valid tth values in degrees with wavelength UserWarning ( None, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), np.array([0, 1, 2, 3, 4, 5]), ), - # Case 3: Correctly specified q and wavelength - # Expected tth values are 2*arcsin(q) in degrees + # 2. Wavelength provided, expect tth values of 2*arcsin(q) in degrees (4 * np.pi, np.array([0, 1 / np.sqrt(2), 1.0]), np.array([0, 90.0, 180.0])), ], ) @@ -39,13 +39,14 @@ def test_q_to_tth(wavelength, q, expected_tth, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, q, expected_error_type", [ - # UC1: user specified invalid q values that result in tth > 180 degrees + # Test ValeuError in q to tth conversion with invalid two-theta values. + # C1: Invalid q values that result in tth > 180 degrees, expect ValueError ( 4 * np.pi, np.array([0.2, 0.4, 0.6, 0.8, 1, 1.2]), ValueError, ), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: Wrong wavelength that results in tth > 180 degrees, expect ValueError ( 100, np.array([0, 0.2, 0.4, 0.6, 0.8, 1]), @@ -62,18 +63,20 @@ def test_q_to_tth_bad(wavelength, q, expected_error_type, invalid_q_or_d_or_wave @pytest.mark.parametrize( "wavelength, tth, expected_q", [ - # UC0: user specified empty tth values (without wavelength) + # Test conversion of q to tth with q and wavelength + # C1: Allow empty tth values to compute 1, with or without wavelength + # 1. Wavelength provided, expect empty array of q (None, np.array([]), np.array([])), - # UC1: user specified empty tth values (with wavelength) + # 2. No wavelength provided, expected empty array of q and wavelength UserWarning (4 * np.pi, np.array([]), np.array([])), - # UC2: user specified valid tth values between 0-180 degrees (without wavelength) + # C2: Use non-empty tth values between 0-180 degrees to compute q, with or without wavelength + # 1. No wavelength provided, expect valid q values between 0-1 ( None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), ), - # UC3: user specified valid tth values between 0-180 degrees (with wavelength) - # expected q values are sin15, sin30, sin45, sin60, sin90 + # 2. Wavelength provided, expect expected q values are sin15, sin30, sin45, sin60, sin90 ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -94,14 +97,15 @@ def test_tth_to_q(wavelength, tth, expected_q, wavelength_warning_msg): @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC0: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: Invalid tth value of > 180 degrees provided, with or without wavelength + # 1. No wavelength provided, expect two theta ValueError ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC1: user specified an invalid tth value of > 180 degrees (with wavelength) + # 2. Wavelength provided, expect two theta ValueError ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -119,16 +123,16 @@ def test_tth_to_q_bad(wavelength, tth, expected_error_type, expected_error_msg): "q, expected_d, warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty q values, expect empty d values + # C1: Empty q values, expect empty d values (np.array([]), np.array([]), False), - # Case 2: - # 1. valid q values, expect d values without warning + # C2: + # 1. Valid q values, expect d values without warning ( np.array([0.1, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([62.83185307, 2, 1, 0.66667, 0.5, 0.4]), False, ), - # 2. valid q values containing 0, expect d values with divide by zero warning + # 2. Valid q values containing 0, expect d values with divide by zero warning ( np.array([0, 1 * np.pi, 2 * np.pi, 3 * np.pi, 4 * np.pi, 5 * np.pi]), np.array([np.inf, 2, 1, 0.66667, 0.5, 0.4]), @@ -148,9 +152,9 @@ def test_q_to_d(q, expected_d, warning_expected): @pytest.mark.parametrize( "d, expected_q, zero_divide_error_expected", [ - # UC1: User specified empty d values + # C1: User specified empty d values (np.array([]), np.array([]), False), - # UC2: User specified valid d values + # C2: User specified valid d values ( np.array([5 * np.pi, 4 * np.pi, 3 * np.pi, 2 * np.pi, np.pi, 0]), np.array([0.4, 0.5, 0.66667, 1, 2, np.inf]), @@ -171,13 +175,13 @@ def test_d_to_q(d, expected_q, zero_divide_error_expected): "wavelength, tth, expected_d, divide_by_zero_warning_expected", [ # Test conversion of q to d with valid values - # Case 1: empty tth values, no, expect empty d values + # C1: Empty tth values, no, expect empty d values (None, np.array([]), np.array([]), False), - # Case 2: empty tth values, wavelength provided, expect empty d values + # C2: Empty tth values, wavelength provided, expect empty d values (4 * np.pi, np.array([]), np.array([]), False), - # Case 3: User specified valid tth values between 0-180 degrees (without wavelength) + # C3: User specified valid tth values between 0-180 degrees (without wavelength) (None, np.array([0, 30, 60, 90, 120, 180]), np.array([0, 1, 2, 3, 4, 5]), False), - # Case 4: User specified valid tth values between 0-180 degrees (with wavelength) + # C4: User specified valid tth values between 0-180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30.0, 60.0, 90.0, 120.0, 180.0]), @@ -201,14 +205,14 @@ def test_tth_to_d(wavelength, tth, expected_d, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, tth, expected_error_type, expected_error_msg", [ - # UC1: user specified an invalid tth value of > 180 degrees (without wavelength) + # C1: User specified an invalid tth value of > 180 degrees (without wavelength) ( None, np.array([0, 30, 60, 90, 120, 181]), ValueError, "Two theta exceeds 180 degrees. Please check the input values for errors.", ), - # UC2: user specified an invalid tth value of > 180 degrees (with wavelength) + # C2: User specified an invalid tth value of > 180 degrees (with wavelength) ( 4 * np.pi, np.array([0, 30, 60, 90, 120, 181]), @@ -225,13 +229,13 @@ def test_tth_to_d_invalid(wavelength, tth, expected_error_type, expected_error_m @pytest.mark.parametrize( "wavelength, d, expected_tth, divide_by_zero_warning_expected", [ - # UC1: Empty d values, no wavelength, return empty arrays + # C1: Empty d values, no wavelength, return empty arrays (None, np.empty((0)), np.empty((0)), False), - # UC2: Empty d values, wavelength specified, return empty arrays + # C2: Empty d values, wavelength specified, return empty arrays (4 * np.pi, np.empty((0)), np.empty(0), False), - # UC3: User specified valid d values, no wavelength, return empty arrays + # C3: User specified valid d values, no wavelength, return empty arrays (None, np.array([1, 0.8, 0.6, 0.4, 0.2, 0]), np.array([0, 1, 2, 3, 4, 5]), True), - # UC4: User specified valid d values (with wavelength) + # C4: User specified valid d values (with wavelength) ( 4 * np.pi, np.array([4 * np.pi, 4 / np.sqrt(2) * np.pi, 4 / np.sqrt(3) * np.pi]), @@ -257,9 +261,9 @@ def test_d_to_tth(wavelength, d, expected_tth, divide_by_zero_warning_expected, @pytest.mark.parametrize( "wavelength, d, expected_error_type", [ - # UC1: user specified invalid d values that result in tth > 180 degrees + # C1: User specified invalid d values that result in tth > 180 degrees (4 * np.pi, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), - # UC2: user specified a wrong wavelength that result in tth > 180 degrees + # C2: User specified a wrong wavelength that result in tth > 180 degrees (100, np.array([1.2, 1, 0.8, 0.6, 0.4, 0.2]), ValueError), ], )