Day 24 我們把前面累積的 Function:
Write-Log
Get-ServerHealth
Get-HealthEventType
Save-HealthState
開始整理成自己的:
SysAdminToolkit
最後可以:
Import-Module SysAdminToolkit
直接使用:
Get-ServerHealth
這代表我們的 PowerShell 已經從:
單一 Script
慢慢變成:
可重複使用的 Tool
但接下來會出現一個非常實際的問題。
假設現在:
SysAdminToolkit 1.0.0
跑得很好。
某一天我修改:
Get-HealthEventType
想增加新的判斷。
原本:
Previous = Warning
Current = Critical
Escalated
結果程式改完之後,不小心變成:
Changed
如果我只是看著程式碼說:
看起來應該沒問題。
然後直接丟進排程,
可能隔天才發現:
Critical 沒有正確 Escalate
Notification 邏輯失效
State 判斷錯誤
所以今天要開始處理另一個非常重要的問題:
Testing
也就是:
怎麼證明修改程式之後,原本的功能還是正常?
今天會從最基本的:
Input
↓
Function
↓
Actual Result
↓
Expected Result
↓
Pass / Fail
開始。
最後再介紹 PowerShell 很重要的測試框架:
Pester
為什麼維運 Script 也需要測試?
很多人提到:
Unit Test
Integration Test
Automated Test
第一個想到的是:
Software Developer
但其實維運 Automation 一樣需要。
甚至有些情況更需要。
因為你的 Script 可能操作:
Active Directory
Windows Server
帳號權限
Service
File Server
Task Scheduler
如果程式寫錯,
影響的不是:
網頁按鈕顏色錯了
而可能是:
帳號判斷錯誤
權限處理錯誤
Server Status 判斷錯誤
錯誤通知沒有發出
尤其我們前面已經做到:
Task Scheduler
↓
凌晨自動執行
沒有人在旁邊看。
所以 Testing 開始變得非常重要。
最簡單的測試其實我們早就在做
假設:
Get-HealthEventType -PreviousStatus "Healthy"
-CurrentStatus "Critical"
得到:
NewAlert
NewAlert
NewAlert
所以
Pass
這本身就是測試。
只是目前:
靠人眼檢查。
先做最簡單的手動 Test
例如:
$Expected =
"NewAlert"
$Actual =
Get-HealthEventType -PreviousStatus "Healthy"
-CurrentStatus "Critical"
然後:
if ($Actual -eq $Expected) {
Write-Host "PASS"
}
else {
Write-Host "FAIL"
}
結果:
PASS
這已經是一個:
Automated Assertion
Assertion 是什麼?
簡單理解:
我預期某件事情應該成立
例如:
1 + 1 應該 = 2
對我們的 Module:
Healthy → Critical
應該 = NewAlert
NewAlert
NewAlert
Changed
測試失敗。
為什麼不要只測一個 Case?
因為 Get-HealthEventType 不只有:
Healthy → Critical
還有很多 Transition。
例如:
Healthy → Healthy
Critical → Critical
Critical → Healthy
Warning → Critical
Critical → Warning
Healthy → Unknown
Unknown → Healthy
假設我們只測:
Healthy → Critical
它正常,
不代表:
Critical → Healthy
沒有被改壞。
建立 Test Cases
例如:
$TestCases = @(
@{
Previous = "Healthy"
Current = "Critical"
Expected = "NewAlert"
}
@{
Previous = "Critical"
Current = "Critical"
Expected = "ExistingIssue"
}
@{
Previous = "Critical"
Current = "Healthy"
Expected = "Recovery"
}
@{
Previous = "Warning"
Current = "Critical"
Expected = "Escalated"
}
@{
Previous = "Critical"
Current = "Warning"
Expected = "Improved"
}
)
然後:
foreach ($Case in $TestCases) {
$Actual =
Get-HealthEventType `
-PreviousStatus $Case.Previous `
-CurrentStatus $Case.Current
if (
$Actual -eq
$Case.Expected
) {
Write-Host `
"PASS: $($Case.Previous) -> $($Case.Current)"
}
else {
Write-Host `
"FAIL: $($Case.Previous) -> $($Case.Current)"
Write-Host `
"Expected: $($Case.Expected)"
Write-Host `
"Actual: $Actual"
}
}
可能:
PASS: Healthy -> Critical
PASS: Critical -> Critical
PASS: Critical -> Healthy
PASS: Warning -> Critical
PASS: Critical -> Warning
這已經比:
修改完之後自己點幾次看看
可靠很多。
但自己寫 Test Framework 很快會變麻煩
接下來可能又需要:
統計幾個 Pass
幾個 Fail
顯示哪一個失敗
支援 Before / After
Mock 外部 Command
產生 Test Result
CI/CD 整合
最後很可能:
為了測試 PowerShell,我又寫了一套測試系統。
所以 PowerShell 已經有成熟的 Testing Framework:
Pester
Pester 是什麼?
Pester 是 PowerShell 常用的測試框架。
概念上可以寫:
Describe "Get-HealthEventType" {
It "Healthy to Critical should return NewAlert" {
$Result =
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Critical"
$Result |
Should -Be "NewAlert"
}
}
閱讀起來甚至很像英文:
Describe
Get-HealthEventType
It
Healthy to Critical should return NewAlert
然後:
Actual Result
Should Be
NewAlert
先確認 Pester
可以:
Get-Module -ListAvailable
Pester |
Sort-Object Version -Descending |
Select-Object `
Name,
Version,
Path
如果環境已有:
Name Version
Pester 5.x.x
就可以開始使用。
不同 Windows / PowerShell 環境可能帶有不同版本的 Pester,所以正式寫測試前最好先確認版本。
如果沒有 Pester
一般環境可以透過 PowerShell Gallery 安裝,例如:
Install-Module Pester
-Scope CurrentUser
如果是企業受管控電腦,可能不能直接連 PowerShell Gallery。
這時候應該依公司的:
Internal Repository
Package Policy
Software Deployment
方式安裝。
不要為了裝測試工具去繞過公司的安全政策。
建立 Tests 資料夾
Day 24 的專案:
SysAdmin-Automation/
│
├── Modules/
├── Config/
├── Scripts/
├── Reports/
├── Logs/
└── State/
今天加入:
Tests/
變成:
SysAdmin-Automation/
│
├── Modules/
│ └── SysAdminToolkit/
│
├── Config/
├── Scripts/
├── Tests/
├── Reports/
├── Logs/
└── State/
建立第一份 Test
例如:
Tests/
└── Get-HealthEventType.Tests.ps1
Pester 常見 Test File 名稱:
*.Tests.ps1
例如:
Get-HealthEventType.Tests.ps1
Get-ServerHealth.Tests.ps1
Write-Log.Tests.ps1
第一個 Pester Test
BeforeAll {
$ModulePath =
Join-Path `
$PSScriptRoot `
"..\Modules\SysAdminToolkit\SysAdminToolkit.psd1"
Import-Module `
$ModulePath `
-Force `
-ErrorAction Stop
}
Describe "Get-HealthEventType" {
It "Healthy to Critical should return NewAlert" {
$Result =
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Critical"
$Result |
Should -Be "NewAlert"
}
}
Describe
Describe "Get-HealthEventType" {
}
可以理解為:
我要測哪一個功能?
例如:
Get-HealthEventType
It
It "Healthy to Critical should return NewAlert" {
}
代表:
我要驗證其中一種具體行為。
例如:
Healthy
↓
Critical
↓
應該 NewAlert
Should -Be
$Result |
Should -Be "NewAlert"
代表:
Result 應該等於 NewAlert。
如果:
Actual = NewAlert
Pass。
如果:
Actual = Changed
Fail。
執行 Pester
可以:
Invoke-Pester `
-Path ".\Tests"
或:
Invoke-Pester `
-Path ".\Tests\Get-HealthEventType.Tests.ps1"
如果想看更詳細:
Invoke-Pester -Path ".\Tests"
-Output Detailed
可能看到:
Describing Get-HealthEventType
[+] Healthy to Critical should return NewAlert
Tests completed
Passed: 1
Failed: 0
現在故意把 Function 改壞
假設有人修改 Module:
原本:
if (
$PreviousStatus -eq "Healthy"
) {
return "NewAlert"
}
不小心改成:
if (
$PreviousStatus -eq "Healthy"
) {
return "Changed"
}
如果沒有 Test:
Import Module
→ 沒有 Error
Script
→ 可以執行
Task Scheduler
→ 也會跑
你可能完全不知道邏輯壞了。
但:
Invoke-Pester
會直接看到:
Failed
Expected:
NewAlert
Actual:
Changed
這就是 Testing 最大的價值之一:
程式可以執行,不代表程式邏輯是正確的。
把 Day 23 的 State Transition 全測一次
可以建立:
$Cases = @(
@{
Previous = $null
Current = "Healthy"
Expected = "InitialHealthy"
}
@{
Previous = $null
Current = "Critical"
Expected = "InitialIssue"
}
@{
Previous = "Healthy"
Current = "Healthy"
Expected = "NoChange"
}
@{
Previous = "Healthy"
Current = "Warning"
Expected = "NewAlert"
}
@{
Previous = "Healthy"
Current = "Critical"
Expected = "NewAlert"
}
@{
Previous = "Healthy"
Current = "Unknown"
Expected = "VisibilityLost"
}
@{
Previous = "Warning"
Current = "Warning"
Expected = "ExistingIssue"
}
@{
Previous = "Warning"
Current = "Critical"
Expected = "Escalated"
}
@{
Previous = "Warning"
Current = "Healthy"
Expected = "Recovery"
}
@{
Previous = "Critical"
Current = "Critical"
Expected = "ExistingIssue"
}
@{
Previous = "Critical"
Current = "Warning"
Expected = "Improved"
}
@{
Previous = "Critical"
Current = "Healthy"
Expected = "Recovery"
}
@{
Previous = "Critical"
Current = "Unknown"
Expected = "VisibilityLost"
}
@{
Previous = "Unknown"
Current = "Healthy"
Expected = "Recovery"
}
@{
Previous = "Unknown"
Current = "Warning"
Expected = "StateRestoredWithIssue"
}
@{
Previous = "Unknown"
Current = "Critical"
Expected = "StateRestoredWithIssue"
}
)
Pester Parameterized Test
不用寫 16 個:
It {
}
可以:
Describe "Get-HealthEventType" {
$Cases = @(
@{
Previous = $null
Current = "Healthy"
Expected = "InitialHealthy"
}
@{
Previous = $null
Current = "Critical"
Expected = "InitialIssue"
}
@{
Previous = "Healthy"
Current = "Critical"
Expected = "NewAlert"
}
@{
Previous = "Warning"
Current = "Critical"
Expected = "Escalated"
}
@{
Previous = "Critical"
Current = "Healthy"
Expected = "Recovery"
}
@{
Previous = "Critical"
Current = "Warning"
Expected = "Improved"
}
@{
Previous = "Critical"
Current = "Unknown"
Expected = "VisibilityLost"
}
@{
Previous = "Unknown"
Current = "Critical"
Expected = "StateRestoredWithIssue"
}
)
It `
"<Previous> -> <Current> should return <Expected>" `
-TestCases $Cases {
param (
$Previous,
$Current,
$Expected
)
$Result =
Get-HealthEventType `
-PreviousStatus $Previous `
-CurrentStatus $Current
$Result |
Should -Be $Expected
}
}
輸出就可能像:
[+] Healthy -> Critical should return NewAlert
[+] Warning -> Critical should return Escalated
[+] Critical -> Healthy should return Recovery
[+] Critical -> Warning should return Improved
[+] Critical -> Unknown should return VisibilityLost
這比手動測每個 Case 快很多。
Invalid Input 也要測
測試不只是:
正常資料
也要測:
錯誤資料
例如:
Get-HealthEventType -PreviousStatus "Healthy"
-CurrentStatus "Broken"
因為 Day 24 我們有:
[ValidateSet(
"Healthy",
"Warning",
"Critical",
"Unknown"
)]
[string]
$CurrentStatus
所以:
Broken
應該被拒絕。
Pester:
It "Should reject invalid CurrentStatus" {
{
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Broken"
} |
Should -Throw
}
如果哪一天有人把:
[ValidateSet()]
拿掉,
這個 Test 就可能幫我們發現:
Input Validation 被改掉了。
不只是測結果,也可以測 Module
Day 24 建立:
SysAdminToolkit.psd1
可以測:
Describe "SysAdminToolkit Module" {
It "Module manifest should be valid" {
{
Test-ModuleManifest `
-Path $ModulePath `
-ErrorAction Stop
} |
Should -Not -Throw
}
}
這可以避免:
Module Version 格式錯誤
RootModule 不存在
Manifest 壞掉
測試 Public Commands
例如我們期待 Module 至少提供:
Write-Log
Get-ServerHealth
Get-HealthEventType
可以:
Describe "SysAdminToolkit Public Commands" {
It "Should export Get-ServerHealth" {
Get-Command `
Get-ServerHealth `
-Module SysAdminToolkit `
-ErrorAction SilentlyContinue |
Should -Not -BeNullOrEmpty
}
It "Should export Write-Log" {
Get-Command `
Write-Log `
-Module SysAdminToolkit `
-ErrorAction SilentlyContinue |
Should -Not -BeNullOrEmpty
}
It "Should export Get-HealthEventType" {
Get-Command `
Get-HealthEventType `
-Module SysAdminToolkit `
-ErrorAction SilentlyContinue |
Should -Not -BeNullOrEmpty
}
}
如果有人誤改:
Export-ModuleMember
導致:
Get-ServerHealth
突然沒有 Export,
Test 就會 Fail。
Write-Log 怎麼測?
Write-Log 會真的寫 File。
可以建立 Temporary Test File。
例如:
Describe "Write-Log" {
BeforeEach {
$TestLog =
Join-Path `
$TestDrive `
"test.log"
}
It "Should create a log entry" {
Write-Log `
-Path $TestLog `
-Message "Test Message" `
-Level "INFO"
Test-Path $TestLog |
Should -BeTrue
}
It "Should contain the message" {
Write-Log `
-Path $TestLog `
-Message "Health Check Started"
$Content =
Get-Content `
$TestLog `
-Raw
$Content |
Should -Match `
"Health Check Started"
}
}
這裡出現:
$TestDrive
Pester 的 TestDrive
Pester 可以提供一個測試用的暫存空間。
也就是:
測試開始
↓
建立 Temporary Drive
↓
寫 Test File
↓
測試結束
↓
清掉
所以不用真的:
C:\Automation\Logs
塞一堆:
test1.log
test2.log
這非常適合測:
File
JSON
CSV
Log
Save-HealthState 也非常適合測
Day 23:
Save-HealthState
負責:
Object
↓
JSON
↓
State File
可以測:
Describe "Save-HealthState" {
It "Should save health state as JSON" {
$StateFile =
Join-Path `
$TestDrive `
"state.json"
$State = @(
[PSCustomObject]@{
ComputerName = "SERVER01"
OverallStatus = "Healthy"
}
[PSCustomObject]@{
ComputerName = "SERVER02"
OverallStatus = "Critical"
}
)
Save-HealthState `
-State $State `
-Path $StateFile
Test-Path $StateFile |
Should -BeTrue
}
}
再確認內容:
It "Should preserve server status" {
$SavedState =
Get-Content `
$StateFile `
-Raw |
ConvertFrom-Json
(
$SavedState |
Where-Object {
$_.ComputerName -eq "SERVER02"
}
).OverallStatus |
Should -Be "Critical"
}
哪種 Function 最容易測?
像:
Get-HealthEventType
非常容易。
因為:
Input
↓
Logic
↓
Output
它不需要:
AD
Network
Server
SMTP
Disk
WinRM
這種 Function 常被稱為比較:
Pure Logic
的 Function。
Get-ServerHealth 就比較難測
因為它依賴:
Test-WSMan
Invoke-Command
Get-CimInstance
Remote Server
如果測試每次都真的連:
SERVER01
會有很多問題。
例如:
SERVER01 今天剛好關機
Network 不通
測試環境沒有這台 Server
WinRM 被 Firewall 擋住
這時候:
Function 可能沒壞,只是環境有問題。
所以 Testing 需要開始區分不同層次。
Unit Test
主要測:
Function Logic
NewAlert
通常希望:
快
穩定
不依賴外部環境
Integration Test
測:
PowerShell
+
Active Directory
PowerShell
+
WinRM
PowerShell
+
SMTP
PowerShell
+
File Server
例如:
Get-ServerHealth `
-ComputerName "TEST-SERVER01"
真的連到測試 Server。
這種比較接近:
Integration Test。
Smoke Test
還可以做最基本的:
Module 可以 Import 嗎?
Command 存在嗎?
Script 可以啟動嗎?
例如:
Import-Module SysAdminToolkit
成功。
再:
Get-Command `
Get-ServerHealth
存在。
這種就是很簡單但很實用的:
Smoke Test。
不要所有 Test 都連 Production
尤其:
AD Onboarding
Offboarding
Remove Group
Disable Account
更不要:
Pester 執行
↓
真的對 Production 建帳號
↓
真的 Disable User
Unit Test 應盡量:
隔離外部系統
Integration Test 也應該使用:
Lab
Test OU
Test Account
Test Server
不要把:
測試
變成:
Production Change。
Mock 的概念
如果 Function 裡:
Test-WSMan
但我們不想真的連 Server,
Pester 可以使用:
Mock
簡單理解就是:
測試時先假裝某個外部 Command 回傳特定結果。
例如原本:
Get-ServerHealth
↓
Test-WSMan SERVER01
測試時可以假裝:
Test-WSMan
永遠 Success
或者:
永遠 Throw Error
來測 Function 的 Error Handling。
為什麼 Mock 很重要?
假設我要測:
WinRM 失敗時,OverallStatus 是否會變成 Unknown?
我不需要真的去:
拔 SERVER01 網路線
只要讓測試:
假裝 Test-WSMan 失敗
就可以驗證:
Connection = Failed
OverallStatus = Unknown
不過今天先把重點放在「可測試的邏輯」
Mock 後面可以繼續深化。
Day 25 最重要的是先建立這個習慣:
Function 寫完
↓
Test Case
↓
Expected Result
↓
Invoke-Pester
↓
全部 Pass
↓
才準備部署
專案結構再增加 Tests
目前:
SysAdmin-Automation/
│
├── Modules/
│ │
│ └── SysAdminToolkit/
│ ├── SysAdminToolkit.psd1
│ ├── SysAdminToolkit.psm1
│ ├── Public/
│ └── Private/
│
├── Config/
├── Input/
│
├── Scripts/
│
├── Tests/
│ ├── Module.Tests.ps1
│ ├── Get-HealthEventType.Tests.ps1
│ ├── Write-Log.Tests.ps1
│ └── Save-HealthState.Tests.ps1
│
├── Reports/
├── Logs/
└── State/
現在已經更像一個真正軟體專案。
建立 Run-Tests.ps1
甚至可以:
Scripts/
└── Run-Tests.ps1
內容:
$TestPath =
Join-Path $PSScriptRoot
"..\Tests"
Invoke-Pester -Path $TestPath
-Output Detailed
以後修改 Module:
.\Scripts\Run-Tests.ps1
即可。
修改 Module 的流程開始改變
以前:
改 Code
↓
手動跑一下
↓
看起來正常
↓
Production
現在:
改 Code
↓
Import Module
↓
Run Pester
↓
Unit Tests
↓
全部 Pass
↓
Integration Test
↓
確認
↓
更新 Version
↓
Deploy
這就開始有:
Release Process
的概念。
測試不是證明「完全沒有 Bug」
這裡也要注意。
假設:
100 Tests Passed
不代表:
這個 Module 絕對沒有任何問題。
只能說:
我們已經驗證的 100 種條件,目前都符合預期。
所以測試的品質仍然取決於:
你測了什麼?
有沒有漏掉 Edge Case?
Expected Result 本身對不對?
Testing 是:
降低風險。
不是:
魔法保證零 Bug。
Bug 發生後,可以先加 Test
這是一個非常好用的工作方式。
假設 Production 發現:
Unknown → Warning
原本回傳
Changed
但我們希望
StateRestoredWithIssue
修 Bug 以前先寫:
It "Unknown to Warning should return StateRestoredWithIssue" {
Get-HealthEventType `
-PreviousStatus "Unknown" `
-CurrentStatus "Warning" |
Should -Be `
"StateRestoredWithIssue"
}
現在 Test:
Fail
接著修改 Function。
再跑:
Pass
以後哪一天有人又改壞,
這個 Test 就會再次提醒我們。
這叫做很重要的:
Regression Test
Regression 是什麼?
簡單說:
原本已經修好的問題
↓
後來修改 Code
↓
又再次出現
例如:
v1.0.0
Recovery 正常
v1.1.0
新增 Unknown 判斷
結果
Recovery 壞了
這就是:
Regression
所以 Test 的其中一個重要任務就是:
防止舊功能被新修改弄壞。
Pester 也能幫我們做 Module Quality Gate
以後甚至可以定義:
Tests Failed
↓
不准 Deploy
例如:
Developer
↓
Modify PowerShell
↓
Git Push
↓
CI
↓
Invoke-Pester
↓
Pass?
├── Yes → Continue
└── No → Stop
你可能會發現:
這開始跟 CI/CD 很像了。
沒錯。
PowerShell Automation 其實可以接 CI
我們前面 Day 20 是:
Task Scheduler
負責:
執行維運工作。
但 Code 本身也可以有:
Git
↓
Pester
↓
CI Pipeline
↓
Deploy Module
這就是從:
SysAdmin Script
逐漸往:
Infrastructure / Platform Tooling
前進。
今天完整 Get-HealthEventType Test
整理成:
Tests/Get-HealthEventType.Tests.ps1
BeforeAll {
$ModulePath =
Join-Path `
$PSScriptRoot `
"..\Modules\SysAdminToolkit\SysAdminToolkit.psd1"
Import-Module `
$ModulePath `
-Force `
-ErrorAction Stop
}
Describe "Get-HealthEventType" {
Context "Initial State" {
It "None to Healthy should return InitialHealthy" {
Get-HealthEventType `
-PreviousStatus $null `
-CurrentStatus "Healthy" |
Should -Be "InitialHealthy"
}
It "None to Critical should return InitialIssue" {
Get-HealthEventType `
-PreviousStatus $null `
-CurrentStatus "Critical" |
Should -Be "InitialIssue"
}
}
Context "Healthy State" {
It "Healthy to Healthy should return NoChange" {
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Healthy" |
Should -Be "NoChange"
}
It "Healthy to Warning should return NewAlert" {
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Warning" |
Should -Be "NewAlert"
}
It "Healthy to Critical should return NewAlert" {
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Critical" |
Should -Be "NewAlert"
}
It "Healthy to Unknown should return VisibilityLost" {
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Unknown" |
Should -Be "VisibilityLost"
}
}
Context "Warning State" {
It "Warning to Warning should return ExistingIssue" {
Get-HealthEventType `
-PreviousStatus "Warning" `
-CurrentStatus "Warning" |
Should -Be "ExistingIssue"
}
It "Warning to Critical should return Escalated" {
Get-HealthEventType `
-PreviousStatus "Warning" `
-CurrentStatus "Critical" |
Should -Be "Escalated"
}
It "Warning to Healthy should return Recovery" {
Get-HealthEventType `
-PreviousStatus "Warning" `
-CurrentStatus "Healthy" |
Should -Be "Recovery"
}
}
Context "Critical State" {
It "Critical to Critical should return ExistingIssue" {
Get-HealthEventType `
-PreviousStatus "Critical" `
-CurrentStatus "Critical" |
Should -Be "ExistingIssue"
}
It "Critical to Warning should return Improved" {
Get-HealthEventType `
-PreviousStatus "Critical" `
-CurrentStatus "Warning" |
Should -Be "Improved"
}
It "Critical to Healthy should return Recovery" {
Get-HealthEventType `
-PreviousStatus "Critical" `
-CurrentStatus "Healthy" |
Should -Be "Recovery"
}
It "Critical to Unknown should return VisibilityLost" {
Get-HealthEventType `
-PreviousStatus "Critical" `
-CurrentStatus "Unknown" |
Should -Be "VisibilityLost"
}
}
Context "Unknown State" {
It "Unknown to Healthy should return Recovery" {
Get-HealthEventType `
-PreviousStatus "Unknown" `
-CurrentStatus "Healthy" |
Should -Be "Recovery"
}
It "Unknown to Warning should return StateRestoredWithIssue" {
Get-HealthEventType `
-PreviousStatus "Unknown" `
-CurrentStatus "Warning" |
Should -Be "StateRestoredWithIssue"
}
It "Unknown to Critical should return StateRestoredWithIssue" {
Get-HealthEventType `
-PreviousStatus "Unknown" `
-CurrentStatus "Critical" |
Should -Be "StateRestoredWithIssue"
}
}
Context "Input Validation" {
It "Invalid CurrentStatus should throw" {
{
Get-HealthEventType `
-PreviousStatus "Healthy" `
-CurrentStatus "Broken"
} |
Should -Throw
}
}
}
Context 是什麼?
你會看到:
Context "Critical State" {
}
它主要是幫測試:
分類。
例如:
Describe
Get-HealthEventType
├── Context Initial State
├── Context Healthy State
├── Context Warning State
├── Context Critical State
├── Context Unknown State
└── Context Input Validation
之後 Test 越多,會比全部塞在同一層更容易閱讀。
我們現在可以開始建立「改 Code 前先跑 Test」習慣
例如今天準備改:
Get-HealthEventType
第一步先:
Invoke-Pester -Path ".\Tests"
-Output Detailed
先確認:
Before Change
All Passed
再修改。
修改完:
Invoke-Pester
如果:
20 Passed
0 Failed
至少知道:
現有測試涵蓋的行為沒有 Regression。
Day 25 小結
今天沒有新增:
Server Health
AD Function
HTML Report
Email Alert
而是開始替我們前 24 天累積的 Automation 加一層非常重要的保護:
Automated Testing
一開始我們從:
Expected
vs
Actual
開始。
接著使用:
Describe
Context
It
Should
Invoke-Pester
建立真正的 PowerShell Tests。
今天最適合拿來測的就是:
Get-HealthEventType
因為它幾乎完全是:
Input
↓
Logic
↓
Output
不需要真的:
連 Server
連 AD
連 SMTP
所以非常適合做:
Unit Test
同時我們也開始區分:
Unit Test
→ Function Logic
Integration Test
→ 真正外部系統
Smoke Test
→ 最基本的 Module / Command 是否正常
並開始理解:
Mock
可以讓我們未來不必真的把 Server 關機,就可以測:
WinRM Failure
Connection Failure
這些 Error Handling。
今天另一個重要觀念是:
Automation 可以跑完,不代表 Automation 是正確的。
我以為成功
現在:
Function Output
↓
Expected Result
↓
Automated Test
↓
Pass / Fail
開始用比較工程化的方式證明程式行為。
到目前為止,我們的流程已經變成:
PowerShell Code
↓
Module
↓
Pester Tests
↓
Pass
↓
Deploy
↓
Task Scheduler
↓
Automation
而不是:
改完
↓
直接 Production
這也是從:
「會寫 PowerShell」
往:
「會維護 PowerShell Automation」
非常重要的一步。
Day 26 預告
Day 26|PowerShell 與 Git:讓每一次 Script 修改都有版本紀錄
今天我們解決:
怎麼知道改完有沒有壞?
但是還有另一個問題。
假設昨天:
SysAdminToolkit 1.1.0
正常
今天修改:
Get-ServerHealth
結果 Production 出問題。
這時候有人問:
昨天正常的版本到底是哪一份?
如果資料夾長這樣:
SysAdminToolkit.psm1
SysAdminToolkit_old.psm1
SysAdminToolkit_new.psm1
SysAdminToolkit_new2.psm1
SysAdminToolkit_final.psm1
SysAdminToolkit_final_真的最後.psm1
其實已經失控了。
所以 Day 26 會正式把整個專案放進:
Git
開始使用:
git init
git status
git add
git commit
git log
git diff
讓我們可以回答:
誰改了?
改了什麼?
什麼時候改?
上一版是什麼?
哪個版本開始出問題?
也會建立 .gitignore,避免把:
Logs/
Reports/
State/
Temporary Files
Secrets
這些不應該進版本控制的資料一起 Commit。
Day 26 開始,我們的 SysAdmin Automation Toolkit 就不只是一堆 PowerShell 檔案,而會真正進入:
Version Control + Testing + Automation
的工程化流程。