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 )