Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract unit test results from multi-stage Docker build (.NET Core 2.0)

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.

like image 538
Francis Avatar asked Oct 12 '17 09:10

Francis


2 Answers

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/

like image 148
GraemeMiller Avatar answered Sep 17 '22 15:09

GraemeMiller


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:

  1. copy across requirements spec and install dependencies (for caching)
  2. run your tests (re-use deps stage to avoid repeating ourselves)
  3. copy your test results to a scratch image
  4. build your image / binary / whatever you'd normally do from 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

like image 39
Ben Avatar answered Sep 19 '22 15:09

Ben