I am building a .NET Core 2.0 web API and I am creating a Docker image. I am quite new to Docker so apologies if the question has been answered before.
I have the following Docker file for creating the image. In particular, I run the unit tests during the build process and the results are output to ./test/test_results.xml
(in a temporary container created during the build, I guess). My question is, how do I access these test results after the build has finished?
FROM microsoft/aspnetcore-build:2.0 AS build-env
WORKDIR /app
# Copy main csproj file for DataService
COPY src/DataService.csproj ./src/
RUN dotnet restore ./src/DataService.csproj
# Copy test csproj file for DataService
COPY test/DataService.Tests.csproj ./test/
RUN dotnet restore ./test/DataService.Tests.csproj
# Copy everything else (excluding elements in dockerignore)
COPY . ./
# Run the unit tests
RUN dotnet test --results-directory ./ --logger "trx;LogFileName=test_results.xml" ./test/DataService.Tests.csproj
# Publish the app to the out directory
RUN dotnet publish ./src/DataService.csproj -c Release -o out
# Build the runtime image
FROM microsoft/aspnetcore:2.0
WORKDIR /app
EXPOSE 5001
COPY --from=build-env /app/src/out .
# Copy test results to the final image as well??
# COPY --from=build-env /app/test/test_results.xml .
ENTRYPOINT ["dotnet", "DataService.dll"]
One approach that I have taken is to comment in the line # COPY --from=build-env /app/test/test_results.xml .
. This puts the test_results.xml
in my final image. I can then extract these results and remove the test_results.xml
from the final image using the following powershell script.
$id=$(docker create dataservice)
docker cp ${id}:app/test_results.xml ./test/test_results.xml
docker start $id
docker exec $id rm -rf /app/test_results.xml
docker commit $id dataservice
docker rm -vf $id
This however seems ugly and I am wondering is there a cleaner way to do it.
I was hoping that there was a way to mount a volume during docker build
but it does not appear that this is going to be supported in the official Docker.
I am looking now at creating a separate image, solely for the unit tests.
Not sure if there is a recommended way of achieving what I want.
Thanks for your question - I needed to solve the same thing.
I added a separate container stage based on results of the build. The tests and its output are all handled in there so they never reach the final container. So build-env is used to build and then an intermediate test container is based on that build-env image and final is based on runtime container with results of build-env copied in.
# ---- Test ---- # run tests and capture results for later use. This use the results of the build stage FROM build AS test #Use label so we can later obtain this container from the multi-stage build LABEL test=true WORKDIR / #Store test results in a file that we will later extract RUN dotnet test --results-directory ../../TestResults/ --logger "trx;LogFileName=test_results.xml" "./src/ProjectNameTests/ProjectNameTests.csproj"
I added a shell script as a next step that then tags the image as project-test.
#!bin/bash id=`docker images --filter "label=test=true" -q` docker tag $id projectname-test:latest
After that, I basically do what you do which is use docker cp and get the file out. The difference is my test results were never in the final image so I don't touch the final image.
Overall I think the correct way to handle tests is probably create a test image (based on the build image) and run it with a mounted volume for test results and have it run the unit tests when that container starts. Having a proper image/container would also allow you to run integration tests etc. This article is older but details similar https://blogs.infosupport.com/build-deploy-test-aspnetcore-docker-linux-tfs2015/
This is an old question, but I ended up here looking for the same thing so will drop my tuppence worth in here for posterity!
Nowadays you can use DOCKER_BUILDKIT=1
to make docker use buildkit to build your images, which is quicker and caches better, and has an --output
option which solves this problem for you. I've got a golang-based example below, but this should work equally well for pretty much anything.
# syntax=docker/dockerfile:1.2
FROM golang:1.17 as deps
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY command ./command
COPY internal ./internal
# Tests
FROM deps as test
RUN --mount=type=cache,target=/root/.cache go test -v ./... 2>&1 | tee unit.out
FROM scratch as test-output
COPY --from=test /src/unit.out /unit.out
# Build
FROM deps as build
RUN your build steps
This dockerfile has a bunch of stages:
deps
stage to avoid repeating ourselves)deps
Now if you set DOCKER_BUILDKIT=1
somewhere and run:
docker build
then docker builds your code / image but DOES NOT RUN THE TESTS! Because your build
stage isn't linked to the test
stage at all it bypasses both test stages entirely. However, you can now use the --target
option to select the test-output
build stage and the --output
option to tell it where on your local disk to copy the result:
docker build --target test-output --output results .
Docker will then build the dependencies (or re-use the cache from the image build if you did that first), run your tests and copy the contents of your scratch image (i.e. your test report) into the results
directory. Job done. :)
Edit:
An article using this approach with a .NET app and explaining the whole thing a bit better! https://kevsoft.net/2021/08/09/exporting-unit-test-results-from-a-multi-stage-docker-build.html
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With