lib/App/Test/Generator/TestStrategy.pm

Structural Coverage (Approximate)

TER1 (Statement): 100.00%
TER2 (Branch): 86.67%
TER3 (LCSAJ): 100.0% (8/8)
Approximate LCSAJ segments: 31

LCSAJ Legend

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.

Mutant Testing Legend

Survived (tests missed this) Killed (tests detected this) No mutation
    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_method

Mutants (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:

Mutants (Total: 1, Killed: 0, Survived: 1)
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: 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;