Testing and refactoring boolean expressions¶
When dealing with non-trivial boolean expressions mutation testing often helps put things into perspective. It causes you to rethink the expression which often leads to refactoring and killing mutants.
Example¶
$ pip install nose
$ pip install https://github.com/sixty-north/cosmic-ray/zipball/master
Initially we start with the example in boolops1.py
and test1.py
.
Although the test appears to be correct, all possible values for list_a
and
list_b
are tested, there are still surviving mutants.
$ cosmic-ray run --test-runner nose --baseline=10 example.json boolops1.py -- test1.py:
$ cosmic-ray report example.json
job ID 5:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Eq_with_LtE 0 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) <= 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
raise Exception('TEST')
job ID 6:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Eq_with_LtE 1 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) == 0) and (len(list_b) <= 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
raise Exception('TEST')
job ID 13:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Gt_with_NotEq 0 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) != 0) and (len(list_b) > 0))):
raise Exception('TEST')
job ID 14:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Gt_with_NotEq 1 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) != 0))):
raise Exception('TEST')
job ID 23:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Gt_with_IsNot 0 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) is not 0) and (len(list_b) > 0))):
raise Exception('TEST')
job ID 24:Outcome.SURVIVED:boolops
command: cosmic-ray worker boolops replace_Gt_with_IsNot 1 nose -- -v test1.py
--- mutation diff ---
--- a/example_10/boolops1.py
+++ b/example_10/boolops1.py
@@ -1,6 +1,6 @@
def xnor_raise(list_a, list_b):
- if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) > 0))):
+ if (((len(list_a) == 0) and (len(list_b) == 0)) or ((len(list_a) > 0) and (len(list_b) is not 0))):
raise Exception('TEST')
total jobs: 38
complete: 38 (100.00%)
survival rate: 15.79%
If we proceed to refactor len(list)
comparisons as shown previously it
is easier to figure out that the boolean function is XNOR (if and only if),
also called logical equality. In boolops2.py
Cosmic-Ray doesn’t
detect any possible mutations because at the moment of writing it doesn’t
support mutating boolean operators.
$ cosmic-ray run --test-runner nose --baseline=10 example.json boolops2.py -- test2.py:
$ cosmic-ray report example.json
total jobs: 0
no jobs completed
Another possible refactoring is boolops3.py
where valid parameters are
enumerated before the condition is checked.
$ cosmic-ray run --test-runner nose --baseline=10 example.json boolops3.py -- test3.py:
$ cosmic-ray report example.json
total jobs: 12
complete: 12 (100.00%)
survival rate: 0.00%
Yet another possible refactoring is boolops4.py
where the condition is
expressed using the built-ins any
and all
. Unfortunately Cosmic-Ray
doesn’t recognize these as possible mutations either.
$ cosmic-ray run --test-runner nose --baseline=10 example.json boolops4.py -- test4.py:
$ cosmic-ray report example.json
total jobs: 0
no jobs completed
Source code¶
def xnor_raise(list_a, list_b):
if (len(list_a) == 0 and len(list_b) == 0) or (len(list_a) > 0 and (len(list_b) > 0)):
raise Exception('TEST')
import boolops1 as boolops
import unittest
class TestBoolOps(unittest.TestCase):
def test_xnor_raise_a_empty_b_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([], [])
def test_xnor_raise_a_not_empty_b_not_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([1], [2])
def test_xnor_raise_a_empty_b_not_empty(self):
# doesn'r raise exception
boolops.xnor_raise([], [2])
def test_xnor_raise_a_not_empty_b_empty(self):
# doesn't raise exception
boolops.xnor_raise([1], [])
if __name__ == "__main__":
unittest.main()
def xnor_raise(list_a, list_b):
if (not list_a and not list_b) or (list_a and list_b):
raise Exception('TEST')
import boolops2 as boolops
import unittest
class TestBoolOps(unittest.TestCase):
def test_xnor_raise_a_empty_b_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([], [])
def test_xnor_raise_a_not_empty_b_not_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([1], [2])
def test_xnor_raise_a_empty_b_not_empty(self):
# doesn'r raise exception
boolops.xnor_raise([], [2])
def test_xnor_raise_a_not_empty_b_empty(self):
# doesn't raise exception
boolops.xnor_raise([1], [])
if __name__ == "__main__":
unittest.main()
def xnor_raise(list_a, list_b):
valid_lists = 0
if list_a:
valid_lists += 1
if list_b:
valid_lists += 1
if valid_lists != 1:
raise Exception('TEST')
import boolops3 as boolops
import unittest
class TestBoolOps(unittest.TestCase):
def test_xnor_raise_a_empty_b_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([], [])
def test_xnor_raise_a_not_empty_b_not_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([1], [2])
def test_xnor_raise_a_empty_b_not_empty(self):
# doesn'r raise exception
boolops.xnor_raise([], [2])
def test_xnor_raise_a_not_empty_b_empty(self):
# doesn't raise exception
boolops.xnor_raise([1], [])
if __name__ == "__main__":
unittest.main()
def xnor_raise(list_a, list_b):
if not any([list_a, list_b]) or all([list_a, list_b]):
raise Exception('TEST')
import boolops4 as boolops
import unittest
class TestBoolOps(unittest.TestCase):
def test_xnor_raise_a_empty_b_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([], [])
def test_xnor_raise_a_not_empty_b_not_empty(self):
with self.assertRaises(Exception):
boolops.xnor_raise([1], [2])
def test_xnor_raise_a_empty_b_not_empty(self):
# doesn'r raise exception
boolops.xnor_raise([], [2])
def test_xnor_raise_a_not_empty_b_empty(self):
# doesn't raise exception
boolops.xnor_raise([1], [])
if __name__ == "__main__":
unittest.main()