Powershell Unit Test Code Coverage Implementation

Unit Test framework:

https://github.com/pester/Pester

C:\PS>./bin/pester.bat
just run pester.bat and all Unit Tests in *.tests.ps1 will be performed.


Code Coverage Implementation with powershell break point:

1. Line coverage:



function Set-LineBreakPoints
{
param(
[string[]]$scripts,
[HashTable]$profiler
)

foreach($script in $scripts)
{
Set-LineBreakPointFor1Script -scriptFullName $script -profiler $profiler
}
}


function Set-LineBreakPointFor1Script
{
param(
[string]$scriptFullName,
[HashTable]$profiler
)

if ($profiler.ContainsKey($key_lineCoverage) -eq $false)
{
$profiler.$key_lineCoverage = @{}
}


$profiler.$key_lineCoverage.$scriptFullName = @{}
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineCount" = 0
$profiler.$key_lineCoverage.$scriptFullName."TotalLineCount" = 0
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineNumbers" = @()


$scriptCode = Get-Content $scriptFullName
$profiler.$key_lineCoverage.$scriptFullName."TotalLineCount" = $scriptCode.Length


for( $i = 1; $i -le $scriptCode.Length; $i++)
{
# Not Work # $bp = Set-PSBreakpoint -Script $scriptFullName -Line $i -Action ([ScriptBlock]::Create("`$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineNumbers`" += $i; `$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineCount`"=`$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineNumbers`".Length; Echo (`"Covered Lines No.: `" + `$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineNumbers`") | Add-Content D:\jobs\log\log0929_2.txt;"))
# works well. # $bp = Set-PSBreakpoint -Script $scriptFullName -Line $i -Action ([ScriptBlock]::Create("`$global:checkPoint=`"$i+ $scriptFullName`"")) # It 
# Not Work # $bp = Set-PSBreakpoint -Script $scriptFullName -Line $i -Action ([ScriptBlock]::Create("`$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineNumbers`" += $i; `$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`" | Add-Content D:\jobs\log\log0929_2.txt;"))
# Not Work # $bp = Set-PSBreakpoint -Script $scriptFullName -Line $i -Action ([ScriptBlock]::Create("`$global:profiler.`"$key_lineCoverage`".`"$scriptFullName`".`"CoveredLineNumbers`" += $i"))
Set-PSBreakpoint -Script $scriptFullName -Line $i -Action ([ScriptBlock]::Create("`"$scriptFullName,$i`" | Add-Content -path $coverageCsv")) | Out-Null
}


Write-Output "Finished setting break point for $scriptFullName ."
}




function GenerateHtmlReport1Script
{
param(
[string]$scriptName,
[HashTable]$coverageData,
[string]$reportFile
)

$coveredBG = "LightGreen"
$missedBG = "DarkSalmon"


if (-not (Test-Path $scriptName -PathType Leaf))
{
Write-Error "Could not found script $scriptName!!!"
return
}


$scriptFullName = (Get-Item $scriptName).FullName
$scriptCode = Get-Content $scriptFullName
$scriptLineCount = $scriptCode.Length


Echo "Generating html report for $scriptFullName"


$key_lineCoverage = [CoverageType]::LineCoverage -as [string]
$key_funcCoverage = [CoverageType]::FunctionCoverage -as [string]

if ((-not $coverageData.Contains($key_lineCoverage)) -and (-not $coverageData.Contains($key_funcCoverage)))
{
Write-Error "Line coverage data and function coverage data is not found, pls check!!!"
return
}
else
{
if ($coverageData.Contains($key_lineCoverage))
{
if (-not $coverageData.$key_lineCoverage.Contains($scriptFullName))
{
Write-Warning "No line coverage data is available for script $scriptFullName !!!"
$coveredLines = @()
#return
}
else
{
$coveredLines = @($coverageData.$key_lineCoverage.$scriptFullName."CoveredLineNumbers" | Sort-Object -Unique)
Echo "$scriptFullName coveredLines is : $coveredLines"
}

$coveredLineCount = $coveredLines.Length
}
else
{
Write-Warning "No line coverage data available."
}

if ($coverageData.Contains($key_funcCoverage))
{
if (-not $coverageData.$key_funcCoverage.Contains($scriptFullName))
{
Write-Warning "No function coverage data is available for script $scriptFullName !!!"
$coveredFunctions = @()
}
else
{
$coveredFunctions = @($coverageData.$key_funcCoverage.$scriptFullName | Sort-Object -Unique)
}

$coveredFunctionCount = $coveredFunctions.Length
}
else
{
Write-Warning "No function coverage data available."
}
}

if ($scriptLineCount -eq 0)
{
Write-Warning "Empty script - $scriptFullName !!!"
$lineCoverageRate = "0 %"
}
else
{
$lineCoverageRate = ($coveredLineCount / $scriptLineCount).ToString("P2")
}

$totalFunctionCount = $coverageData.$key_funcCoverage."FunctionCount".$scriptFullName


if ($totalFunctionCount -eq 0 -or $totalFunctionCount -eq $null)
{
Write-Warning "There are no function in script $scriptFullName ..."
$functionCoverageRate = "0 %"
}
else
{
$functionCoverageRate = ($coveredFunctionCount / $totalFunctionCount).ToString("P2")
}

$backgroundColors = 1..($scriptCode.Length)
for($i = 0; $i -lt $scriptLineCount; $i++)
{
if ($coveredLines -contains ($i + 1))
{
$backgroundColors[$i] = $coveredBG
}
else
{
$backgroundColors[$i] = $missedBG
}
}


if ($scriptLineCount -ne $backgroundColors.Length)
{
Write-Error "scriptCode.Length should be same as colorSetting.Length !!!"
return
}


Add-Content "<style>
table,th,td
{
border:1px solid black;
border-collapse:collapse
}


th
{
text-align: left;
background-color: Azure;
}
</style>


<Table Width=80% Align='Center' Border='1,1,1,1'>
<tr>
<th colspan='2'>Code Location: $scriptFullName, <BR>=== Line Coverage Rate: $lineCoverageRate <BR>=== Covered Line Count: $coveredLineCount <BR>=== Total Line Count: $scriptLineCount <BR>=== Function Coverage Rate: $functionCoverageRate <BR>=== Covered Functions Count: $coveredFunctionCount <BR>=== Total Function Count: $totalFunctionCount</th>
</tr>
" -Path $reportFile


for($i = 0; $i -lt $scriptLineCount; $i++)
{
Echo ("<TR><TD bgcolor='" + $backgroundColors[$i] + "'>" + ($i + 1) + "</TD><TD>" + $scriptCode[$i].Replace(" ", "&nbsp").Replace("`t","&nbsp&nbsp&nbsp&nbsp") + "</TD></TR>") | Add-Content $reportFile
}

Add-Content -Value "</Table><BR>" -Path $reportFile
Write-Output "Completed processing $scriptName !"
Write-Output "--> Generated $reportFile !!!"
}




function GenerateMultipleHtmlReport
{
param(
[string[]]$scriptCollection,
[HashTable]$coverageData,
[string]$reportFolder
)

foreach($script in $scriptCollection)
{
$name = (Get-Item $script).BaseName
GenerateHtmlReport1Script -scriptName $script -coverageData $coverageData -reportFile $reportFolder\${name}.html
}

Write-Output "==> Finished generating all html reports!!!"
}


function GenerateOverviewReport
{
param(
[HashTable]$coverageData,
[string]$reportFolder
)


$totalLinesCountAll = 0
$totalCoveredLinesCount = 0
$reportFile = "$reportFolder\CoverageReport.html"

$style = "<style>
table
{
margin:0 auto;
width:80%;
}

table,th,td
{
border:1px solid black;
border-collapse:collapse
}

th
{
text-align: left;
background-color: Azure;
}
a
{
color:black
}
</style>
"


"<table>" | Add-Content $reportFile
foreach($script in $profiler.LineCoverage.Keys)
{
$fileName = Split-Path $script -Leaf
if ($fileName -ne $null -and $fileName.Trim() -ne "")
{
$fileName = $fileName.Replace('.ps1','')
}
else
{
"fileName is empty!!!!!!"
}

$totalLinesCountAll += $profiler.LineCoverage."$script".TotalLineCount;
$totalCoveredLinesCount += $profiler.LineCoverage."$script".CoveredLineCount; 

$coverageNumber = $profiler.LineCoverage."$script".CoveredLineCount/$profiler.LineCoverage."$script".TotalLineCount
$coveragePercent = $coverageNumber

if ($coveragePercent -gt 0)
{
$coverText = ("<B>"+ $coveragePercent.ToString("P2") + "</B>")
}
else
{
$coverText = $coveragePercent.ToString("P2")
}

("<tr><td><a href='.\${fileName}.html' target='new'>$script</a></td><td>$coverText</td></tr>") | Add-Content $reportFile
}
"</table>" | Add-Content $reportFile

$reportBody = Get-Content "$reportFile"


$reportHeader = ("<table><th colspan=2><B>OverAll Coverage Percentage:    " + ($totalCoveredLinesCount/$totalLinesCountAll).ToString("P2") + "</B></th></table>")

$style + $reportHeader + $reportBody | Set-Content $reportFile

Write-Output "==> Finished generating overview report!!"
}


# Main Process Starts here.
if ("CoverageType" -as [Type])
{
Echo "Type CoverageType already exists, need open a new session to resolve this."
}
else
{
Add-Type -TypeDefinition @"
public enum CoverageType
{
   LineCoverage = 0,
   FunctionCoverage = 1,
   BranchCoverage = 2
}
"@
}


$key_lineCoverage = [CoverageType]::LineCoverage -as [string] # convert to string type.
$key_funcCoverage = [CoverageType]::FunctionCoverage -as [string] # convert to string type.


$profiler = @{}
$files = Get-ChildItem -Recurse -Path "$workSpaceRoot\ToBeTested\" | Where-Object { $_.Extension -eq ".ps1"  } | % { $_.FullName }
Set-LineBreakPoints -scripts $files -profiler $profiler

$timestamp = Get-Date -f yyyyMMddhhmmss
$coverageCsv = "$workSpaceRoot\coverage.${timestamp}.csv"


### Perform UnitTest here.
#......
### Perform UnitTest here.

$reportFolder = "$workSpaceRoot\CoverageReports\"
[void](New-Item $reportFolder -ItemType container)

$csvObj = Import-Csv -Path $coverageCsv -Header "script","lineNo"


foreach($scriptFullName in $files)
{
"Checking $scriptFullName ..."
if ($profiler.ContainsKey($key_lineCoverage) -eq $false)
{
$profiler.$key_lineCoverage = @{}
}

$profiler.$key_lineCoverage.$scriptFullName = @{}
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineCount" = 0
$profiler.$key_lineCoverage.$scriptFullName."TotalLineCount" = 0
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineNumbers" = @()


$profiler.$key_lineCoverage.$scriptFullName."TotalLineCount" = (@(Get-Content $scriptFullName)).Length


foreach($entry in ($csvObj | Where-Object { $_.script -eq "$scriptFullName" }))
{
if ($profiler.$key_lineCoverage.$scriptFullName."CoveredLineNumbers" -contains $entry.lineNo)
{
"line $entry.lineNo already added, skipped."
}
else
{
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineNumbers" += $entry.lineNo
$profiler.$key_lineCoverage.$scriptFullName."CoveredLineCount" = $profiler.$key_lineCoverage.$scriptFullName."CoveredLineNumbers".Length
}
}
}

GenerateMultipleHtmlReport -scriptCollection $files -coverageData $profiler -reportFolder $reportFolder
GenerateOverviewReport -coverageData $profiler -reportFolder $reportFolder


2. Function coverage

(

This is not perfect! 

a. some script failed with when invoking ([ScriptBlock]::Create($scriptCode), should be defect of Powershell

b. when we invoke New-Module cmdlet, we actually run the script, this is not what we want, we actually need static code analysis and not run it.

)


$scriptCode = Get-Content $scriptFullName
$tempModule = New-Module -ScriptBlock ([ScriptBlock]::Create($scriptCode))
$functionsList = $tempModule.ExportedFunctions.Keys

foreach( $f in $functionsList )
{
[void](Set-PSBreakpoint -Script $scriptFullName -Command $f -Action ([ScriptBlock]::Create("`$global:profiler.`"$key_funcCoverage`".`"$scriptFullName`" += `"$f`"")))
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值