App-Test-Generator

 view release on metacpan or  search on metacpan

lib/App/Test/Generator/SchemaExtractor.pm  view on Meta::CPAN

=item * B<Accessor Method Detection>

Automatically identifies getter, setter, and combined accessor methods
by analyzing common patterns like C<return $self-E<gt>{property}> and
C<$self-E<gt>{property} = $value>.

=item * B<Params::Get Integration>

Recognises parameters extracted via C<Params::Get::get_params('key', \@_)>,
treating the quoted key as a named parameter equivalent to a traditional
C<my ($self, $key) = @_> signature.  This prevents false positives from
C<--strict-pod> when the method body never declares an explicit C<$key>
variable.

=item * B<Direct-Index Self Style>

Recognises C<my $self = $_[0]> as a valid method-invocant pattern.  Parameters
at C<$_[1]>, C<$_[2]>, etc. are extracted as positional parameters.  Without
this, the signature fallback would incorrectly pick up C<my (...) = @_> from
inner closures defined in the method body and treat those variables as the
outer method's parameters.

=item * B<Boolean Return Inference>

Detects boolean-returning methods through multiple signals:
  - Method name patterns (is_*, has_*, can_*)
  - Return patterns (consistent 1/0 returns)
  - POD descriptions ("returns true on success")
  - Ternary operators with boolean results

=item * B<Context Awareness>

Identifies methods that use C<wantarray> and can return different
values in scalar vs list context.

=item * B<Object Lifecycle Management>

Detects instance methods requiring object instantiation and
automatically adds the C<new> field to schemas.

=item * B<Enhanced Object Detection>

The extractor includes sophisticated object detection capabilities that go beyond simple instance method identification:

=over 4

=item * B<Factory Method Recognition>

Automatically identifies methods that create and return object instances, such as methods named C<create_*>, C<make_*>, C<build_*>, or C<get_*>. Factory methods are correctly classified as class methods that don't require pre-existing objects for tes...

=item * B<Singleton Pattern Detection>

Recognizes singleton patterns through multiple signals: method names like C<instance> or C<get_instance>, static variables holding instance references, lazy initialization patterns (C<$instance ||= new()>), and consistent return of the same instance ...

=item * B<Constructor Parameter Analysis>

Examines C<new> methods to determine required and optional parameters, validation requirements, and default values. This enables test generators to provide appropriate constructor arguments when object instantiation is needed.

=item * B<Inheritance Relationship Handling>

Detects parent classes through C<use parent>, C<use base>, and C<@ISA> declarations. Identifies when methods use C<SUPER::> calls and determines whether the current class or a parent class constructor should be used for object instantiation.

=item * B<External Object Dependency Detection>

Identifies when methods create or depend on objects from other classes, enabling proper test setup with mock objects or real dependencies.

=back

These enhancements ensure that generated test schemas accurately reflect the object-oriented structure of the code, leading to more meaningful and effective test generation.

=back

=head2 Confidence Scoring

Each generated schema includes detailed confidence assessments:

=over 4

=item * B<High Confidence>

Multiple independent analysis sources converge on consistent,
well-constrained parameters with explicit validation logic and
comprehensive documentation.

=item * B<Medium Confidence>

Reasonable evidence from code patterns or partial documentation,
but may lack comprehensive constraints or have some ambiguities.

=item * B<Low Confidence>

Minimal evidence - primarily based on naming conventions,
default assumptions, or single-source analysis.

=item * B<Very Low Confidence>

Barely any detectable signals - schema should be thoroughly
reviewed before use in test generation.

=back

=head2 Use Cases

=over 4

=item * B<Automated Test Generation>

Generate comprehensive test suites with L<App::Test::Generator> using
extracted schemas as input. The schemas provide the necessary structure
for generating both positive and negative test cases.

=item * B<API Documentation Generation>

Supplement existing documentation with automatically inferred interface
specifications, parameter requirements, and return types.

=item * B<Code Quality Assessment>

Identify methods with poor documentation, inconsistent parameter handling,
or unclear interfaces that may benefit from refactoring.

lib/App/Test/Generator/SchemaExtractor.pm  view on Meta::CPAN

		$instance_info{explicit_self} = 1;
		$instance_info{confidence} = 'high';
	}

	# Pattern 1b: my $self = $_[0];  (direct-index style)
	elsif ($method_body =~ /my\s+\$self\s*=\s*\$_\[0\]/) {
		$instance_info{explicit_self} = 1;
		$instance_info{confidence} = 'high';
	}

	# Pattern 2: my $self = shift;
	elsif ($method_body =~ /my\s+\$self\s*=\s*shift/) {
		$instance_info{shift_self} = 1;
		$instance_info{confidence} = 'high';
	}

	# Pattern 3: Uses $self->something (including hash/array access)
	# This catches $self->{value} and $self->[0] as well as $self->method()
	elsif ($method_body =~ /\$self\s*->\s*(\w+|[\{\[])/) {
		$instance_info{uses_self} = 1;
		$instance_info{confidence} = 'medium';
	}

	# Pattern 4: Accesses object data: $self->{...}, $self->[...]
	if ($method_body =~ /\$self\s*->\s*[\{\[]/) {
		$instance_info{accesses_object_data} = 1;
		$instance_info{confidence} = 'high' unless $instance_info{confidence} eq 'high';
	}

	# Pattern 5: Calls other instance methods on $self
	if ($method_body =~ /\$self\s*->\s*(\w+)\s*\(/s) {
		$instance_info{calls_instance_methods} = [];
		while ($method_body =~ /\$self\s*->\s*(\w+)\s*\(/g) {
			push @{$instance_info{calls_instance_methods}}, $1;
		}
		$instance_info{confidence} = 'high' if @{$instance_info{calls_instance_methods}};
	}

	# Pattern 6: Method name suggests instance method (not perfect but helpful)
	if ($method_name =~ /^_/ && $method_name !~ /^_new/) {
		# Private methods are usually instance methods
		$instance_info{private_method} = 1;
		$instance_info{confidence} = 'low' unless exists $instance_info{confidence};
	}

	return \%instance_info if keys %instance_info;
	return undef;
}

# --------------------------------------------------
# _check_inheritance_for_constructor
#
# Purpose:    Determine whether the current package
#             uses an inherited constructor from a
#             parent class, by examining use parent,
#             use base, and @ISA declarations.
#
# Entry:      $current_package - current package
#                                name string.
#             $method_body     - method source string
#                                (checked for SUPER::
#                                calls).
#
# Exit:       Returns an inheritance_info hashref
#             if any inheritance information is
#             found, or undef otherwise.
#             The hashref may contain:
#             parent_statements, isa_array,
#             uses_super, calls_super_new,
#             has_own_constructor,
#             use_parent_constructor, parent_class.
#
# Side effects: None.
# --------------------------------------------------
sub _check_inheritance_for_constructor {
	my ($self, $current_package, $method_body) = @_;

	my $doc = $self->{_document};
	return undef unless $doc;

	my %inheritance_info;

	# 1. Look for parent/base statements
	my @parent_classes;

	# Find all 'use parent' or 'use base' statements
	my $includes = $doc->find('PPI::Statement::Include') || [];
	foreach my $inc (@$includes) {
		my $content = $inc->content;
		if ($content =~ /use\s+(parent|base)\s+['"]?([\w:]+)['"]?/) {
			push @parent_classes, $2;
			$inheritance_info{parent_statements} = \@parent_classes;
		}
		# Also check for multiple parents: use parent qw(Class1 Class2)
		if ($content =~ /use\s+(parent|base)\s+qw?[\(\[]?(.+?)[\)\]]?;/) {
			my $parents = $2;
			my @multi_parents = split /\s+/, $parents;
			push @parent_classes, @multi_parents;
			$inheritance_info{parent_statements} = \@parent_classes;
		}
	}

	# 2. Look for @ISA assignments (with or without 'our')
	my $isas = $doc->find('PPI::Statement::Variable') || [];
	foreach my $isa (@$isas) {
		my $content = $isa->content();
		# Match both "our @ISA = qw(...)" and "@ISA = qw(...)"
		if ($content =~ /(?:our\s+)?\@ISA\s*=\s*qw?[\(\[]?(.+?)[\)\]]?/) {
			my $parents = $1;
			my @isa_parents = split(/\s+/, $parents);
			push @parent_classes, @isa_parents;
			$inheritance_info{isa_array} = \@isa_parents;
		}
	}

	# Also look for @ISA in regular statements
	my $statements = $doc->find('PPI::Statement') || [];
	foreach my $stmt (@$statements) {
		my $content = $stmt->content;
		if ($content =~ /\@ISA\s*=\s*qw?[\(\[]?(.+?)[\)\]]?/) {
			my $parents = $1;
			my @isa_parents = split(/\s+/, $parents);
			push @parent_classes, @isa_parents;
			$inheritance_info{isa_array} = \@isa_parents;
		}
	}

	# 3. Check if method uses SUPER:: calls
	if ($method_body && $method_body =~ /SUPER::/) {
		$inheritance_info{uses_super} = 1;
		if ($method_body =~ /SUPER::new/) {
			$inheritance_info{calls_super_new} = 1;
		}
	}

	# 4. Check if current package has its own new method
	my $has_own_new = $doc->find(sub {
		$_[1]->isa('PPI::Statement::Sub') &&
		$_[1]->name eq 'new'
	});

	if ($has_own_new) {
		$inheritance_info{has_own_constructor} = 1;
	} elsif (@parent_classes) {
		# No own constructor, but has parents - might need parent constructor
		$inheritance_info{use_parent_constructor} = 1;
		$inheritance_info{parent_class} = $parent_classes[0];	# Use first parent
	}

	return \%inheritance_info if keys %inheritance_info;
	return undef;
}

# --------------------------------------------------
# _detect_constructor_requirements
#
# Purpose:    Analyse the new() method of the
#             current or target package to determine
#             what parameters the constructor
#             requires, including required and
#             optional parameters and their defaults.
#
# Entry:      $current_package - the package being
#                                analysed.
#             $target_package  - the package whose
#                                constructor will
#                                be called (may
#                                differ from current
#                                for inherited
#                                constructors).
#
# Exit:       Returns a requirements hashref on
#             success, or undef if no new() method
#             is found. For external classes,
#             returns a minimal hashref with
#             external_class => 1.
#
# Side effects: None.
# --------------------------------------------------
sub _detect_constructor_requirements {
	my ($self, $current_package, $target_package) = @_;

	my $doc = $self->{_document};
	return undef unless $doc;

	# If target is different from current, we can't analyze it
	# (external class, parent class in different file)
	if ($target_package ne $current_package) {
		return {
			external_class => 1,
			package => $target_package,



( run in 0.849 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )