TER1 (Statement): 100.00%
TER2 (Branch): 86.67%
TER3 (LCSAJ): 100.0% (8/8)
Approximate LCSAJ segments: 31
● Covered — this LCSAJ path was executed during testing.
● Not covered — this LCSAJ path was never executed. These are the paths to focus on.
Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:
start → end → jump
Uncovered paths show [NOT COVERED] in the tooltip.
1: package App::Test::Generator::TestStrategy; 2: 3: use strict; 4: use warnings; 5: use Readonly; 6: 7: # -------------------------------------------------- 8: # Accessor type strings from the schema 9: # -------------------------------------------------- 10: Readonly my $ACCESSOR_GETTER => 'getter'; 11: Readonly my $ACCESSOR_SETTER => 'setter'; 12: Readonly my $ACCESSOR_GETSET => 'getset'; 13: 14: # -------------------------------------------------- 15: # Output type strings from the schema 16: # -------------------------------------------------- 17: Readonly my $TYPE_BOOLEAN => 'boolean'; 18: Readonly my $TYPE_OBJECT => 'object'; 19: Readonly my $TYPE_VOID => 'void'; 20: 21: # -------------------------------------------------- 22: # Default confidence threshold for plan generation 23: # -------------------------------------------------- 24: Readonly my $DEFAULT_CONFIDENCE => 'medium'; 25: 26: # -------------------------------------------------- 27: # Test plan flag keys written to the method plan 28: # -------------------------------------------------- 29: Readonly my $TEST_CONTEXT => 'context_tests'; 30: Readonly my $TEST_PREDICATE => 'predicate_test'; 31: Readonly my $TEST_GETTER => 'getter_test'; 32: Readonly my $TEST_SETTER => 'setter_test'; 33: Readonly my $TEST_GETSET => 'getset_test'; 34: Readonly my $TEST_OBJECT_INJECT => 'object_injection_test'; 35: Readonly my $TEST_BOOLEAN_SET => 'boolean_set_test'; 36: Readonly my $TEST_VOID => 'void_context_test'; 37: Readonly my $TEST_ERROR_HANDLING => 'error_handling_test'; 38: Readonly my $TEST_BOUNDARY => 'boundary_tests'; 39: Readonly my $TEST_CHAINING => 'chaining_test'; 40: Readonly my $TEST_BASIC => 'basic_test'; 41: 42: our $VERSION = '0.41'; 43: 44: =head1 VERSION 45: 46: Version 0.41 47: 48: =head1 DESCRIPTION 49: 50: Generates a test strategy plan for all methods in a schema, determining 51: which test types should be produced for each method based on its 52: accessor classification, output type, and other metadata. Side-effect 53: and dependency-driven planning (mocking, isolation) is handled 54: separately by L<App::Test::Generator::Planner::Mock> and 55: L<App::Test::Generator::Planner::Isolation>. 56: 57: =head2 new 58: 59: Construct a new TestStrategy. 60: 61: my $strategy = App::Test::Generator::TestStrategy->new( 62: schema => \%schemas, 63: thresholds => { confidence => 'high' }, 64: ); 65: 66: =head3 Arguments 67: 68: =over 4 69: 70: =item * C<schema> 71: 72: A hashref of method name to schema hashref. Optional â defaults to 73: an empty hashref. 74: 75: =item * C<thresholds> 76: 77: A hashref of threshold configuration. Optional â defaults to 78: C<< { confidence => 'medium' } >>. 79: 80: =back 81: 82: =head3 Returns 83: 84: A blessed hashref. 85: 86: =head3 API specification 87: 88: =head4 input 89: 90: { 91: schema => { type => HASHREF, optional => 1 }, 92: thresholds => { type => HASHREF, optional => 1 }, 93: } 94: 95: =head4 output 96: 97: { 98: type => OBJECT, 99: isa => 'App::Test::Generator::TestStrategy', 100: } 101: 102: =cut 103: 104: sub new { 105: my ($class, %args) = @_; 106: return bless { 107: schema => $args{schema} || {}, 108: thresholds => $args{thresholds} || { confidence => $DEFAULT_CONFIDENCE }, 109: plans => {}, 110: }, $class; 111: } 112: 113: =head2 generate_plan 114: 115: Generate a test plan for all methods in the schema and return it as 116: a hashref mapping method names to plan hashrefs. 117: 118: my $strategy = App::Test::Generator::TestStrategy->new( 119: schema => \%schemas, 120: ); 121: my $plan = $strategy->generate_plan; 122: 123: for my $method (keys %{$plan}) { 124: print "$method: ", join(', ', keys %{ $plan->{$method} }), "\n"; 125: } 126: 127: =head3 Arguments 128: 129: None beyond C<$self>. 130: 131: =head3 Returns 132: 133: A hashref mapping method names to test plan hashrefs, each containing 134: boolean flags for the test types that should be generated. 135: 136: =head3 API specification 137: 138: =head4 input 139: 140: { 141: self => { type => OBJECT, isa => 'App::Test::Generator::TestStrategy' }, 142: } 143: 144: =head4 output 145: 146: { 147: type => HASHREF, 148: keys => { 149: '*' => { type => HASHREF }, 150: }, 151: } 152: 153: =cut 154: 155: sub generate_plan { ●156 → 158 → 165 156: my $self = $_[0]; 157: 158: for my $method (keys %{ $self->{schema} }) { 159: my $schema = $self->{schema}{$method}; 160: 161: # Generate and store the plan for this method 162: $self->{plans}{$method} = $self->_plan_for_method($schema); 163: } 164: 165: return $self->{plans}; 166: } 167: 168: # -------------------------------------------------- 169: # _plan_for_methodMutants (Total: 2, Killed: 2, Survived: 0)
170: # 171: # Determine which test types should be 172: # generated for a single method based on 173: # its schema metadata. 174: # 175: # Entry: $schema - the per-method schema hashref 176: # 177: # Exit: Returns a hashref of test type flags. 178: # Always contains at least basic_test => 1. 179: # 180: # Side effects: None. 181: # 182: # Notes: All string comparisons use // '' guards 183: # to avoid uninitialized value warnings 184: # when schema fields are absent. 185: # -------------------------------------------------- 186: sub _plan_for_method { ●187 → 195 → 203 187: my ($self, $schema) = @_; 188: 189: my %plan; 190: 191: # -------------------------------------------------- 192: # Context-aware returns need both scalar and list 193: # context tests to verify correct behaviour in each 194: # -------------------------------------------------- 195: if($schema->{output}{_context_aware}) { 196: $plan{$TEST_CONTEXT} = 1; 197: } 198: 199: # --------------------------------------------------
Mutants (Total: 1, Killed: 1, Survived: 0)
200: # Accessor detection â choose test types based on 201: # whether the method is a getter, setter, or both 202: # -------------------------------------------------- ●203 → 203 → 243 203: if($schema->{accessor} && scalar keys %{ $schema->{accessor} }) { 204: my $acc_type = $schema->{accessor}{type} // ''; 205: 206: if($acc_type eq $ACCESSOR_GETTER) { 207: # Boolean getters are predicates and need
Mutants (Total: 1, Killed: 1, Survived: 0)
208: # truthy/falsy tests in addition to getter tests 209: if(($schema->{output}{type} // '') eq $TYPE_BOOLEAN) { 210: $plan{$TEST_PREDICATE} = 1;
Mutants (Total: 1, Killed: 1, Survived: 0)
211: } 212: $plan{$TEST_GETTER} = 1; 213:
214: } elsif($acc_type eq $ACCESSOR_SETTER) { 215: $plan{$TEST_SETTER} = 1; 216: 217: } elsif($acc_type eq $ACCESSOR_GETSET) { 218: # For getset accessors, check the input parameter 219: # type to determine if object injection or boolean 220: # set tests are more appropriate. Sort by position (keys 221: # %hash has no defined order) so the choice is deterministic 222: # if more than one candidate is present. 223: my $input = $schema->{input} || {}; 224: my ($param) = sort { 225: ($input->{$a}{position} // 9999) <=> ($input->{$b}{position} // 9999) 226: || $a cmp $b 227: } grep { !/^_/ } keys %{ $input }; 228: my $param_type = ($param && $input->{$param}{type}) // '';Mutants (Total: 1, Killed: 0, Survived: 1)
- COND_INV_213_4: Invert condition if to unless
MEDIUM: Add tests asserting both true and false outcomesMutants (Total: 1, Killed: 1, Survived: 0)
229: 230: if($param_type eq $TYPE_OBJECT) { 231: $plan{$TEST_OBJECT_INJECT} = 1; 232: } elsif($param_type eq $TYPE_BOOLEAN) { 233: $plan{$TEST_BOOLEAN_SET} = 1; 234: } 235: $plan{$TEST_GETSET} = 1; 236: } 237: } 238: 239: # -------------------------------------------------- 240: # Void return type â verify the method returns nothing 241: # and does not accidentally return a useful value
Mutants (Total: 1, Killed: 1, Survived: 0)
242: # -------------------------------------------------- ●243 → 243 → 251 243: if(($schema->{output}{type} // '') eq $TYPE_VOID) { 244: $plan{$TEST_VOID} = 1; 245: } 246: 247: # -------------------------------------------------- 248: # Error handling â verify error return conventions 249: # are tested explicitly
Mutants (Total: 1, Killed: 1, Survived: 0)
250: # -------------------------------------------------- ●251 → 251 → 260 251: if($schema->{output}{_error_return} 252: || $schema->{output}{success_failure_pattern}) { 253: $plan{$TEST_ERROR_HANDLING} = 1; 254: } 255: 256: # -------------------------------------------------- 257: # Boundary hints from YAML test configuration â 258: # generate boundary/equivalence class tests
Mutants (Total: 1, Killed: 1, Survived: 0)
259: # -------------------------------------------------- ●260 → 260 → 268 260: if($schema->{_yamltest_hints} && keys %{ $schema->{_yamltest_hints} }) { 261: $plan{$TEST_BOUNDARY} = 1; 262: } 263: 264: # -------------------------------------------------- 265: # Method chaining â verify that $self is returned 266: # and that calls can be chained
Mutants (Total: 1, Killed: 1, Survived: 0)
267: # -------------------------------------------------- ●268 → 268 → 276 268: if($schema->{output}{_returns_self}) { 269: $plan{$TEST_CHAINING} = 1; 270: } 271: 272: # -------------------------------------------------- 273: # Boolean output â needs predicate tests regardless 274: # of whether an accessor was detected
Mutants (Total: 1, Killed: 1, Survived: 0)
275: # -------------------------------------------------- ●276 → 276 → 284 276: if(($schema->{output}{type} // '') eq $TYPE_BOOLEAN) { 277: $plan{$TEST_PREDICATE} = 1; 278: } 279: 280: # -------------------------------------------------- 281: # Always generate at least a basic call test even 282: # if no other test types were identified 283: # -------------------------------------------------- 284: $plan{$TEST_BASIC} = 1 unless %plan; 285: 286: return \%plan; 287: } 288: 289: 1;