1
1
import unittest
2
2
import os
3
-
4
3
import pandas as pd
4
+ from itertools import combinations
5
5
6
6
from tests .test_helpers import create_temp_dir_if_non_existent , remove_temp_dir_if_existent
7
7
from causal_testing .specification .causal_dag import CausalDAG
8
8
from causal_testing .specification .causal_specification import Scenario
9
- from causal_testing .specification .metamorphic_relation import ShouldCause
9
+ from causal_testing .specification .metamorphic_relation import ShouldCause , ShouldNotCause
10
10
from causal_testing .data_collection .data_collector import ExperimentalDataCollector
11
11
from causal_testing .specification .variable import Input , Output
12
12
@@ -18,7 +18,7 @@ def program_under_test(X1, X2, X3, Z=None, M=None, Y=None):
18
18
M = 3 * Z + X3
19
19
if Y is None :
20
20
Y = M / 2
21
- return {'Z' : Z , 'M' : M , 'Y' : Y }
21
+ return {'X1' : X1 , 'X2' : X2 , 'X3' : X3 , ' Z' : Z , 'M' : M , 'Y' : Y }
22
22
23
23
24
24
def buggy_program_under_test (X1 , X2 , X3 , Z = None , M = None , Y = None ):
@@ -28,7 +28,7 @@ def buggy_program_under_test(X1, X2, X3, Z=None, M=None, Y=None):
28
28
M = 3 * Z + X3
29
29
if Y is None :
30
30
Y = M / 2
31
- return {'Z' : Z , 'M' : M , 'Y' : Y }
31
+ return {'X1' : X1 , 'X2' : X2 , 'X3' : X3 , ' Z' : Z , 'M' : M , 'Y' : Y }
32
32
33
33
34
34
class ProgramUnderTestEDC (ExperimentalDataCollector ):
@@ -52,7 +52,7 @@ class TestMetamorphicRelation(unittest.TestCase):
52
52
def setUp (self ) -> None :
53
53
temp_dir_path = create_temp_dir_if_non_existent ()
54
54
self .dag_dot_path = os .path .join (temp_dir_path , "dag.dot" )
55
- dag_dot = """digraph DAG { rankdir=LR; X1 -> Z; Z -> M; M -> Y; X1 -> M; X2 -> Z; X3 -> M;}"""
55
+ dag_dot = """digraph DAG { rankdir=LR; X1 -> Z; Z -> M; M -> Y; X2 -> Z; X3 -> M;}"""
56
56
with open (self .dag_dot_path , "w" ) as f :
57
57
f .write (dag_dot )
58
58
@@ -69,7 +69,8 @@ def setUp(self) -> None:
69
69
self .default_control_input_config ,
70
70
self .default_treatment_input_config )
71
71
72
- def test_should_cause_metamorphic_relations_should_pass (self ):
72
+ def test_should_cause_metamorphic_relations_correct_spec (self ):
73
+ """Test if the ShouldCause MR passes all metamorphic tests where the DAG perfectly represents the program."""
73
74
causal_dag = CausalDAG (self .dag_dot_path )
74
75
for edge in causal_dag .graph .edges :
75
76
(u , v ) = edge
@@ -78,9 +79,30 @@ def test_should_cause_metamorphic_relations_should_pass(self):
78
79
test_results = should_cause_MR .execute_tests (self .data_collector )
79
80
should_cause_MR .test_oracle (test_results )
80
81
82
+ def test_should_not_cause_metamorphic_relations_correct_spec (self ):
83
+ """Test if the ShouldNotCause MR passes all metamorphic tests where the DAG perfectly represents the program."""
84
+ causal_dag = CausalDAG (self .dag_dot_path )
85
+ for node_pair in combinations (causal_dag .graph .nodes , 2 ):
86
+ (u , v ) = node_pair
87
+ # Get all pairs of nodes which don't form an edge
88
+ if ((u , v ) not in causal_dag .graph .edges ) and ((v , u ) not in causal_dag .graph .edges ):
89
+ # Check both directions if there is no causality
90
+ # This can be done more efficiently by ignoring impossible directions (output --> input)
91
+ adj_set = list (causal_dag .direct_effect_adjustment_sets ([u ], [v ])[0 ])
92
+ u_should_not_cause_v_MR = ShouldNotCause (u , v , adj_set , causal_dag )
93
+ v_should_not_cause_u_MR = ShouldNotCause (v , u , adj_set , causal_dag )
94
+ u_should_not_cause_v_MR .generate_follow_up (10 , - 100 , 100 )
95
+ v_should_not_cause_u_MR .generate_follow_up (10 , - 100 , 100 )
96
+ u_should_not_cause_v_test_results = u_should_not_cause_v_MR .execute_tests (self .data_collector )
97
+ v_should_not_cause_u_test_results = v_should_not_cause_u_MR .execute_tests (self .data_collector )
98
+ u_should_not_cause_v_MR .test_oracle (u_should_not_cause_v_test_results )
99
+ v_should_not_cause_u_MR .test_oracle (v_should_not_cause_u_test_results )
100
+
81
101
def test_should_cause_metamorphic_relation_missing_relationship (self ):
82
102
"""Test whether the ShouldCause MR catches missing relationships in the DAG."""
83
103
causal_dag = CausalDAG (self .dag_dot_path )
104
+
105
+ # Replace the data collector with one that runs a buggy program in which X1 and X2 do not affect Z
84
106
self .data_collector = BuggyProgramUnderTestEDC (self .scenario ,
85
107
self .default_control_input_config ,
86
108
self .default_treatment_input_config )
0 commit comments