練習 - 將測試階段新增至管線

已完成

您的玩具公司安全性小組請您確認您的網站只能透過 HTTPS 存取。 在此練習中,您將設定管線來執行煙霧測試,以檢查安全性小組的需求。

在此過程中,您將會:

  • 將測試指令碼新增至您的存放庫。
  • 更新管線定義以新增測試階段。
  • 執行管線並觀察測試失敗。
  • 修正 Bicep 檔案,並觀察管線順利執行。

新增測試指令碼

您將在此新增測試指令碼,以確認使用 HTTPS 時可存取網站,使用不安全的 HTTP 通訊協定時則無法存取。

  1. 在 Visual Studio Code 中的 [deploy] 資料夾內建立名為 Website.Tests.ps1 的新檔案。

    Screenshot of Visual Studio Code Explorer, with the deploy folder and the test file shown.

  2. 將下列測試程式碼貼到檔案中:

    param(
      [Parameter(Mandatory)]
      [ValidateNotNullOrEmpty()]
      [string] $HostName
    )
    
    Describe 'Toy Website' {
    
        It 'Serves pages over HTTPS' {
          $request = [System.Net.WebRequest]::Create("https://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode |
            Should -Be 200 -Because "the website requires HTTPS"
        }
    
        It 'Does not serves pages over HTTP' {
          $request = [System.Net.WebRequest]::Create("http://$HostName/")
          $request.AllowAutoRedirect = $false
          $request.GetResponse().StatusCode | 
            Should -BeGreaterOrEqual 300 -Because "HTTP is not secure"
        }
    
    }
    

    此程式碼是 Pester 測試檔案。 其需要名為 $HostName 的參數。 其會針對主機名稱執行兩項測試:

    • 嘗試透過 HTTPS 連線至網站。 如果伺服器回應 200 到 299 之間的 HTTP 回應狀態碼,表示連線成功,測試會通過。
    • 嘗試透過 HTTP 連線至網站。 如果伺服器回應 300 以上的 HTTP 回應狀態碼,測試會通過。

    本練習的目的並非一定要您了解測試檔案的詳細資料及其運作方式。 我們將會在摘要中提供連結,如有興趣您可以自行深入瞭解。

將 Bicep 檔案的輸出發佈為階段輸出變數

您在上述步驟中建立的測試指令碼,需要主機名稱才能進行測試。 Bicep 檔案已經包含輸出,但您必須先將其發佈為階段輸出變數,才能在煙霧測試中使用。

  1. 在 Visual Studio Code 中,開啟 deploy 資料夾中的 azure-pipelines.yml 檔案。

  2. 在 [部署] 階段中,更新部署步驟以將輸出發佈至變數:

    - task: AzureResourceManagerTemplateDeployment@3
      name: DeployBicepFile
      displayName: Deploy Bicep file
      inputs:
        connectedServiceName: $(ServiceConnectionName)
        deploymentName: $(Build.BuildNumber)
        location: $(deploymentDefaultLocation)
        resourceGroupName: $(ResourceGroupName)
        csmFile: deploy/main.bicep
        overrideParameters: >
          -environmentType $(EnvironmentType)
        deploymentOutputs: deploymentOutputs
    

    現在,您的部署程序仍會使用與先前執行的相同工作,但部署的輸出會儲存在名為 deploymentOutputs 的管線變數中。 輸出變數會格式化為 JSON。

  3. 若要將 JSON 格式的輸出轉換成管線變數,請在部署步驟下方新增下列指令碼步驟:

    - bash: |
        echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
      name: SaveDeploymentOutputs
      displayName: Save deployment outputs into variables
      env:
        DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    

    如果部署成功完成,指令碼會存取 Bicep 部署的每個輸出值。 此指令碼使用 jq 工具來存取 JSON 輸出相關部分來執行這項作業。 然後,該值會發佈至與 Bicep 部署輸出具有相同名稱的階段輸出變數。

    注意

    Pester 和 jq 都預先安裝在 Microsoft 裝載的 Azure Pipelines 代理程式上。 您不需要執行任何特殊動作,即可在指令碼步驟中使用兩者。

  4. 儲存檔案。

將煙霧測試階段新增至管線

現在,您可以新增執行測試的煙霧測試階段。

  1. 在檔案底端,為 SmokeTest 階段新增下列定義:

    - stage: SmokeTest
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
    

    此程式碼會定義階段和作業。 其也會在名為 appServiceAppHostName 的作業中建立變數。 此變數會從您在上一節中建立的輸出變數取得其值。

  2. 在檔案底部,將下列步驟定義新增至 SmokeTest 階段:

    steps:
      - task: PowerShell@2
        name: RunSmokeTests
        displayName: Run smoke tests
        inputs:
          targetType: inline
          script: |
            $container = New-PesterContainer `
              -Path 'deploy/Website.Tests.ps1' `
              -Data @{ HostName = '$(appServiceAppHostName)' }
            Invoke-Pester `
              -Container $container `
              -CI
    

    此步驟會執行 PowerShell 指令碼,以執行您稍早使用 Pester 測試工具撰寫的測試指令碼。

  3. 在檔案底部,將下列步驟定義新增至 SmokeTest 階段:

    - task: PublishTestResults@2
      name: PublishTestResults
      displayName: Publish test results
      condition: always()
      inputs:
        testResultsFormat: NUnit
        testResultsFiles: 'testResults.xml'
    

    此步驟會採用 Pester 建立的測試結果檔案,並將其發佈為管線測試結果。 您很快就會看到結果的顯示出。

    請注意,步驟定義包含 condition: always()。 此條件表示 Azure Pipelines,即使上述步驟失敗,其也應該一律發佈測試結果。 此條件很重要,因為任何失敗的測試都會造成測試步驟失敗,而且管線通常會在失敗的步驟之後停止執行。

  4. 儲存檔案。

驗證並認可管線定義

  1. 確認 azure-pipelines.yml 檔案看起來像下列程式碼:

    trigger:
      batch: true
      branches:
        include:
        - main
    
    pool:
      vmImage: ubuntu-latest
    
    variables:
      - name: deploymentDefaultLocation
        value: westus3
    
    stages:
    
    - stage: Lint
      jobs:
      - job: LintCode
        displayName: Lint code
        steps:
          - script: |
              az bicep build --file deploy/main.bicep
            name: LintBicepCode
            displayName: Run Bicep linter
    
    - stage: Validate
      jobs:
      - job: ValidateBicepCode
        displayName: Validate Bicep code
        steps:
          - task: AzureResourceManagerTemplateDeployment@3
            name: RunPreflightValidation
            displayName: Run preflight validation
            inputs:
              connectedServiceName: $(ServiceConnectionName)
              location: $(deploymentDefaultLocation)
              deploymentMode: Validation
              resourceGroupName: $(ResourceGroupName)
              csmFile: deploy/main.bicep
              overrideParameters: >
                -environmentType $(EnvironmentType)
    
    - stage: Preview
      jobs:
      - job: PreviewAzureChanges
        displayName: Preview Azure changes
        steps:
          - task: AzureCLI@2
            name: RunWhatIf
            displayName: Run what-if
            inputs:
              azureSubscription: $(ServiceConnectionName)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment group what-if \
                  --resource-group $(ResourceGroupName) \
                  --template-file deploy/main.bicep \
                  --parameters environmentType=$(EnvironmentType)
    
    - stage: Deploy
      jobs:
      - deployment: DeployWebsite
        displayName: Deploy website
        environment: Website
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: AzureResourceManagerTemplateDeployment@3
                  name: DeployBicepFile
                  displayName: Deploy Bicep file
                  inputs:
                    connectedServiceName: $(ServiceConnectionName)
                    deploymentName: $(Build.BuildNumber)
                    location: $(deploymentDefaultLocation)
                    resourceGroupName: $(ResourceGroupName)
                    csmFile: deploy/main.bicep
                    overrideParameters: >
                      -environmentType $(EnvironmentType)
                    deploymentOutputs: deploymentOutputs
    
                - bash: |
                    echo "##vso[task.setvariable variable=appServiceAppHostName;isOutput=true]$(echo $DEPLOYMENT_OUTPUTS | jq -r '.appServiceAppHostName.value')"
                  name: SaveDeploymentOutputs
                  displayName: Save deployment outputs into variables
                  env:
                    DEPLOYMENT_OUTPUTS: $(deploymentOutputs)
    
    - stage: SmokeTest
      jobs:
      - job: SmokeTest
        displayName: Smoke test
        variables:
          appServiceAppHostName: $[ stageDependencies.Deploy.DeployWebsite.outputs['DeployWebsite.SaveDeploymentOutputs.appServiceAppHostName'] ]
        steps:
          - task: PowerShell@2
            name: RunSmokeTests
            displayName: Run smoke tests
            inputs:
              targetType: inline
              script: |
                $container = New-PesterContainer `
                  -Path 'deploy/Website.Tests.ps1' `
                  -Data @{ HostName = '$(appServiceAppHostName)' }
                Invoke-Pester `
                  -Container $container `
                  -CI
    
          - task: PublishTestResults@2
            name: PublishTestResults
            displayName: Publish test results
            condition: always()
            inputs:
              testResultsFormat: NUnit
              testResultsFiles: 'testResults.xml'
    

    如果不相符,請更新以符合此範例,然後將其儲存。

  2. 在 Visual Studio Code 終端機中執行下列命令,以認可所做的變更,並將其推送至您的 Git 存放庫:

    git add .
    git commit -m "Add test stage"
    git push
    

執行管線並檢閱測試結果

  1. 在瀏覽器中,前往您的管線。

  2. 選取管線的最近一次執行。

    等候管線完成 Lint驗證預覽階段。 雖然 Azure Pipelines 自動將頁面更新為最新狀態,但最好偶爾重新整理頁面。

  3. 選取 [檢閱] 按鈕,然後選取 [核准]

    等候管線執行完成。

  4. 請注意,部署階段會順利完成。 煙霧測試階段完成時發生錯誤。

    Screenshot of the Azure DevOps interface that shows the pipeline run stages. The SmokeTest stage reports failure.

  5. 選取 [測試] 索引標籤。

    Screenshot of the Azure DevOps interface that shows the pipeline run, with the Tests tab highlighted.

  6. 請注意,測試摘要顯示已執行兩個測試。 一個通過,一個失敗。 失敗的測試會列為 Toy Website.Does not serve pages over HTTP

    Screenshot of the Azure DevOps interface that shows the pipeline run's test results, with the failed test highlighted.

    此文字表示網站尚未根據安全性小組的需求正確設定。

更新 Bicep 檔案

既然您已發現 Bicep 定義不符合安全性小組的需求,現在要予以修正。

  1. 在 Visual Studio Code 中,開啟 [deploy] 資料夾中的 main.bicep 檔案。

  2. 尋找 Azure App Service 應用程式的定義並予以更新,以在其 properties 區域中加入 httpsOnly 屬性:

    resource appServiceApp 'Microsoft.Web/sites@2022-03-01' = {
      name: appServiceAppName
      location: location
      properties: {
        serverFarmId: appServicePlan.id
        httpsOnly: true
        siteConfig: {
          appSettings: [
            {
              name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
              value: applicationInsights.properties.InstrumentationKey
            }
            {
              name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
              value: applicationInsights.properties.ConnectionString
            }
          ]
        }
      }
    }
    
  3. 儲存檔案。

  4. 在 Visual Studio Code 終端機中執行下列命令,以認可所做的變更,並將其推送至您的 Git 存放庫:

    git add .
    git commit -m "Configure HTTPS on website"
    git push
    

再次執行管線

  1. 在瀏覽器中,前往您的管線。

  2. 選取最近的執行。

    等候管線完成 Lint驗證預覽階段。 雖然 Azure Pipelines 自動將頁面更新為最新狀態,但最好偶爾重新整理頁面。

  3. 選取 [預覽] 階段,然後再次檢閱假設狀況結果。

    請注意,假設狀況命令已偵測到 httpsOnly 屬性值中的變更:

    Resource and property changes are indicated with these symbols:
      + Create
      ~ Modify
      = Nochange
    
    The deployment will update the following scope:
    
    Scope: /subscriptions/f0750bbe-ea75-4ae5-b24d-a92ca601da2c/resourceGroups/ToyWebsiteTest
    
      ~ Microsoft.Web/sites/toy-website-nbfnedv766snk [2021-01-15]
        + properties.siteConfig.localMySqlEnabled:   false
        + properties.siteConfig.netFrameworkVersion: "v4.6"
        ~ properties.httpsOnly:                      false => true
    
      = Microsoft.Insights/components/toywebsite [2020-02-02]
      = Microsoft.Storage/storageAccounts/mystoragenbfnedv766snk [2021-04-01]
      = Microsoft.Web/serverfarms/toy-website [2021-01-15]
    
    Resource changes: 1 to modify, 3 no change.
    
  4. 回到管線執行。

  5. 選取 [檢閱] 按鈕,然後選取 [核准]。

    等候管線執行完成。

  6. 請注意,整個工作流程會順利完成,包括 SmokeTest 階段。 這表示兩個測試都通過了。

    Screenshot of the Azure DevOps interface that shows a successful pipeline run.

清除資源

既然您已完成練習,您可以移除資源,以免產生費用。

在 Visual Studio Code 終端中,執行下列命令:

az group delete --resource-group ToyWebsiteTest --yes --no-wait

資源群組會在背景中刪除。

Remove-AzResourceGroup -Name ToyWebsiteTest -Force