From ad4238f183efad2e327ab7349f363efcaf3a2fb4 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 11:46:17 +0000 Subject: [PATCH 01/16] #246: Started working on simple example for demonstrating how to use cosmic-ray to improve the testing suite --- examples/adam/adam.py | 129 +++++++++++++++++++++++ examples/adam/adam.rst | 26 +++++ examples/adam/cosmic-ray-bad_tests.conf | 13 +++ examples/adam/cosmic-ray-good_tests.conf | 13 +++ examples/adam/test_adam_bad_tests.py | 0 examples/adam/test_adam_good_tests.py | 0 6 files changed, 181 insertions(+) create mode 100644 examples/adam/adam.py create mode 100644 examples/adam/adam.rst create mode 100644 examples/adam/cosmic-ray-bad_tests.conf create mode 100644 examples/adam/cosmic-ray-good_tests.conf create mode 100644 examples/adam/test_adam_bad_tests.py create mode 100644 examples/adam/test_adam_good_tests.py diff --git a/examples/adam/adam.py b/examples/adam/adam.py new file mode 100644 index 00000000..4baa758d --- /dev/null +++ b/examples/adam/adam.py @@ -0,0 +1,129 @@ +# A set of function which exercise specific mutation operators. This +# is paired up with a test suite. The idea is that cosmic-ray should +# kill every mutant when that suite is run; if it doesn't, then we've +# got a problem. + +import operator + + +def constant_number(): + return 42 + + +def constant_true(): + return True + + +def constant_false(): + return False + + +def bool_and(): + return object() and None + + +def bool_or(): + return object() or None + + +def bool_expr_with_not(): + return not object() + + +def bool_if(): + if object(): + return True + + raise Exception('bool_if() failed') + + +def if_expression(): + return True if object() else None + + +def assert_in_func(): + assert object() + return True + + +def unary_sub(): + return -1 + + +def unary_add(): + return +1 + + +def binary_add(): + return 5 + 6 + +def equals(vals): + def constraint(x, y): + return operator.xor(x == y, x != y) + + return all([constraint(x, y) + for x in vals + for y in vals]) + + +def use_break(limit): + for x in range(limit): + break + return x + + +def use_continue(limit): + for x in range(limit): + continue + return x + + +def trigger_infinite_loop(): + result = None + # When `break` becomes `continue`, this should enter an infinite loop. This + # helps us test timeouts. + # Any object which isn't None passes the truth value testing so here + # we use `while object()` instead of `while True` b/c the later becomes + # `while False` when ReplaceTrueFalse is applied and we don't trigger an + # infinite loop. + while object(): + result = object() + break + + # when `while object()` becomes `while not object()` + # the code below will be triggered + return result + + +def single_iteration(): + result = None + iter = [object()] + + for i in iter: + result = True + + return result + + +def handle_exception(): + result = None + try: + raise IOError + except IOError: + result = True + + return result + + +def decorator(func): + func.cosmic_ray = True + return func + + +@decorator +def decorated_func(): + result = None + if decorated_func.cosmic_ray: + result = True + + return result diff --git a/examples/adam/adam.rst b/examples/adam/adam.rst new file mode 100644 index 00000000..26d64b6e --- /dev/null +++ b/examples/adam/adam.rst @@ -0,0 +1,26 @@ +Cosmic Ray example: Adam +------------------------ + +This example demonstrates how to use cosmic-ray to improve the testing suite +for a simple module called `adam`. + +First run cosmic-ray on the so called 'bad' testing suite. + +:: + + cosmic-ray init cosmic-ray-bad_tests.conf bad_session + cosmic-ray --verbose exec bad_session + cosmic-ray dump bad_session | cr-report + + +You should end up with a series of mutants that have survived. This is because in +`test_adam_bad_tests.py` there are not enough tests to cover `adam.py`. + +We add a couple of tests in `test_adam_good_tests.py` to ensure full coverage. Run +cosmic-ray again on the new testing suite. + +:: + + cosmic-ray init cosmic-ray-good_tests.conf good_session + cosmic-ray --verbose exec good_session + cosmic-ray dump good_session | cr-report diff --git a/examples/adam/cosmic-ray-bad_tests.conf b/examples/adam/cosmic-ray-bad_tests.conf new file mode 100644 index 00000000..907d447f --- /dev/null +++ b/examples/adam/cosmic-ray-bad_tests.conf @@ -0,0 +1,13 @@ +# Run the adam tests with unittest +module: adam + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_adam_bad_tests + +execution-engine: + name: local diff --git a/examples/adam/cosmic-ray-good_tests.conf b/examples/adam/cosmic-ray-good_tests.conf new file mode 100644 index 00000000..3881fe9f --- /dev/null +++ b/examples/adam/cosmic-ray-good_tests.conf @@ -0,0 +1,13 @@ +# Run the adam tests with unittest +module: adam + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_adam_good_tests + +execution-engine: + name: local diff --git a/examples/adam/test_adam_bad_tests.py b/examples/adam/test_adam_bad_tests.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/adam/test_adam_good_tests.py b/examples/adam/test_adam_good_tests.py new file mode 100644 index 00000000..e69de29b From d9c57d15451065241342352aca1f3ac0ee1fa4dc Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 12:39:19 +0000 Subject: [PATCH 02/16] #246: Added docs for using simple example 'adam' in toctree --- docs/examples.rst | 4 ++++ docs/index.rst | 1 + examples/adam/adam.rst | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 docs/examples.rst diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 00000000..25240864 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,4 @@ +Examples +======== + +.. include:: ../examples/adam/adam.rst \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index d6aafbc1..45c1d8e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ And, of course, patches and ideas are welcome. distributed implementation tests + examples diff --git a/examples/adam/adam.rst b/examples/adam/adam.rst index 26d64b6e..13547ca8 100644 --- a/examples/adam/adam.rst +++ b/examples/adam/adam.rst @@ -1,8 +1,8 @@ -Cosmic Ray example: Adam ------------------------- +Improving the testing suite for Adam +------------------------------------ This example demonstrates how to use cosmic-ray to improve the testing suite -for a simple module called `adam`. +for a simple module called ``adam``. First run cosmic-ray on the so called 'bad' testing suite. @@ -14,9 +14,9 @@ First run cosmic-ray on the so called 'bad' testing suite. You should end up with a series of mutants that have survived. This is because in -`test_adam_bad_tests.py` there are not enough tests to cover `adam.py`. +``test_adam_bad_tests.py`` there are not enough tests to cover ``adam.py``. -We add a couple of tests in `test_adam_good_tests.py` to ensure full coverage. Run +We add a couple of tests in ``test_adam_good_tests.py`` to ensure full coverage. Run cosmic-ray again on the new testing suite. :: From d358c1f46a4c067beb7139a3534667c174478f9e Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 12:40:13 +0000 Subject: [PATCH 03/16] Added .idea to gitignore and exluded directories in conf.py --- .gitignore | 1 + docs/conf.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 47a237fc..5db71a41 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ test_project/*.json .cache .hypothesis .csearchindex +.idea diff --git a/docs/conf.py b/docs/conf.py index 554319f0..8a4c8244 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.idea'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' From da7d80a599b3f652577e76832860a439d5bf4484 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 13:39:41 +0000 Subject: [PATCH 04/16] #246: Added a link to examples/adam in quickstart docs --- docs/examples.rst | 2 +- docs/quickstart.rst | 2 ++ examples/adam/adam.rst | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/examples.rst b/docs/examples.rst index 25240864..76d4e05f 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,4 +1,4 @@ Examples ======== -.. include:: ../examples/adam/adam.rst \ No newline at end of file +.. include:: ../examples/adam/adam.rst diff --git a/docs/quickstart.rst b/docs/quickstart.rst index da3344f1..74bf0258 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -85,3 +85,5 @@ Ray, you can run these tests, too, like this: In this case we're passing the ``--verbose`` flag to the ``exec`` command so that you can see what Cosmic Ray is doing. If everything goes as expected, the ``cr-report`` command will report a 0% survival rate. + +For an example which has a non-zero survival rate, see :ref:`examples-adam` diff --git a/examples/adam/adam.rst b/examples/adam/adam.rst index 13547ca8..0ffe0c37 100644 --- a/examples/adam/adam.rst +++ b/examples/adam/adam.rst @@ -1,5 +1,7 @@ -Improving the testing suite for Adam ------------------------------------- +.. _examples-adam: + +Improving the tests for a simple module +--------------------------------------- This example demonstrates how to use cosmic-ray to improve the testing suite for a simple module called ``adam``. From dae0b3c88ac1ca5a690c5f6c0384155b3f3f110e Mon Sep 17 00:00:00 2001 From: Walter Date: Mon, 30 Oct 2017 11:04:27 +0000 Subject: [PATCH 05/16] First commit of ten-pin bowling game example for test run. --- .../score_calculator.py | 16 ++++++++++++++++ .../test_score_calculator.py | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 examples/bowling_game_score_calculator/score_calculator.py create mode 100644 examples/bowling_game_score_calculator/test_score_calculator.py diff --git a/examples/bowling_game_score_calculator/score_calculator.py b/examples/bowling_game_score_calculator/score_calculator.py new file mode 100644 index 00000000..9aa096a1 --- /dev/null +++ b/examples/bowling_game_score_calculator/score_calculator.py @@ -0,0 +1,16 @@ +""" +This is a simple class to demonstrate the cosmic-ray library. +The BowlingGame class keeps and calculates the score of a ten-pin bowling +game for one player. +""" + +class BowlingGame(): + def __init__(self): + self.score_count = 0 + + def score(self): + return self.score_count + + def roll(self, first_roll, second_roll): + roll_result = first_roll + second_roll + self.score_count += roll_result diff --git a/examples/bowling_game_score_calculator/test_score_calculator.py b/examples/bowling_game_score_calculator/test_score_calculator.py new file mode 100644 index 00000000..dbbaa820 --- /dev/null +++ b/examples/bowling_game_score_calculator/test_score_calculator.py @@ -0,0 +1,18 @@ +import unittest + +from score_calculator import BowlingGame + + +class ScoreCalculatorTest(unittest.TestCase): + def setUp(self): + self.game = BowlingGame() + + def test_create_bowling_game(self): + self.assertIsInstance(self.game, BowlingGame) + + def test_the_score_of_a_new_game_is_zero(self): + self.assertEqual(self.game.score(), 0) + + def test_the_count_of_a_roll_can_be_added_to_the_score(self): + self.game.roll(2, 3) + self.assertEqual(self.game.score(), 5) From f04ba1989369c5ba6a9fff7b76128c5735a77902 Mon Sep 17 00:00:00 2001 From: Walter Date: Mon, 30 Oct 2017 12:16:17 +0000 Subject: [PATCH 06/16] Implemented strike and spare scroing. Plus tests. --- .../score_calculator.py | 36 ++++++++++++++- .../test_score_calculator.py | 46 ++++++++++++++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/examples/bowling_game_score_calculator/score_calculator.py b/examples/bowling_game_score_calculator/score_calculator.py index 9aa096a1..7442d967 100644 --- a/examples/bowling_game_score_calculator/score_calculator.py +++ b/examples/bowling_game_score_calculator/score_calculator.py @@ -2,15 +2,47 @@ This is a simple class to demonstrate the cosmic-ray library. The BowlingGame class keeps and calculates the score of a ten-pin bowling game for one player. +The traditional bowling scoring is used: +https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring """ + +ALL_PINS = 10 + class BowlingGame(): def __init__(self): self.score_count = 0 + self.spare = False + self.strike = False def score(self): return self.score_count def roll(self, first_roll, second_roll): - roll_result = first_roll + second_roll - self.score_count += roll_result + frame_result = first_roll + second_roll + self._handle_spare_and_strikes(first_roll, frame_result) + self.score_count += frame_result + + def _handle_spare_and_strikes(self, first_roll, frame_result): + self._award_previous_spare_count(first_roll) + self._award_previous_strike_count(frame_result) + self._check_for_strike(first_roll) + self._check_for_spare(frame_result) + + def _award_previous_spare_count(self, first_roll): + if self.spare == True: + self.score_count += first_roll + self.spare = False + + def _award_previous_strike_count(self, frame_result): + if self.strike == True: + self.score_count += frame_result + self.strike = False + + def _check_for_strike(self, first_roll): + if first_roll == ALL_PINS: + self.strike = True + + def _check_for_spare(self, frame_result): + if frame_result == ALL_PINS and not self.strike: + self.spare = True diff --git a/examples/bowling_game_score_calculator/test_score_calculator.py b/examples/bowling_game_score_calculator/test_score_calculator.py index dbbaa820..ca9089f0 100644 --- a/examples/bowling_game_score_calculator/test_score_calculator.py +++ b/examples/bowling_game_score_calculator/test_score_calculator.py @@ -13,6 +13,50 @@ def test_create_bowling_game(self): def test_the_score_of_a_new_game_is_zero(self): self.assertEqual(self.game.score(), 0) - def test_the_count_of_a_roll_can_be_added_to_the_score(self): + def test_the_count_of_the_first_frame_is_added_to_the_score(self): self.game.roll(2, 3) self.assertEqual(self.game.score(), 5) + + def test_multiple_frame_results_are_kept_in_the_score(self): + self.game.roll(2, 4) + self.game.roll(6, 2) + self.assertEqual(self.game.score(), 14) + + def test_spares_are_detected_for_the_next_frame(self): + self.game.roll(6, 4) + self.assertTrue(self.game.spare) + + def test_previous_spare_results_in_that_next_roll_points_are_doubled(self): + self.game.roll(6, 4) + self.game.roll(5, 3) + self.assertEqual(self.game.score(), 23) + + def test_double_spares_are_counted_correctly(self): + self.game.roll(6, 4) + self.game.roll(5, 5) + self.game.roll(8, 0) + self.assertEqual(self.game.score(), 41) + + def test_the_spare_flag_is_removed_in_the_next_frame(self): + self.game.roll(6, 4) + self.game.roll(1, 1) + self.game.roll(2, 0) + self.assertEqual(self.game.score(), 15) + + def test_a_strike_is_detected_and_no_spare_flag_is_set(self): + self.game.roll(10, 0) + self.assertTrue(self.game.strike) + self.assertFalse(self.game.spare) + + def test_previous_strike_doubles_the_next_frame_pin_count(self): + self.game.roll(10, 0) + self.game.roll(5, 4) + self.assertEqual(self.game.score(), 28) + + def test_the_strike_flag_is_removed_in_the_next_frame(self): + self.game.roll(10, 0) + self.game.roll(1, 1) + self.game.roll(2, 5) + self.assertEqual(self.game.score(), 21) + + # case with strike after spare or the way around From 70bb9abdacd0c674291c7408dc9c28d029f1adf1 Mon Sep 17 00:00:00 2001 From: Walter Date: Mon, 30 Oct 2017 13:52:15 +0000 Subject: [PATCH 07/16] config.yml file cosmic-ray run. --- examples/bowling_game_score_calculator/config.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 examples/bowling_game_score_calculator/config.yml diff --git a/examples/bowling_game_score_calculator/config.yml b/examples/bowling_game_score_calculator/config.yml new file mode 100644 index 00000000..afab72f0 --- /dev/null +++ b/examples/bowling_game_score_calculator/config.yml @@ -0,0 +1,13 @@ +# Run the adam tests with unittest +module: score_calculator + +baseline: 10 + +exclude-modules: + +test-runner: + name: unittest + args: test_score_calculator + +execution-engine: + name: local From 6585316769e7f2cdbc2974d1f6794db2e8a8f501 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 14:14:47 +0000 Subject: [PATCH 08/16] #246: renamed adam in examples to simple_math --- docs/examples.rst | 2 +- docs/quickstart.rst | 2 +- examples/adam/adam.py | 129 ------------------ .../cosmic-ray-bad_tests.conf | 6 +- .../cosmic-ray-good_tests.conf | 6 +- examples/simple_math/simple_math.py | 26 ++++ .../adam.rst => simple_math/simple_math.rst} | 28 +++- .../test_simple_math_bad.py} | 0 .../test_simple_math_good.py} | 0 9 files changed, 56 insertions(+), 143 deletions(-) delete mode 100644 examples/adam/adam.py rename examples/{adam => simple_math}/cosmic-ray-bad_tests.conf (50%) rename examples/{adam => simple_math}/cosmic-ray-good_tests.conf (50%) create mode 100644 examples/simple_math/simple_math.py rename examples/{adam/adam.rst => simple_math/simple_math.rst} (54%) rename examples/{adam/test_adam_bad_tests.py => simple_math/test_simple_math_bad.py} (100%) rename examples/{adam/test_adam_good_tests.py => simple_math/test_simple_math_good.py} (100%) diff --git a/docs/examples.rst b/docs/examples.rst index 76d4e05f..3f6de7a5 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,4 +1,4 @@ Examples ======== -.. include:: ../examples/adam/adam.rst +.. include:: ../examples/simple_math/simple_math.rst diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 74bf0258..fe5e74fc 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -86,4 +86,4 @@ In this case we're passing the ``--verbose`` flag to the ``exec`` command so that you can see what Cosmic Ray is doing. If everything goes as expected, the ``cr-report`` command will report a 0% survival rate. -For an example which has a non-zero survival rate, see :ref:`examples-adam` +For a simple example which has a non-zero survival rate, see :ref:`examples-simple_math` diff --git a/examples/adam/adam.py b/examples/adam/adam.py deleted file mode 100644 index 4baa758d..00000000 --- a/examples/adam/adam.py +++ /dev/null @@ -1,129 +0,0 @@ -# A set of function which exercise specific mutation operators. This -# is paired up with a test suite. The idea is that cosmic-ray should -# kill every mutant when that suite is run; if it doesn't, then we've -# got a problem. - -import operator - - -def constant_number(): - return 42 - - -def constant_true(): - return True - - -def constant_false(): - return False - - -def bool_and(): - return object() and None - - -def bool_or(): - return object() or None - - -def bool_expr_with_not(): - return not object() - - -def bool_if(): - if object(): - return True - - raise Exception('bool_if() failed') - - -def if_expression(): - return True if object() else None - - -def assert_in_func(): - assert object() - return True - - -def unary_sub(): - return -1 - - -def unary_add(): - return +1 - - -def binary_add(): - return 5 + 6 - -def equals(vals): - def constraint(x, y): - return operator.xor(x == y, x != y) - - return all([constraint(x, y) - for x in vals - for y in vals]) - - -def use_break(limit): - for x in range(limit): - break - return x - - -def use_continue(limit): - for x in range(limit): - continue - return x - - -def trigger_infinite_loop(): - result = None - # When `break` becomes `continue`, this should enter an infinite loop. This - # helps us test timeouts. - # Any object which isn't None passes the truth value testing so here - # we use `while object()` instead of `while True` b/c the later becomes - # `while False` when ReplaceTrueFalse is applied and we don't trigger an - # infinite loop. - while object(): - result = object() - break - - # when `while object()` becomes `while not object()` - # the code below will be triggered - return result - - -def single_iteration(): - result = None - iter = [object()] - - for i in iter: - result = True - - return result - - -def handle_exception(): - result = None - try: - raise IOError - except IOError: - result = True - - return result - - -def decorator(func): - func.cosmic_ray = True - return func - - -@decorator -def decorated_func(): - result = None - if decorated_func.cosmic_ray: - result = True - - return result diff --git a/examples/adam/cosmic-ray-bad_tests.conf b/examples/simple_math/cosmic-ray-bad_tests.conf similarity index 50% rename from examples/adam/cosmic-ray-bad_tests.conf rename to examples/simple_math/cosmic-ray-bad_tests.conf index 907d447f..8c8aaf99 100644 --- a/examples/adam/cosmic-ray-bad_tests.conf +++ b/examples/simple_math/cosmic-ray-bad_tests.conf @@ -1,5 +1,5 @@ -# Run the adam tests with unittest -module: adam +# Run the simple_math tests with pytest +module: simple_math baseline: 10 @@ -7,7 +7,7 @@ exclude-modules: test-runner: name: pytest - args: -x test_adam_bad_tests + args: -x test_simple_math_bad execution-engine: name: local diff --git a/examples/adam/cosmic-ray-good_tests.conf b/examples/simple_math/cosmic-ray-good_tests.conf similarity index 50% rename from examples/adam/cosmic-ray-good_tests.conf rename to examples/simple_math/cosmic-ray-good_tests.conf index 3881fe9f..fb9cb823 100644 --- a/examples/adam/cosmic-ray-good_tests.conf +++ b/examples/simple_math/cosmic-ray-good_tests.conf @@ -1,5 +1,5 @@ -# Run the adam tests with unittest -module: adam +# Run the simple_math tests with pytest +module: simple_math baseline: 10 @@ -7,7 +7,7 @@ exclude-modules: test-runner: name: pytest - args: -x test_adam_good_tests + args: -x test_simple_math_good execution-engine: name: local diff --git a/examples/simple_math/simple_math.py b/examples/simple_math/simple_math.py new file mode 100644 index 00000000..b1df2945 --- /dev/null +++ b/examples/simple_math/simple_math.py @@ -0,0 +1,26 @@ +""" +----------- +Simple Math +----------- + +A set of simple math functions. +This is paired up with a test suite and intended to be run with cosmic-ray. +The idea is that cosmic-ray should kill every mutant when that suite is run; +if it doesn't, then we've got a problem. +""" + + +def mult_by_2(x): + return x + x + + +def square(x): + return x*x + + +def cube(x): + return x*x*x + + +def is_positive(x): + return x > 0 diff --git a/examples/adam/adam.rst b/examples/simple_math/simple_math.rst similarity index 54% rename from examples/adam/adam.rst rename to examples/simple_math/simple_math.rst index 0ffe0c37..e31d7a8f 100644 --- a/examples/adam/adam.rst +++ b/examples/simple_math/simple_math.rst @@ -1,10 +1,24 @@ -.. _examples-adam: +.. _examples-simple_math: Improving the tests for a simple module --------------------------------------- -This example demonstrates how to use cosmic-ray to improve the testing suite -for a simple module called ``adam``. +This example demonstrates how to use cosmic-ray to improve the testing +suite for a module called ``simple_math``. + +:: + def mult_by_2(x): + return x + x + + def square(x): + return x*x + + def cube(x): + return x*x*x + + def is_positive(x): + return x > 0 + First run cosmic-ray on the so called 'bad' testing suite. @@ -14,11 +28,10 @@ First run cosmic-ray on the so called 'bad' testing suite. cosmic-ray --verbose exec bad_session cosmic-ray dump bad_session | cr-report - You should end up with a series of mutants that have survived. This is because in -``test_adam_bad_tests.py`` there are not enough tests to cover ``adam.py``. +``test_simple_math_bad.py`` there are not enough tests to cover ``simple_math.py``. -We add a couple of tests in ``test_adam_good_tests.py`` to ensure full coverage. Run +We add a couple of tests in ``test_simple_good_tests.py`` to ensure full coverage. Run cosmic-ray again on the new testing suite. :: @@ -26,3 +39,6 @@ cosmic-ray again on the new testing suite. cosmic-ray init cosmic-ray-good_tests.conf good_session cosmic-ray --verbose exec good_session cosmic-ray dump good_session | cr-report + +You should now get 0% survival rate for the mutants (yay!). This means that you +have a robust testing suite. diff --git a/examples/adam/test_adam_bad_tests.py b/examples/simple_math/test_simple_math_bad.py similarity index 100% rename from examples/adam/test_adam_bad_tests.py rename to examples/simple_math/test_simple_math_bad.py diff --git a/examples/adam/test_adam_good_tests.py b/examples/simple_math/test_simple_math_good.py similarity index 100% rename from examples/adam/test_adam_good_tests.py rename to examples/simple_math/test_simple_math_good.py From 27da967109b1d635d02fdf15990b68f4efcc6334 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 14:31:07 +0000 Subject: [PATCH 09/16] #246: Filled in the unit tests for simple_math.py --- examples/simple_math/__init__.py | 0 examples/simple_math/test_simple_math_bad.py | 9 +++++++ examples/simple_math/test_simple_math_good.py | 26 +++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 examples/simple_math/__init__.py diff --git a/examples/simple_math/__init__.py b/examples/simple_math/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/simple_math/test_simple_math_bad.py b/examples/simple_math/test_simple_math_bad.py index e69de29b..e8dab1d5 100644 --- a/examples/simple_math/test_simple_math_bad.py +++ b/examples/simple_math/test_simple_math_bad.py @@ -0,0 +1,9 @@ +from .simple_math import square, cube + + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 diff --git a/examples/simple_math/test_simple_math_good.py b/examples/simple_math/test_simple_math_good.py index e69de29b..ab17a9c8 100644 --- a/examples/simple_math/test_simple_math_good.py +++ b/examples/simple_math/test_simple_math_good.py @@ -0,0 +1,26 @@ +from .simple_math import square, cube, mult_by_2, is_positive + + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 + + +def test_mult_by_2(): + assert mult_by_2(2) == 4 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) + From 05f0fb67da665cb33044a09537de43839933c8ef Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 15:05:52 +0000 Subject: [PATCH 10/16] #246: Fixed bugs with cosmic-ray-bad_tests.conf and relative imports --- examples/simple_math/__init__.py | 0 examples/simple_math/cosmic-ray-bad_tests.conf | 2 +- examples/simple_math/test_simple_math_bad.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 examples/simple_math/__init__.py diff --git a/examples/simple_math/__init__.py b/examples/simple_math/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/simple_math/cosmic-ray-bad_tests.conf b/examples/simple_math/cosmic-ray-bad_tests.conf index 8c8aaf99..c1bfff41 100644 --- a/examples/simple_math/cosmic-ray-bad_tests.conf +++ b/examples/simple_math/cosmic-ray-bad_tests.conf @@ -7,7 +7,7 @@ exclude-modules: test-runner: name: pytest - args: -x test_simple_math_bad + args: -x test_simple_math_bad.py execution-engine: name: local diff --git a/examples/simple_math/test_simple_math_bad.py b/examples/simple_math/test_simple_math_bad.py index e8dab1d5..c8a32082 100644 --- a/examples/simple_math/test_simple_math_bad.py +++ b/examples/simple_math/test_simple_math_bad.py @@ -1,4 +1,4 @@ -from .simple_math import square, cube +from simple_math import square, cube def test_square(): From bf4bfc2ac51870e0b975973b2fa933a4ab2bfd39 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 15:40:28 +0000 Subject: [PATCH 11/16] #246: Fixed problems with the simple_math example and updated the documentation --- docs/quickstart.rst | 3 ++- examples/simple_math/.gitignore | 1 + .../simple_math/cosmic-ray-good_tests.conf | 2 +- examples/simple_math/simple_math.rst | 27 ++++++++++++++----- examples/simple_math/test_simple_math_bad.py | 19 ++++++++++++- examples/simple_math/test_simple_math_good.py | 8 +++--- 6 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 examples/simple_math/.gitignore diff --git a/docs/quickstart.rst b/docs/quickstart.rst index fe5e74fc..84fe6a83 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -86,4 +86,5 @@ In this case we're passing the ``--verbose`` flag to the ``exec`` command so that you can see what Cosmic Ray is doing. If everything goes as expected, the ``cr-report`` command will report a 0% survival rate. -For a simple example which has a non-zero survival rate, see :ref:`examples-simple_math` +See :ref:`examples-simple_math` for a step-by-step guide for +dealing with tests that have a non-zero mutation survival rate. diff --git a/examples/simple_math/.gitignore b/examples/simple_math/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/examples/simple_math/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/examples/simple_math/cosmic-ray-good_tests.conf b/examples/simple_math/cosmic-ray-good_tests.conf index fb9cb823..82044b5e 100644 --- a/examples/simple_math/cosmic-ray-good_tests.conf +++ b/examples/simple_math/cosmic-ray-good_tests.conf @@ -7,7 +7,7 @@ exclude-modules: test-runner: name: pytest - args: -x test_simple_math_good + args: -x test_simple_math_good.py execution-engine: name: local diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst index e31d7a8f..6565bc5e 100644 --- a/examples/simple_math/simple_math.rst +++ b/examples/simple_math/simple_math.rst @@ -7,6 +7,9 @@ This example demonstrates how to use cosmic-ray to improve the testing suite for a module called ``simple_math``. :: + + # examples/simple_math/simple_math.py + def mult_by_2(x): return x + x @@ -28,11 +31,23 @@ First run cosmic-ray on the so called 'bad' testing suite. cosmic-ray --verbose exec bad_session cosmic-ray dump bad_session | cr-report -You should end up with a series of mutants that have survived. This is because in -``test_simple_math_bad.py`` there are not enough tests to cover ``simple_math.py``. +You should end up with at least one mutant that survives. This is because +``test_mult_by_2`` from ``test_simple_math_bad.py`` still passes when we replace +``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, 4, when 'x' +is 2. + +To fix this bad test script, we modify ``test_mult_by_2`` so that it checks the +answer for a range of values of x. + +:: + + @pytest.mark.parametrize('x', range(-5, 5)) + def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 -We add a couple of tests in ``test_simple_good_tests.py`` to ensure full coverage. Run -cosmic-ray again on the new testing suite. +Now this test should fail for all the mutations to the underlying +function ``mult_by_2``, which is what we want it to do. +Run cosmic-ray again on the new testing suite, ``test_simple_math_good.py`` :: @@ -40,5 +55,5 @@ cosmic-ray again on the new testing suite. cosmic-ray --verbose exec good_session cosmic-ray dump good_session | cr-report -You should now get 0% survival rate for the mutants (yay!). This means that you -have a robust testing suite. +You should now get 0% survival rate for the mutants (yay!). This means your +testing suite is now more robust. diff --git a/examples/simple_math/test_simple_math_bad.py b/examples/simple_math/test_simple_math_bad.py index c8a32082..55a87d96 100644 --- a/examples/simple_math/test_simple_math_bad.py +++ b/examples/simple_math/test_simple_math_bad.py @@ -1,4 +1,4 @@ -from simple_math import square, cube +from simple_math import square, cube, mult_by_2, is_positive def test_square(): @@ -7,3 +7,20 @@ def test_square(): def test_cube(): assert cube(2) == 8 + + +def test_mult_by_2(): + assert mult_by_2(2) == 4 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) + diff --git a/examples/simple_math/test_simple_math_good.py b/examples/simple_math/test_simple_math_good.py index ab17a9c8..e9e954ee 100644 --- a/examples/simple_math/test_simple_math_good.py +++ b/examples/simple_math/test_simple_math_good.py @@ -1,5 +1,6 @@ -from .simple_math import square, cube, mult_by_2, is_positive +from simple_math import square, cube, mult_by_2, is_positive +import pytest def test_square(): assert square(3) == 9 @@ -9,8 +10,9 @@ def test_cube(): assert cube(2) == 8 -def test_mult_by_2(): - assert mult_by_2(2) == 4 +@pytest.mark.parametrize('x', range(-5, 5)) +def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 def test_is_positive_for_positive_numbers(): From eb7bc85d882e7a11d6b64b795fcacfb303496e4f Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 15:50:11 +0000 Subject: [PATCH 12/16] #246: Improved the documentation for the simple_math example --- examples/simple_math/simple_math.rst | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst index 6565bc5e..1b6fcf6d 100644 --- a/examples/simple_math/simple_math.rst +++ b/examples/simple_math/simple_math.rst @@ -33,11 +33,18 @@ First run cosmic-ray on the so called 'bad' testing suite. You should end up with at least one mutant that survives. This is because ``test_mult_by_2`` from ``test_simple_math_bad.py`` still passes when we replace -``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, 4, when 'x' -is 2. +``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, ``4``, +when ``x = 2``. -To fix this bad test script, we modify ``test_mult_by_2`` so that it checks the -answer for a range of values of x. +Here is the bad test that lets the mutant(s) survive: + +:: + + def test_mult_by_2(): + assert mult_by_2(2) == 4 + +To fix this bad test, we decorate it so that a range +of values of x are tested: :: From 0ed993812a2e556f598273bc05f422eaf3aec996 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Sun, 5 Nov 2017 17:33:57 +0000 Subject: [PATCH 13/16] Improved the documentation for simple_math --- examples/simple_math/simple_math.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst index 1b6fcf6d..0f3bf0b8 100644 --- a/examples/simple_math/simple_math.rst +++ b/examples/simple_math/simple_math.rst @@ -4,7 +4,8 @@ Improving the tests for a simple module --------------------------------------- This example demonstrates how to use cosmic-ray to improve the testing -suite for a module called ``simple_math``. +suite for a module called ``simple_math``. The code is located in the +``examples/simple_math`` directory. :: @@ -23,7 +24,9 @@ suite for a module called ``simple_math``. return x > 0 -First run cosmic-ray on the so called 'bad' testing suite. +We would like to measure the performance of a testing suite, +``test_simple_math_bad.py``, with intention to improve it. +First run cosmic-ray on the so-called 'bad' testing suite. :: @@ -31,7 +34,7 @@ First run cosmic-ray on the so called 'bad' testing suite. cosmic-ray --verbose exec bad_session cosmic-ray dump bad_session | cr-report -You should end up with at least one mutant that survives. This is because +You should end up with at least one mutant that survives. This is because the test ``test_mult_by_2`` from ``test_simple_math_bad.py`` still passes when we replace ``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, ``4``, when ``x = 2``. @@ -62,5 +65,5 @@ Run cosmic-ray again on the new testing suite, ``test_simple_math_good.py`` cosmic-ray --verbose exec good_session cosmic-ray dump good_session | cr-report -You should now get 0% survival rate for the mutants (yay!). This means your +You should now get 0% survival rate for the mutants. This means your testing suite is now more robust. From 45d7bcaa9d476eb53c364e1b2c9534c691e25088 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Sun, 5 Nov 2017 17:39:15 +0000 Subject: [PATCH 14/16] More edits to the documentation --- examples/simple_math/simple_math.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst index 0f3bf0b8..b547d269 100644 --- a/examples/simple_math/simple_math.rst +++ b/examples/simple_math/simple_math.rst @@ -42,14 +42,16 @@ when ``x = 2``. Here is the bad test that lets the mutant(s) survive: :: + # examples/simple_math/test_simple_math_bad.py def test_mult_by_2(): assert mult_by_2(2) == 4 To fix this bad test, we decorate it so that a range -of values of x are tested: +of values of `x` are tested: :: + # examples/simple_math/test_simple_math_good.py @pytest.mark.parametrize('x', range(-5, 5)) def test_mult_by_2(x): From 7004d13599886fbfa22b037b80c486a38b165b65 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 11:46:17 +0000 Subject: [PATCH 15/16] Added .idea to gitignore and exluded directories in conf.py First commit of ten-pin bowling game example for test run. Implemented strike and spare scroing. Plus tests. config.yml file cosmic-ray run. Improved the documentation for simple_math More edits to the documentation --- .gitignore | 1 + docs/conf.py | 2 +- docs/examples.rst | 4 ++ docs/index.rst | 1 + docs/quickstart.rst | 3 + .../bowling_game_score_calculator/config.yml | 13 ++++ .../score_calculator.py | 48 +++++++++++++ .../test_score_calculator.py | 62 ++++++++++++++++ examples/simple_math/.gitignore | 1 + .../simple_math/cosmic-ray-bad_tests.conf | 13 ++++ .../simple_math/cosmic-ray-good_tests.conf | 13 ++++ examples/simple_math/simple_math.py | 26 +++++++ examples/simple_math/simple_math.rst | 71 +++++++++++++++++++ examples/simple_math/test_simple_math_bad.py | 26 +++++++ examples/simple_math/test_simple_math_good.py | 28 ++++++++ 15 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 docs/examples.rst create mode 100644 examples/bowling_game_score_calculator/config.yml create mode 100644 examples/bowling_game_score_calculator/score_calculator.py create mode 100644 examples/bowling_game_score_calculator/test_score_calculator.py create mode 100644 examples/simple_math/.gitignore create mode 100644 examples/simple_math/cosmic-ray-bad_tests.conf create mode 100644 examples/simple_math/cosmic-ray-good_tests.conf create mode 100644 examples/simple_math/simple_math.py create mode 100644 examples/simple_math/simple_math.rst create mode 100644 examples/simple_math/test_simple_math_bad.py create mode 100644 examples/simple_math/test_simple_math_good.py diff --git a/.gitignore b/.gitignore index 47a237fc..5db71a41 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ test_project/*.json .cache .hypothesis .csearchindex +.idea diff --git a/docs/conf.py b/docs/conf.py index 554319f0..8a4c8244 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.idea'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 00000000..3f6de7a5 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,4 @@ +Examples +======== + +.. include:: ../examples/simple_math/simple_math.rst diff --git a/docs/index.rst b/docs/index.rst index d6aafbc1..45c1d8e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ And, of course, patches and ideas are welcome. distributed implementation tests + examples diff --git a/docs/quickstart.rst b/docs/quickstart.rst index da3344f1..84fe6a83 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -85,3 +85,6 @@ Ray, you can run these tests, too, like this: In this case we're passing the ``--verbose`` flag to the ``exec`` command so that you can see what Cosmic Ray is doing. If everything goes as expected, the ``cr-report`` command will report a 0% survival rate. + +See :ref:`examples-simple_math` for a step-by-step guide for +dealing with tests that have a non-zero mutation survival rate. diff --git a/examples/bowling_game_score_calculator/config.yml b/examples/bowling_game_score_calculator/config.yml new file mode 100644 index 00000000..afab72f0 --- /dev/null +++ b/examples/bowling_game_score_calculator/config.yml @@ -0,0 +1,13 @@ +# Run the adam tests with unittest +module: score_calculator + +baseline: 10 + +exclude-modules: + +test-runner: + name: unittest + args: test_score_calculator + +execution-engine: + name: local diff --git a/examples/bowling_game_score_calculator/score_calculator.py b/examples/bowling_game_score_calculator/score_calculator.py new file mode 100644 index 00000000..7442d967 --- /dev/null +++ b/examples/bowling_game_score_calculator/score_calculator.py @@ -0,0 +1,48 @@ +""" +This is a simple class to demonstrate the cosmic-ray library. +The BowlingGame class keeps and calculates the score of a ten-pin bowling +game for one player. +The traditional bowling scoring is used: +https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring +""" + + +ALL_PINS = 10 + +class BowlingGame(): + def __init__(self): + self.score_count = 0 + self.spare = False + self.strike = False + + def score(self): + return self.score_count + + def roll(self, first_roll, second_roll): + frame_result = first_roll + second_roll + self._handle_spare_and_strikes(first_roll, frame_result) + self.score_count += frame_result + + def _handle_spare_and_strikes(self, first_roll, frame_result): + self._award_previous_spare_count(first_roll) + self._award_previous_strike_count(frame_result) + self._check_for_strike(first_roll) + self._check_for_spare(frame_result) + + def _award_previous_spare_count(self, first_roll): + if self.spare == True: + self.score_count += first_roll + self.spare = False + + def _award_previous_strike_count(self, frame_result): + if self.strike == True: + self.score_count += frame_result + self.strike = False + + def _check_for_strike(self, first_roll): + if first_roll == ALL_PINS: + self.strike = True + + def _check_for_spare(self, frame_result): + if frame_result == ALL_PINS and not self.strike: + self.spare = True diff --git a/examples/bowling_game_score_calculator/test_score_calculator.py b/examples/bowling_game_score_calculator/test_score_calculator.py new file mode 100644 index 00000000..ca9089f0 --- /dev/null +++ b/examples/bowling_game_score_calculator/test_score_calculator.py @@ -0,0 +1,62 @@ +import unittest + +from score_calculator import BowlingGame + + +class ScoreCalculatorTest(unittest.TestCase): + def setUp(self): + self.game = BowlingGame() + + def test_create_bowling_game(self): + self.assertIsInstance(self.game, BowlingGame) + + def test_the_score_of_a_new_game_is_zero(self): + self.assertEqual(self.game.score(), 0) + + def test_the_count_of_the_first_frame_is_added_to_the_score(self): + self.game.roll(2, 3) + self.assertEqual(self.game.score(), 5) + + def test_multiple_frame_results_are_kept_in_the_score(self): + self.game.roll(2, 4) + self.game.roll(6, 2) + self.assertEqual(self.game.score(), 14) + + def test_spares_are_detected_for_the_next_frame(self): + self.game.roll(6, 4) + self.assertTrue(self.game.spare) + + def test_previous_spare_results_in_that_next_roll_points_are_doubled(self): + self.game.roll(6, 4) + self.game.roll(5, 3) + self.assertEqual(self.game.score(), 23) + + def test_double_spares_are_counted_correctly(self): + self.game.roll(6, 4) + self.game.roll(5, 5) + self.game.roll(8, 0) + self.assertEqual(self.game.score(), 41) + + def test_the_spare_flag_is_removed_in_the_next_frame(self): + self.game.roll(6, 4) + self.game.roll(1, 1) + self.game.roll(2, 0) + self.assertEqual(self.game.score(), 15) + + def test_a_strike_is_detected_and_no_spare_flag_is_set(self): + self.game.roll(10, 0) + self.assertTrue(self.game.strike) + self.assertFalse(self.game.spare) + + def test_previous_strike_doubles_the_next_frame_pin_count(self): + self.game.roll(10, 0) + self.game.roll(5, 4) + self.assertEqual(self.game.score(), 28) + + def test_the_strike_flag_is_removed_in_the_next_frame(self): + self.game.roll(10, 0) + self.game.roll(1, 1) + self.game.roll(2, 5) + self.assertEqual(self.game.score(), 21) + + # case with strike after spare or the way around diff --git a/examples/simple_math/.gitignore b/examples/simple_math/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/examples/simple_math/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/examples/simple_math/cosmic-ray-bad_tests.conf b/examples/simple_math/cosmic-ray-bad_tests.conf new file mode 100644 index 00000000..c1bfff41 --- /dev/null +++ b/examples/simple_math/cosmic-ray-bad_tests.conf @@ -0,0 +1,13 @@ +# Run the simple_math tests with pytest +module: simple_math + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_simple_math_bad.py + +execution-engine: + name: local diff --git a/examples/simple_math/cosmic-ray-good_tests.conf b/examples/simple_math/cosmic-ray-good_tests.conf new file mode 100644 index 00000000..82044b5e --- /dev/null +++ b/examples/simple_math/cosmic-ray-good_tests.conf @@ -0,0 +1,13 @@ +# Run the simple_math tests with pytest +module: simple_math + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_simple_math_good.py + +execution-engine: + name: local diff --git a/examples/simple_math/simple_math.py b/examples/simple_math/simple_math.py new file mode 100644 index 00000000..b1df2945 --- /dev/null +++ b/examples/simple_math/simple_math.py @@ -0,0 +1,26 @@ +""" +----------- +Simple Math +----------- + +A set of simple math functions. +This is paired up with a test suite and intended to be run with cosmic-ray. +The idea is that cosmic-ray should kill every mutant when that suite is run; +if it doesn't, then we've got a problem. +""" + + +def mult_by_2(x): + return x + x + + +def square(x): + return x*x + + +def cube(x): + return x*x*x + + +def is_positive(x): + return x > 0 diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst new file mode 100644 index 00000000..b547d269 --- /dev/null +++ b/examples/simple_math/simple_math.rst @@ -0,0 +1,71 @@ +.. _examples-simple_math: + +Improving the tests for a simple module +--------------------------------------- + +This example demonstrates how to use cosmic-ray to improve the testing +suite for a module called ``simple_math``. The code is located in the +``examples/simple_math`` directory. + +:: + + # examples/simple_math/simple_math.py + + def mult_by_2(x): + return x + x + + def square(x): + return x*x + + def cube(x): + return x*x*x + + def is_positive(x): + return x > 0 + + +We would like to measure the performance of a testing suite, +``test_simple_math_bad.py``, with intention to improve it. +First run cosmic-ray on the so-called 'bad' testing suite. + +:: + + cosmic-ray init cosmic-ray-bad_tests.conf bad_session + cosmic-ray --verbose exec bad_session + cosmic-ray dump bad_session | cr-report + +You should end up with at least one mutant that survives. This is because the test +``test_mult_by_2`` from ``test_simple_math_bad.py`` still passes when we replace +``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, ``4``, +when ``x = 2``. + +Here is the bad test that lets the mutant(s) survive: + +:: + # examples/simple_math/test_simple_math_bad.py + + def test_mult_by_2(): + assert mult_by_2(2) == 4 + +To fix this bad test, we decorate it so that a range +of values of `x` are tested: + +:: + # examples/simple_math/test_simple_math_good.py + + @pytest.mark.parametrize('x', range(-5, 5)) + def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 + +Now this test should fail for all the mutations to the underlying +function ``mult_by_2``, which is what we want it to do. +Run cosmic-ray again on the new testing suite, ``test_simple_math_good.py`` + +:: + + cosmic-ray init cosmic-ray-good_tests.conf good_session + cosmic-ray --verbose exec good_session + cosmic-ray dump good_session | cr-report + +You should now get 0% survival rate for the mutants. This means your +testing suite is now more robust. diff --git a/examples/simple_math/test_simple_math_bad.py b/examples/simple_math/test_simple_math_bad.py new file mode 100644 index 00000000..55a87d96 --- /dev/null +++ b/examples/simple_math/test_simple_math_bad.py @@ -0,0 +1,26 @@ +from simple_math import square, cube, mult_by_2, is_positive + + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 + + +def test_mult_by_2(): + assert mult_by_2(2) == 4 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) + diff --git a/examples/simple_math/test_simple_math_good.py b/examples/simple_math/test_simple_math_good.py new file mode 100644 index 00000000..e9e954ee --- /dev/null +++ b/examples/simple_math/test_simple_math_good.py @@ -0,0 +1,28 @@ +from simple_math import square, cube, mult_by_2, is_positive + +import pytest + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 + + +@pytest.mark.parametrize('x', range(-5, 5)) +def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) + From ede77500f0b640ffbc32ecfd4c7b0ec0e65a7415 Mon Sep 17 00:00:00 2001 From: Thomas Edwards Date: Mon, 30 Oct 2017 11:46:17 +0000 Subject: [PATCH 16/16] Added simple_math example Added .idea to gitignore and exluded directories in conf.py Implemented strike and spare scroing. Plus tests. config.yml file cosmic-ray run. Improved the documentation for simple_math More edits to the documentation --- .gitignore | 1 + docs/conf.py | 2 +- docs/examples.rst | 4 ++ docs/index.rst | 1 + docs/quickstart.rst | 3 + .../bowling_game_score_calculator/config.yml | 13 ++++ .../score_calculator.py | 48 +++++++++++++ .../test_score_calculator.py | 62 ++++++++++++++++ examples/simple_math/.gitignore | 1 + .../simple_math/cosmic-ray-bad_tests.conf | 13 ++++ .../simple_math/cosmic-ray-good_tests.conf | 13 ++++ examples/simple_math/simple_math.py | 26 +++++++ examples/simple_math/simple_math.rst | 71 +++++++++++++++++++ examples/simple_math/test_simple_math_bad.py | 25 +++++++ examples/simple_math/test_simple_math_good.py | 28 ++++++++ 15 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 docs/examples.rst create mode 100644 examples/bowling_game_score_calculator/config.yml create mode 100644 examples/bowling_game_score_calculator/score_calculator.py create mode 100644 examples/bowling_game_score_calculator/test_score_calculator.py create mode 100644 examples/simple_math/.gitignore create mode 100644 examples/simple_math/cosmic-ray-bad_tests.conf create mode 100644 examples/simple_math/cosmic-ray-good_tests.conf create mode 100644 examples/simple_math/simple_math.py create mode 100644 examples/simple_math/simple_math.rst create mode 100644 examples/simple_math/test_simple_math_bad.py create mode 100644 examples/simple_math/test_simple_math_good.py diff --git a/.gitignore b/.gitignore index 47a237fc..5db71a41 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ test_project/*.json .cache .hypothesis .csearchindex +.idea diff --git a/docs/conf.py b/docs/conf.py index 554319f0..8a4c8244 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.idea'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 00000000..3f6de7a5 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,4 @@ +Examples +======== + +.. include:: ../examples/simple_math/simple_math.rst diff --git a/docs/index.rst b/docs/index.rst index d6aafbc1..45c1d8e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -40,6 +40,7 @@ And, of course, patches and ideas are welcome. distributed implementation tests + examples diff --git a/docs/quickstart.rst b/docs/quickstart.rst index da3344f1..84fe6a83 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -85,3 +85,6 @@ Ray, you can run these tests, too, like this: In this case we're passing the ``--verbose`` flag to the ``exec`` command so that you can see what Cosmic Ray is doing. If everything goes as expected, the ``cr-report`` command will report a 0% survival rate. + +See :ref:`examples-simple_math` for a step-by-step guide for +dealing with tests that have a non-zero mutation survival rate. diff --git a/examples/bowling_game_score_calculator/config.yml b/examples/bowling_game_score_calculator/config.yml new file mode 100644 index 00000000..afab72f0 --- /dev/null +++ b/examples/bowling_game_score_calculator/config.yml @@ -0,0 +1,13 @@ +# Run the adam tests with unittest +module: score_calculator + +baseline: 10 + +exclude-modules: + +test-runner: + name: unittest + args: test_score_calculator + +execution-engine: + name: local diff --git a/examples/bowling_game_score_calculator/score_calculator.py b/examples/bowling_game_score_calculator/score_calculator.py new file mode 100644 index 00000000..7442d967 --- /dev/null +++ b/examples/bowling_game_score_calculator/score_calculator.py @@ -0,0 +1,48 @@ +""" +This is a simple class to demonstrate the cosmic-ray library. +The BowlingGame class keeps and calculates the score of a ten-pin bowling +game for one player. +The traditional bowling scoring is used: +https://en.wikipedia.org/wiki/Ten-pin_bowling#Traditional_scoring +""" + + +ALL_PINS = 10 + +class BowlingGame(): + def __init__(self): + self.score_count = 0 + self.spare = False + self.strike = False + + def score(self): + return self.score_count + + def roll(self, first_roll, second_roll): + frame_result = first_roll + second_roll + self._handle_spare_and_strikes(first_roll, frame_result) + self.score_count += frame_result + + def _handle_spare_and_strikes(self, first_roll, frame_result): + self._award_previous_spare_count(first_roll) + self._award_previous_strike_count(frame_result) + self._check_for_strike(first_roll) + self._check_for_spare(frame_result) + + def _award_previous_spare_count(self, first_roll): + if self.spare == True: + self.score_count += first_roll + self.spare = False + + def _award_previous_strike_count(self, frame_result): + if self.strike == True: + self.score_count += frame_result + self.strike = False + + def _check_for_strike(self, first_roll): + if first_roll == ALL_PINS: + self.strike = True + + def _check_for_spare(self, frame_result): + if frame_result == ALL_PINS and not self.strike: + self.spare = True diff --git a/examples/bowling_game_score_calculator/test_score_calculator.py b/examples/bowling_game_score_calculator/test_score_calculator.py new file mode 100644 index 00000000..ca9089f0 --- /dev/null +++ b/examples/bowling_game_score_calculator/test_score_calculator.py @@ -0,0 +1,62 @@ +import unittest + +from score_calculator import BowlingGame + + +class ScoreCalculatorTest(unittest.TestCase): + def setUp(self): + self.game = BowlingGame() + + def test_create_bowling_game(self): + self.assertIsInstance(self.game, BowlingGame) + + def test_the_score_of_a_new_game_is_zero(self): + self.assertEqual(self.game.score(), 0) + + def test_the_count_of_the_first_frame_is_added_to_the_score(self): + self.game.roll(2, 3) + self.assertEqual(self.game.score(), 5) + + def test_multiple_frame_results_are_kept_in_the_score(self): + self.game.roll(2, 4) + self.game.roll(6, 2) + self.assertEqual(self.game.score(), 14) + + def test_spares_are_detected_for_the_next_frame(self): + self.game.roll(6, 4) + self.assertTrue(self.game.spare) + + def test_previous_spare_results_in_that_next_roll_points_are_doubled(self): + self.game.roll(6, 4) + self.game.roll(5, 3) + self.assertEqual(self.game.score(), 23) + + def test_double_spares_are_counted_correctly(self): + self.game.roll(6, 4) + self.game.roll(5, 5) + self.game.roll(8, 0) + self.assertEqual(self.game.score(), 41) + + def test_the_spare_flag_is_removed_in_the_next_frame(self): + self.game.roll(6, 4) + self.game.roll(1, 1) + self.game.roll(2, 0) + self.assertEqual(self.game.score(), 15) + + def test_a_strike_is_detected_and_no_spare_flag_is_set(self): + self.game.roll(10, 0) + self.assertTrue(self.game.strike) + self.assertFalse(self.game.spare) + + def test_previous_strike_doubles_the_next_frame_pin_count(self): + self.game.roll(10, 0) + self.game.roll(5, 4) + self.assertEqual(self.game.score(), 28) + + def test_the_strike_flag_is_removed_in_the_next_frame(self): + self.game.roll(10, 0) + self.game.roll(1, 1) + self.game.roll(2, 5) + self.assertEqual(self.game.score(), 21) + + # case with strike after spare or the way around diff --git a/examples/simple_math/.gitignore b/examples/simple_math/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/examples/simple_math/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/examples/simple_math/cosmic-ray-bad_tests.conf b/examples/simple_math/cosmic-ray-bad_tests.conf new file mode 100644 index 00000000..c1bfff41 --- /dev/null +++ b/examples/simple_math/cosmic-ray-bad_tests.conf @@ -0,0 +1,13 @@ +# Run the simple_math tests with pytest +module: simple_math + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_simple_math_bad.py + +execution-engine: + name: local diff --git a/examples/simple_math/cosmic-ray-good_tests.conf b/examples/simple_math/cosmic-ray-good_tests.conf new file mode 100644 index 00000000..82044b5e --- /dev/null +++ b/examples/simple_math/cosmic-ray-good_tests.conf @@ -0,0 +1,13 @@ +# Run the simple_math tests with pytest +module: simple_math + +baseline: 10 + +exclude-modules: + +test-runner: + name: pytest + args: -x test_simple_math_good.py + +execution-engine: + name: local diff --git a/examples/simple_math/simple_math.py b/examples/simple_math/simple_math.py new file mode 100644 index 00000000..b1df2945 --- /dev/null +++ b/examples/simple_math/simple_math.py @@ -0,0 +1,26 @@ +""" +----------- +Simple Math +----------- + +A set of simple math functions. +This is paired up with a test suite and intended to be run with cosmic-ray. +The idea is that cosmic-ray should kill every mutant when that suite is run; +if it doesn't, then we've got a problem. +""" + + +def mult_by_2(x): + return x + x + + +def square(x): + return x*x + + +def cube(x): + return x*x*x + + +def is_positive(x): + return x > 0 diff --git a/examples/simple_math/simple_math.rst b/examples/simple_math/simple_math.rst new file mode 100644 index 00000000..b547d269 --- /dev/null +++ b/examples/simple_math/simple_math.rst @@ -0,0 +1,71 @@ +.. _examples-simple_math: + +Improving the tests for a simple module +--------------------------------------- + +This example demonstrates how to use cosmic-ray to improve the testing +suite for a module called ``simple_math``. The code is located in the +``examples/simple_math`` directory. + +:: + + # examples/simple_math/simple_math.py + + def mult_by_2(x): + return x + x + + def square(x): + return x*x + + def cube(x): + return x*x*x + + def is_positive(x): + return x > 0 + + +We would like to measure the performance of a testing suite, +``test_simple_math_bad.py``, with intention to improve it. +First run cosmic-ray on the so-called 'bad' testing suite. + +:: + + cosmic-ray init cosmic-ray-bad_tests.conf bad_session + cosmic-ray --verbose exec bad_session + cosmic-ray dump bad_session | cr-report + +You should end up with at least one mutant that survives. This is because the test +``test_mult_by_2`` from ``test_simple_math_bad.py`` still passes when we replace +``x + x`` with ``x * x`` or ``x ** x``, as they all return the same answer, ``4``, +when ``x = 2``. + +Here is the bad test that lets the mutant(s) survive: + +:: + # examples/simple_math/test_simple_math_bad.py + + def test_mult_by_2(): + assert mult_by_2(2) == 4 + +To fix this bad test, we decorate it so that a range +of values of `x` are tested: + +:: + # examples/simple_math/test_simple_math_good.py + + @pytest.mark.parametrize('x', range(-5, 5)) + def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 + +Now this test should fail for all the mutations to the underlying +function ``mult_by_2``, which is what we want it to do. +Run cosmic-ray again on the new testing suite, ``test_simple_math_good.py`` + +:: + + cosmic-ray init cosmic-ray-good_tests.conf good_session + cosmic-ray --verbose exec good_session + cosmic-ray dump good_session | cr-report + +You should now get 0% survival rate for the mutants. This means your +testing suite is now more robust. diff --git a/examples/simple_math/test_simple_math_bad.py b/examples/simple_math/test_simple_math_bad.py new file mode 100644 index 00000000..ab8023e3 --- /dev/null +++ b/examples/simple_math/test_simple_math_bad.py @@ -0,0 +1,25 @@ +from simple_math import square, cube, mult_by_2, is_positive + + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 + + +def test_mult_by_2(): + assert mult_by_2(2) == 4 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) diff --git a/examples/simple_math/test_simple_math_good.py b/examples/simple_math/test_simple_math_good.py new file mode 100644 index 00000000..e9e954ee --- /dev/null +++ b/examples/simple_math/test_simple_math_good.py @@ -0,0 +1,28 @@ +from simple_math import square, cube, mult_by_2, is_positive + +import pytest + +def test_square(): + assert square(3) == 9 + + +def test_cube(): + assert cube(2) == 8 + + +@pytest.mark.parametrize('x', range(-5, 5)) +def test_mult_by_2(x): + assert mult_by_2(x) == x * 2 + + +def test_is_positive_for_positive_numbers(): + assert is_positive(1) + assert is_positive(2) + assert is_positive(3) + + +def test_is_positive_for_non_positive_numbers(): + assert not is_positive(0) + assert not is_positive(-1) + assert not is_positive(-2) +