From ccd8befcacf068ca7b36d7c42a38dd8b9612ca60 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Sun, 19 Apr 2020 19:28:30 +0300 Subject: [PATCH 01/10] bpo-40331: increase test coverage for the statistics module --- Lib/statistics.py | 4 ++-- Lib/test/test_statistics.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Lib/statistics.py b/Lib/statistics.py index 9beafb341b3ad1..f13b3003d5edbd 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -267,7 +267,7 @@ def _convert(value, T): def _find_lteq(a, x): - 'Locate the leftmost value exactly equal to x' + """Locate the leftmost value exactly equal to x""" i = bisect_left(a, x) if i != len(a) and a[i] == x: return i @@ -275,7 +275,7 @@ def _find_lteq(a, x): def _find_rteq(a, l, x): - 'Locate the rightmost value exactly equal to x' + """Locate the rightmost value exactly equal to x""" i = bisect_right(a, x, lo=l) if i != (len(a)+1) and a[i-1] == x: return i-1 diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 0e46a7119f0efc..8596dbb74aeca5 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1004,6 +1004,9 @@ def test_nan(self): x = statistics._convert(nan, type(nan)) self.assertTrue(_nan_equal(x, nan)) + def test_raise_type_error(self): + self.assertRaises(TypeError, statistics._convert, None, float) + class FailNegTest(unittest.TestCase): """Test _fail_neg private function.""" @@ -1033,6 +1036,45 @@ def test_error_msg(self): self.assertEqual(errmsg, msg) +class FindLteqTest(unittest.TestCase): + # Test _find_lteq private function. + + def test_raise_value_error(self): + for a, x in [ + ([], 1), # empty a triggers `i != len(a)` + ([1, 2], 3), # non-existing max value triggers `i != len(a)` + ([1, 3], 2) # non-existing value triggers `a[i] == x` + ]: + self.assertRaises(ValueError, statistics._find_lteq, a, x) + + def test_locate_successfully(self): + for a, x, expected_i in [ + ([1, 1, 1, 2, 3], 1, 0), + ([0, 1, 1, 1, 2, 3], 1, 1), + ([1, 2, 3, 3, 3], 3, 2) + ]: + self.assertEqual(expected_i, statistics._find_lteq(a, x)) + + +class FindRteqTest(unittest.TestCase): + # Test _find_rteq private function. + + def test_raise_value_error(self): + for a, l, x in [ + ([1], 2, 1), # when l=len(a)+1 triggers `i != (len(a)+1)` + ([1, 3], 0, 2) # non-existing value triggers `a[i-1] == x` + ]: + self.assertRaises(ValueError, statistics._find_rteq, a, l, x) + + def test_locate_successfully(self): + for a, l, x, expected_i in [ + ([1, 1, 1, 2, 3], 0, 1, 2), + ([0, 1, 1, 1, 2, 3], 0, 1, 3), + ([1, 2, 3, 3, 3], 0, 3, 4) + ]: + self.assertEqual(expected_i, statistics._find_rteq(a, l, x)) + + # === Tests for public functions === class UnivariateCommonMixin: @@ -1454,6 +1496,9 @@ class TestHarmonicMean(NumericTestCase, AverageMixin, UnivariateTypeMixin): def setUp(self): self.func = statistics.harmonic_mean + def test_single_value_unsupported_type(self): + self.assertRaises(TypeError, self.func, ['3.14']) + def prepare_data(self): # Override mixin method. values = super().prepare_data() From 21084c1d9d39e135be971cb28f456f56796f080b Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Mon, 20 Apr 2020 00:52:00 +0300 Subject: [PATCH 02/10] Add a News entry using the blurb tool --- Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst diff --git a/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst b/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst new file mode 100644 index 00000000000000..e213cb33ebc0d4 --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst @@ -0,0 +1 @@ +Increase the coverage for the :mod:`statistics` module by 1%. From 49def61e93e0468e35afc104ed36ad262818f4d5 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Mon, 20 Apr 2020 01:09:23 +0300 Subject: [PATCH 03/10] Revert cosmetic change in docstring --- Lib/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/statistics.py b/Lib/statistics.py index f13b3003d5edbd..9beafb341b3ad1 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -267,7 +267,7 @@ def _convert(value, T): def _find_lteq(a, x): - """Locate the leftmost value exactly equal to x""" + 'Locate the leftmost value exactly equal to x' i = bisect_left(a, x) if i != len(a) and a[i] == x: return i @@ -275,7 +275,7 @@ def _find_lteq(a, x): def _find_rteq(a, l, x): - """Locate the rightmost value exactly equal to x""" + 'Locate the rightmost value exactly equal to x' i = bisect_right(a, x, lo=l) if i != (len(a)+1) and a[i-1] == x: return i-1 From b2b9844100db09aa716c48be4918eec31ee16603 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Mon, 20 Apr 2020 13:35:44 +0300 Subject: [PATCH 04/10] Remove the News entry --- Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst diff --git a/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst b/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst deleted file mode 100644 index e213cb33ebc0d4..00000000000000 --- a/Misc/NEWS.d/next/Tests/2020-04-20-00-45-56.bpo-40331.oLPd4g.rst +++ /dev/null @@ -1 +0,0 @@ -Increase the coverage for the :mod:`statistics` module by 1%. From 50a843fceff657a2107b37a7563deeec46a2e7b2 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Sat, 25 Apr 2020 23:24:13 +0300 Subject: [PATCH 05/10] Use assertRaises as a context manager in the tests --- Lib/test/test_statistics.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 8596dbb74aeca5..67d3677662e3ee 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1005,7 +1005,8 @@ def test_nan(self): self.assertTrue(_nan_equal(x, nan)) def test_raise_type_error(self): - self.assertRaises(TypeError, statistics._convert, None, float) + with self.assertRaises(TypeError): + statistics._convert(None, float) class FailNegTest(unittest.TestCase): @@ -1045,7 +1046,8 @@ def test_raise_value_error(self): ([1, 2], 3), # non-existing max value triggers `i != len(a)` ([1, 3], 2) # non-existing value triggers `a[i] == x` ]: - self.assertRaises(ValueError, statistics._find_lteq, a, x) + with self.assertRaises(ValueError): + statistics._find_lteq(a, x) def test_locate_successfully(self): for a, x, expected_i in [ @@ -1064,7 +1066,8 @@ def test_raise_value_error(self): ([1], 2, 1), # when l=len(a)+1 triggers `i != (len(a)+1)` ([1, 3], 0, 2) # non-existing value triggers `a[i-1] == x` ]: - self.assertRaises(ValueError, statistics._find_rteq, a, l, x) + with self.assertRaises(ValueError): + statistics._find_rteq(a, l, x) def test_locate_successfully(self): for a, l, x, expected_i in [ @@ -1496,9 +1499,6 @@ class TestHarmonicMean(NumericTestCase, AverageMixin, UnivariateTypeMixin): def setUp(self): self.func = statistics.harmonic_mean - def test_single_value_unsupported_type(self): - self.assertRaises(TypeError, self.func, ['3.14']) - def prepare_data(self): # Override mixin method. values = super().prepare_data() @@ -1521,6 +1521,10 @@ def test_negative_error(self): with self.subTest(values=values): self.assertRaises(exc, self.func, values) + def test_single_value_unsupported_type(self): + with self.assertRaises(TypeError): + self.func(['3.14']) + def test_ints(self): # Test harmonic mean with ints. data = [2, 4, 4, 8, 16, 16] From bbe16bd8f5f3fa27fe3a9d7957ad449daa004fd5 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Sun, 26 Apr 2020 15:21:20 +0300 Subject: [PATCH 06/10] Use assertRaisesRegex in the test_single_value_unsupported_type test --- Lib/test/test_statistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 67d3677662e3ee..1b28248398416d 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1522,7 +1522,7 @@ def test_negative_error(self): self.assertRaises(exc, self.func, values) def test_single_value_unsupported_type(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, '^unsupported type$'): self.func(['3.14']) def test_ints(self): From 74b047a86a0383fc8a487bc5d89ac5f3fad1218a Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Sun, 26 Apr 2020 17:18:59 +0300 Subject: [PATCH 07/10] Add test for inputs with multiple mixed valid/invalid values --- Lib/test/test_statistics.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 1b28248398416d..9f7cbe8752c995 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1525,6 +1525,16 @@ def test_single_value_unsupported_type(self): with self.assertRaisesRegex(TypeError, '^unsupported type$'): self.func(['3.14']) + def test_multiple_values_type_error(self): + # Test TypeError is raised when given multiple (valid/invalid) values + for data in [ + ['1', '2', '3'], # only strings + [1, '2', 3, '4', 5], # mixed strings and valid integers + [2.3, 3.4, 4.5, '5.6'] # only one string and valid floats + ]: + with self.assertRaises(TypeError): + self.func(data) + def test_ints(self): # Test harmonic mean with ints. data = [2, 4, 4, 8, 16, 16] From 1a5c2dff0c98ca23ca3b16ea45e28c6c0fbcd0da Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Sun, 26 Apr 2020 18:21:17 +0300 Subject: [PATCH 08/10] Rename tests and remove comments as requested in review --- Lib/test/test_statistics.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 9f7cbe8752c995..f675adfb2ed6cc 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1004,7 +1004,7 @@ def test_nan(self): x = statistics._convert(nan, type(nan)) self.assertTrue(_nan_equal(x, nan)) - def test_raise_type_error(self): + def test_invalid_input_type(self): with self.assertRaises(TypeError): statistics._convert(None, float) @@ -1040,11 +1040,11 @@ def test_error_msg(self): class FindLteqTest(unittest.TestCase): # Test _find_lteq private function. - def test_raise_value_error(self): + def test_invalid_input_values(self): for a, x in [ - ([], 1), # empty a triggers `i != len(a)` - ([1, 2], 3), # non-existing max value triggers `i != len(a)` - ([1, 3], 2) # non-existing value triggers `a[i] == x` + ([], 1), + ([1, 2], 3), + ([1, 3], 2) ]: with self.assertRaises(ValueError): statistics._find_lteq(a, x) @@ -1063,8 +1063,8 @@ class FindRteqTest(unittest.TestCase): def test_raise_value_error(self): for a, l, x in [ - ([1], 2, 1), # when l=len(a)+1 triggers `i != (len(a)+1)` - ([1, 3], 0, 2) # non-existing value triggers `a[i-1] == x` + ([1], 2, 1), + ([1, 3], 0, 2) ]: with self.assertRaises(ValueError): statistics._find_rteq(a, l, x) @@ -1522,7 +1522,7 @@ def test_negative_error(self): self.assertRaises(exc, self.func, values) def test_single_value_unsupported_type(self): - with self.assertRaisesRegex(TypeError, '^unsupported type$'): + with self.assertRaises(TypeError): self.func(['3.14']) def test_multiple_values_type_error(self): From ac8b586efb20bf0fd52585b1f6fe6f3f773dcf01 Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Mon, 27 Apr 2020 00:51:03 +0300 Subject: [PATCH 09/10] Combine two unit tests into one --- Lib/test/test_statistics.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index f675adfb2ed6cc..5e102814292f6a 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1061,7 +1061,7 @@ def test_locate_successfully(self): class FindRteqTest(unittest.TestCase): # Test _find_rteq private function. - def test_raise_value_error(self): + def test_invalid_input_values(self): for a, l, x in [ ([1], 2, 1), ([1, 3], 0, 2) @@ -1521,14 +1521,11 @@ def test_negative_error(self): with self.subTest(values=values): self.assertRaises(exc, self.func, values) - def test_single_value_unsupported_type(self): - with self.assertRaises(TypeError): - self.func(['3.14']) - - def test_multiple_values_type_error(self): - # Test TypeError is raised when given multiple (valid/invalid) values + def test_invalid_type_error(self): + # Test error is raised when input contains invalid type(s) for data in [ - ['1', '2', '3'], # only strings + ['3.14'], # single string + ['1', '2', '3'], # multiple strings [1, '2', 3, '4', 5], # mixed strings and valid integers [2.3, 3.4, 4.5, '5.6'] # only one string and valid floats ]: From ee55b16b614315ebae69a4081ea06897a4a546ff Mon Sep 17 00:00:00 2001 From: Tzanetos Balitsaris Date: Mon, 27 Apr 2020 19:11:31 +0300 Subject: [PATCH 10/10] Use subTest in tests with 3 or more cases --- Lib/test/test_statistics.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index 5e102814292f6a..5c3b1fdd8b110d 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -1046,8 +1046,9 @@ def test_invalid_input_values(self): ([1, 2], 3), ([1, 3], 2) ]: - with self.assertRaises(ValueError): - statistics._find_lteq(a, x) + with self.subTest(a=a, x=x): + with self.assertRaises(ValueError): + statistics._find_lteq(a, x) def test_locate_successfully(self): for a, x, expected_i in [ @@ -1055,7 +1056,8 @@ def test_locate_successfully(self): ([0, 1, 1, 1, 2, 3], 1, 1), ([1, 2, 3, 3, 3], 3, 2) ]: - self.assertEqual(expected_i, statistics._find_lteq(a, x)) + with self.subTest(a=a, x=x): + self.assertEqual(expected_i, statistics._find_lteq(a, x)) class FindRteqTest(unittest.TestCase): @@ -1075,7 +1077,8 @@ def test_locate_successfully(self): ([0, 1, 1, 1, 2, 3], 0, 1, 3), ([1, 2, 3, 3, 3], 0, 3, 4) ]: - self.assertEqual(expected_i, statistics._find_rteq(a, l, x)) + with self.subTest(a=a, l=l, x=x): + self.assertEqual(expected_i, statistics._find_rteq(a, l, x)) # === Tests for public functions === @@ -1529,8 +1532,9 @@ def test_invalid_type_error(self): [1, '2', 3, '4', 5], # mixed strings and valid integers [2.3, 3.4, 4.5, '5.6'] # only one string and valid floats ]: - with self.assertRaises(TypeError): - self.func(data) + with self.subTest(data=data): + with self.assertRaises(TypeError): + self.func(data) def test_ints(self): # Test harmonic mean with ints.