TER1 (Statement): 98.11%
TER2 (Branch): 83.33%
TER3 (LCSAJ): 100.0% (18/18)
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.36'; 43: 44: =head1 VERSION 45: 46: Version 0.36 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, side effects, and other metadata. 53: 54: =head2 new 55: 56: Construct a new TestStrategy. 57: 58: my $strategy = App::Test::Generator::TestStrategy->new( 59: schema => \%schemas, 60: thresholds => { confidence => 'high' }, 61: ); 62: 63: =head3 Arguments 64: 65: =over 4 66: 67: =item * C<schema> 68: 69: A hashref of method name to schema hashref. Optional â defaults to 70: an empty hashref. 71: 72: =item * C<thresholds> 73: 74: A hashref of threshold configuration. Optional â defaults to 75: C<< { confidence => 'medium' } >>. 76: 77: =back 78: 79: =head3 Returns 80: 81: A blessed hashref. 82: 83: =head3 API specification 84: 85: =head4 input 86: 87: { 88: schema => { type => HASHREF, optional => 1 }, 89: thresholds => { type => HASHREF, optional => 1 }, 90: } 91: 92: =head4 output 93: 94: { 95: type => OBJECT, 96: isa => 'App::Test::Generator::TestStrategy', 97: } 98: 99: =cut 100: 101: sub new { 102: my ($class, %args) = @_; 103: return bless { 104: schema => $args{schema} || {}, 105: thresholds => $args{thresholds} || { confidence => $DEFAULT_CONFIDENCE }, 106: plans => {}, 107: }, $class; 108: } 109: 110: =head2 generate_plan 111: 112: Generate a test plan for all methods in the schema and return it as 113: a hashref mapping method names to plan hashrefs. 114: 115: my $strategy = App::Test::Generator::TestStrategy->new( 116: schema => \%schemas, 117: ); 118: my $plan = $strategy->generate_plan; 119: 120: for my $method (keys %{$plan}) { 121: print "$method: ", join(', ', keys %{ $plan->{$method} }), "\n"; 122: } 123: 124: =head3 Arguments 125: 126: None beyond C<$self>. 127: 128: =head3 Returns 129: 130: A hashref mapping method names to test plan hashrefs, each containing 131: boolean flags for the test types that should be generated. 132: 133: =head3 API specification 134: 135: =head4 input 136: 137: { 138: self => { type => OBJECT, isa => 'App::Test::Generator::TestStrategy' }, 139: } 140: 141: =head4 output 142: 143: { 144: type => HASHREF, 145: keys => { 146: '*' => { type => HASHREF }, 147: }, 148: } 149: 150: =cut 151: 152: sub generate_plan { ●153 → 155 → 169●153 → 155 → 0 153: my $self = $_[0]; 154: 155: for my $method (keys %{ $self->{schema} }) { 156: my $schema = $self->{schema}{$method}; 157: 158: # Extract analysis metadata from the schema â note that 159: # $schema is already the per-method hashref so we access 160: # _analysis directly, not via the method name key again 161: my $analysis = $schema->{_analysis} || {}; 162: my $effects = $analysis->{side_effects} || {}; 163: my $deps = $analysis->{dependencies} || {}; 164: 165: # Generate and store the plan for this method 166: $self->{plans}{$method} = $self->_plan_for_method($schema); 167: } 168: ●169 → 169 → 0 169: return $self->{plans};Mutants (Total: 2, Killed: 2, Survived: 0)
170: } 171: 172: # -------------------------------------------------- 173: # _plan_for_method 174: # 175: # Determine which test types should be 176: # generated for a single method based on 177: # its schema metadata. 178: # 179: # Entry: $schema - the per-method schema hashref 180: # 181: # Exit: Returns a hashref of test type flags. 182: # Always contains at least basic_test => 1. 183: # 184: # Side effects: None. 185: # 186: # Notes: All string comparisons use // '' guards 187: # to avoid uninitialized value warnings 188: # when schema fields are absent. 189: # -------------------------------------------------- 190: sub _plan_for_method { ●191 → 199 → 207●191 → 199 → 0 191: my ($self, $schema) = @_; 192: 193: my %plan; 194: 195: # -------------------------------------------------- 196: # Context-aware returns need both scalar and list 197: # context tests to verify correct behaviour in each 198: # -------------------------------------------------- 199: if($schema->{output}{_context_aware}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
200: $plan{$TEST_CONTEXT} = 1; 201: } 202: 203: # -------------------------------------------------- 204: # Accessor detection â choose test types based on 205: # whether the method is a getter, setter, or both 206: # -------------------------------------------------- ●207 → 207 → 241●207 → 207 → 0 207: if($schema->{accessor} && scalar keys %{ $schema->{accessor} }) {
Mutants (Total: 1, Killed: 1, Survived: 0)
208: my $acc_type = $schema->{accessor}{type} // ''; 209: 210: if($acc_type eq $ACCESSOR_GETTER) {
Mutants (Total: 1, Killed: 1, Survived: 0)
211: # Boolean getters are predicates and need 212: # truthy/falsy tests in addition to getter tests 213: if(($schema->{output}{type} // '') eq $TYPE_BOOLEAN) {
214: $plan{$TEST_PREDICATE} = 1; 215: } 216: $plan{$TEST_GETTER} = 1; 217: 218: } elsif($acc_type eq $ACCESSOR_SETTER) { 219: $plan{$TEST_SETTER} = 1; 220: 221: } elsif($acc_type eq $ACCESSOR_GETSET) { 222: # For getset accessors, check the input parameter 223: # type to determine if object injection or boolean 224: # set tests are more appropriate 225: my ($param) = grep { !/^_/ } keys %{ $schema->{input} || {} }; 226: my $param_type = ($param && $schema->{input}{$param}{type}) // ''; 227: 228: if($param_type eq $TYPE_OBJECT) {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: $plan{$TEST_OBJECT_INJECT} = 1; 230: } elsif($param_type eq $TYPE_BOOLEAN) { 231: $plan{$TEST_BOOLEAN_SET} = 1; 232: } 233: $plan{$TEST_GETSET} = 1; 234: } 235: } 236: 237: # -------------------------------------------------- 238: # Void return type â verify the method returns nothing 239: # and does not accidentally return a useful value 240: # -------------------------------------------------- ●241 → 241 → 249●241 → 241 → 0 241: if(($schema->{output}{type} // '') eq $TYPE_VOID) {
Mutants (Total: 1, Killed: 1, Survived: 0)
242: $plan{$TEST_VOID} = 1; 243: } 244: 245: # -------------------------------------------------- 246: # Error handling â verify error return conventions 247: # are tested explicitly 248: # -------------------------------------------------- ●249 → 249 → 258●249 → 249 → 0 249: if($schema->{output}{_error_return}
Mutants (Total: 1, Killed: 1, Survived: 0)
250: || $schema->{output}{success_failure_pattern}) { 251: $plan{$TEST_ERROR_HANDLING} = 1; 252: } 253: 254: # -------------------------------------------------- 255: # Boundary hints from YAML test configuration â 256: # generate boundary/equivalence class tests 257: # -------------------------------------------------- ●258 → 258 → 266●258 → 258 → 0 258: if($schema->{_yamltest_hints} && keys %{ $schema->{_yamltest_hints} }) {
Mutants (Total: 1, Killed: 1, Survived: 0)
259: $plan{$TEST_BOUNDARY} = 1; 260: } 261: 262: # -------------------------------------------------- 263: # Method chaining â verify that $self is returned 264: # and that calls can be chained 265: # -------------------------------------------------- ●266 → 266 → 274●266 → 266 → 0 266: if($schema->{output}{_returns_self}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
267: $plan{$TEST_CHAINING} = 1; 268: } 269: 270: # -------------------------------------------------- 271: # Boolean output â needs predicate tests regardless 272: # of whether an accessor was detected 273: # -------------------------------------------------- ●274 → 274 → 282●274 → 274 → 0 274: if(($schema->{output}{type} // '') eq $TYPE_BOOLEAN) {
Mutants (Total: 1, Killed: 1, Survived: 0)
275: $plan{$TEST_PREDICATE} = 1; 276: } 277: 278: # -------------------------------------------------- 279: # Always generate at least a basic call test even 280: # if no other test types were identified 281: # -------------------------------------------------- ●282 → 284 → 0 282: $plan{$TEST_BASIC} = 1 unless %plan; 283: 284: return \%plan; 285: } 286: 287: 1;