lib/App/Test/Generator/TestStrategy.pm

Structural Coverage (Approximate)

TER1 (Statement): 98.11%
TER2 (Branch): 83.33%
TER3 (LCSAJ): 100.0% (18/18)
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.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 → 169153 → 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 → 207191 → 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 → 241207 → 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) {

Mutants (Total: 1, Killed: 0, Survived: 1)
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: 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 → 249241 → 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 → 258249 → 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 → 266258 → 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 → 274266 → 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 → 282274 → 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;