Hi community,
As a developer, I love using Azure DevOps in my projects to create robust CI/CD pipelines. It streamlines workflows and ensures consistent, automated deployment processes.
But recently, I faced a challenge with my Azure DevOps pipeline and builds. My build agent had a broken software update, causing dotnet CLI commands to fail consistently.
However, my .NET builds already used Dockerfiles, following the standard Microsoft-recommended approach. Many of us have seen those files; they look like this: Microsoft .NET Docker Build.
This technique is called multi stage build and it can help us to build more structured Dockerfiles and also optimize a build process. But we can use the same idea not only to optimize docker build, but to make different useful things inside the docker build process.
This got me thinking: if we can abstract away the agent environment setup for the image build, why not use the same technique to run tests? So, I started researching.
Turns out, it's possible! You can run the dotnet test command directly in a Dockerfile like this:
Example Dockerfile
Here’s an example Dockerfile that you can use:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["YOUR_PROJECT.Api/YOUR_PROJECT.Api.csproj", "YOUR_PROJECT.Api/"]
COPY ["YOUR_PROJECT.Tests/YOUR_PROJECT.Tests.csproj", "YOUR_PROJECT.Tests/"]
RUN dotnet restore "YOUR_PROJECT.Api/YOUR_PROJECT.Api.csproj"
COPY . .
RUN dotnet build "YOUR_PROJECT.Api/YOUR_PROJECT.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
RUN dotnet build "YOUR_PROJECT.Tests/YOUR_PROJECT.Tests.csproj" -c $BUILD_CONFIGURATION -o /app
FROM build AS test
RUN dotnet test -l "trx;logfilename=testResults.trx"; exit 0;
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "YOUR_PROJECT.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "YOUR_PROJECT.Api.dll"]
Pipeline for Extracting Test Results
One challenge with this approach is retrieving the final test results since we can't use Docker volumes in this context. The workaround is to use the docker cp command.
Here's the final azure-pipeline.yml file you can use:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
stages:
- stage: 'Build'
jobs:
- job: 'Build'
steps:
- task: Docker@2
displayName: Build an image
inputs:
command: 'build'
Dockerfile: './Dockerfile'
arguments: '--target build'
- job: 'Test'
steps:
- task: PowerShell@2
inputs:
targetType: inline
script: |
docker build --target test -t familybudget:$env:BUILDID .
docker create -ti --name testcontainer YOUR_PROJECT_NAME:$env:BUILDID
docker cp testcontainer:/src/YOUR_PROJECT_NAME.Tests/TestResults/ $env:ARTIFACTDIR/testresults
docker rm -fv testcontainer
env:
BUILDID: $(Build.BuildId)
ARTIFACTDIR: $(Build.ArtifactStagingDirectory)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/*.trx'
searchFolder: '$(Build.ArtifactStagingDirectory)/testresults'
failTaskOnFailedTests: true
- stage: 'Deploy_DEV'
dependsOn: Build
jobs:
- job: 'Approve'
pool: server
steps:
- task: ManualValidation@0
displayName: Wait for external validation
inputs:
notifyUsers: |
[email protected]
instructions: 'Please validate the build configuration and resume'
- job: 'Deploy'
dependsOn: Approve
steps:
- task: CmdLine@2
inputs:
script: echo deployed successfully
You can find the full example on GitHub.
Pros and Cons
Pros:
No need to worry about the agent environment.
Consistent builds.
Cons:
Requires creating a container to extract test results.
Involves manual steps and a PowerShell script, which can't be done with the built-in DotNetCoreCLI@2 task.