PowerShell AST ile Pester Etiketlerini Keşfetmek


Geçen yılın sonunda çözdüğüm gibi, 2022'de Pester ile daha fazlasını yapacağım. Pester 5 ile biraz daha rahat olmaya başlıyorum ve testlerimin karmaşıklığı arttıkça etiket kullanımını benimsiyorum. Farklı Pester test öğelerine etiketler ekleyebilirsiniz. Daha sonra bir Pester testini başlattığınızda, belirli testleri filtreleyebilir ve yalnızca etiketlerine göre çalıştırabilirsiniz. Çalışırken bir test komut dosyasındaki tüm etiketleri tanımlamanın faydalı olacağını fark ettim. Biraz çalıştıktan sonra bir PowerShell fonksiyonu buldum.

İlk düşüncem normal ifadeler kullanmak ve -etiketli satırları aramaktı.

$path = "C:\scripts\psfunctiontools\tests\psfunctiontools.tests.ps1"
[regex]$rx="(?<=-tag\s)(\w(,)?(\s)?)*"
$c = Get-Content $path
#make all tags lower case
([System.Text.RegularExpressions.Regex]::Matches($c,$rx,"IgnoreCase")).foreach({($_.value -split ",").Trim().toLower()}) |
Select-Object -Unique | Sort-Object

Bu kod işe yarar ancak biraz kaba kuvvet yaklaşımıdır. Birden fazla etiketi ve sözdizimi varyasyonunu hesaba katmak zorunda kaldım. Böylece PowerShell AST'ye geri döndüm.

Dosyayı ayrıştırmak yeterince basittir.

$AST = [System.Management.Automation.Language.Parser]::ParseFile(
    $path,
    [ref]$astTokens,
    [ref]$astErr
)

Bir Pester testinde, etiketler teknik olarak Tanımla veya It gibi bir Pester komutu için -Tag parametresinin bir parçasıdır. Bu, bu komut öğelerini keşfedebileceğim anlamına gelir.

$tags = $AST.FindAll({
    $args[0] -is [System.Management.Automation.Language.CommandParameterAst] -AND $args[0].parametername -eq 'tag' },
$true
)

Şimdi işin zor kısmına geçelim. İşte örnek bir nesne.

Argüman özelliğinin ebeveynde görebileceğiniz "yardım" ve "kabul" değerlerini içermesi güzel olurdu. Ama bu çok kolay olurdu. Ancak Parent özelliğini ve onun CommandElements özelliğini kullanabilirim.

Size son iki unsuru gösteriyorum. Tag parametre değeri parametre elemanından sonraki elemandır. Bu konuyu daha net hale getirebilir.

for ($i = 0;$i -lt $tags[0].Parent.CommandElements.count;$i++) {
    if ($tags[0].parent.CommandElements[$i].parametername -eq 'tag') {
    $tags[0].parent.CommandElements[$i+1].extent.text.split(",").trim().tolower()
    }
}

For numaralandırma döngüsünü kullanarak tüm komut öğeleri arasında döngü yapabilirim. Parametreadı özelliği 'tag' ise, sonraki öğeyi ($i+1) alın ve Extent.Text özelliğini kullanın. Bu bir dizi olabilir, bu yüzden onu böleceğim ve fazladan boşlukları kırpacağım.

Şimdi yapmam gereken tek şey geri adım atmak ve bunu iç içe geçmiş For döngüleri içeren tüm etiket öğeleri için yapmak.

Son adım benzersiz öğeler elde etmektir. Gerçekten benzersiz değerler elde ettiğimden emin olmak için her girişi küçük harfle yazdığıma da dikkat edin.

Get-PesterTag

Bu temel işlevsellik ile bunun etrafında bir PowerShell işlevi oluşturdum.

Function Get-PesterTag {
    [cmdletbinding()]
    [OutputType("pesterTag")]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = "Specify a Pester test file",
            ValueFromPipeline
        )]
        [ValidateScript({
            #validate file exits
            if (Test-Path $_) {
                #now test for extension
                if ($_ -match "\.ps1$") {
                    $True
                }
                else {
                    Throw "The filename must end in '.ps1'."
                }
            }
            else {
                Throw "Cannot find file $_."
            }
        })]
        [string]$Path
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
        New-Variable astTokens -Force
        New-Variable astErr -Force
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting tags from $Path "
        $AST = [System.Management.Automation.Language.Parser]::ParseFile(
            $path,
            [ref]$astTokens,
            [ref]$astErr
        )
        $tags = $AST.FindAll({
                $args[0] -is [System.Management.Automation.Language.CommandParameterAst] -AND $args[0].parametername -eq 'tag' },
            $true
        )

        $all = for ($j = 0; $j -lt $tags.count; $j++) {
            for ($i = 0; $i -lt $tags[$j].Parent.CommandElements.count; $i++) {
                if ($tags[$j].parent.CommandElements[$i].parametername -eq 'tag') {
                    $tags[$j].parent.CommandElements[$i + 1].extent.text.split(",").trim().tolower()
                }
            }
        }
        if ($all) {
            [pscustomobject]@{
                PSTypename = "pesterTag"
                Path       = $Path
                Tags       = $all | Select-Object -Unique | Sort-Object
            }
        }
        else {
            Write-Warning "No tags found in $Path"
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end

} #close Get-PesterTag

İşlev, işlem hattına dosya adını içeren özel bir nesne yazar.

Yanlış yazılmış bir etiket adını keşfettiğim için bu çok yardımcı oldu. İşlev, yolda parametre doğrulamayı içerir. Doğrulama komut dosyası önce dosyanın var olduğunu ve ardından .ps1 ile bittiğini doğrular. Bunları iki ayrı doğrulama testine ayırırdım ama birleştirmemek için hiçbir neden yok. Yapabileceğim diğer tek şey pesterTag nesne türü için özel bir format dosyası oluşturmak. Ama bunu sana bırakacağım. Testlerim üzerinde çalışmaya geri dönmem gerekiyor.

İlgili makaleler