如果在Docker下运行的.NET Core单元测试的代码覆盖率不到90%,则将打破TeamCity的构建

时间:2018-09-04 11:56:36

标签: docker .net-core teamcity code-coverage xunit

我最近一直在研究Docker,以及如何使用TeamCity在Docker容器中运行.NET Core单元测试,这是我的构建管道的一部分。我将此添加为Dockerfile的最后一行,以便能够运行测试:

ENTRYPOINT ["dotnet", "test", "--verbosity=normal"]

这些Dockerfile然后在TeamCity在命令行中使用docker-compose构建和运行的组合文件中引用。

我现在已经成功工作了。下一个挑战是,如果单元/集成测试的覆盖率小于90%(或其他值),请破坏构建!

我已经成功使用coverlet.msbuild NuGet依赖关系来测量代码覆盖率,这是我构建的一部分。这在TeamCity中也能正常工作,我在TeamCity构建中看到了输出。

通过为每个测试项目添加coverlet.msbuild并将Dockerfile入口点更改为:

ENTRYPOINT ["dotnet", "test", "--verbosity=normal", "/p:CollectCoverage=true", "/p:Threshold=90", "/p:ThresholdType=line"]

TeamCity构建输出显示了包含结果的ASCII表,但是到目前为止,如果代码覆盖率不够高,我还找不到找到打破构建的好方法。如果代码覆盖率过低,TeamCity不会将其留给自己的设备标记为构建失败,如果足够合理的话,这是不合逻辑的,就不会将其标记为失败!

我天真地以为我可以在TeamCity中创建一个失败条件,该条件将检测到以下文本的存在:

'[Assemnbly]' has a line coverage '9.8%' below specified threshold '95%'

...使用这样的正则表达式:

has a line coverage '((\d+(\.\d*)?)|(\.\d+))%' below specified threshold '((\d+(\.\d*)?)|(\.\d+))%'

但是,当要测试的DLL引用单独测试的其他DLL时,就会变得棘手,因为Coverlet.msbuild报告了所有“接触”的DLL的覆盖率指标。例如,我有一个名为Steve.Core.Files.Tests的测试项目,用于测试Steve.Core.Files。但是,Steve.Core.Files依次引用Steve.Core.Extensions。我在自己的测试DLL中分别测试Steve.Core.Extensions,因此在测试文件时,我不在乎该DLL的结果。 TeamCity中的输出如下所示:

+-----------------------+--------+--------+--------+
| Module                | Line   | Branch | Method |
+-----------------------+--------+--------+--------+
| Steve.Core.Extensions | 23.5%  | 40%    | 40%    |
+-----------------------+--------+--------+--------+
| Steve.Core.Files      | 100%   | 100%   | 100%   |
+-----------------------+--------+--------+--------+

...因此,即使所讨论的DLL是100%,它也会基于23.5%的位失败。实际上,这使得检查Regex失败情况非常困难。

为了使事情更加复杂,出于两个原因,我正在使用单个动态Dockerfile运行所有程序集中的所有测试:

  1. 我不想每次添加更多项目和测试时都必须更改Dockerfile和docker-compose文件(以及TeamCity)。

  2. 这些DLL之间有许多依赖关系,因此有意义的是一次构建它们并一起测试它们。

这意味着我不愿意将测试分开,以便每个测试都有自己的Dockerfile-我知道这将允许我使用Exclude / Include标志获得所需的行为。

请问有人有其他想法可以解决这个问题吗?

我希望我可以在每个测试项目的级别添加一个文件,以告诉它要覆盖哪些DLL-那将是最好的解决方案。失败了,因为我在项目和测试项目之间使用了严格的命名约定,我可以在我的dotnet test命令中添加一个开关以仅测试与测试程序集具有相同名称的程序集减去.Tests位。结束吗?

预先感谢;帮助表示赞赏!

干杯

史蒂夫。

更新2018年9月7日:

因此,我的Dockerfile现在特定于每个单元测试项目。它们看起来像这样,并存在于测试项目文件的旁边:

FROM microsoft/dotnet:2-sdk

# Set the working directory:
WORKDIR /src

# Copy the solution file and the NuGet.config across to the src directory:
COPY *.sln NuGet.config ./

# Copy the main source project files to the root level:
COPY */*.csproj ./

# Make directories for each project file and move the project file to the correct place:
RUN for file in $(ls *.csproj); do mkdir -p ${file%.*}/ && mv $file ${file%.*}/; done

# Restore dependencies:
RUN dotnet restore

# Copy all files so that we have all everything ready to compile:
COPY . .

# Set the flag to tell TeamCity that these are unit tests:
ENV TEAMCITY_PROJECT_NAME = ${TEAMCITY_PROJECT_NAME}

# Run the tests:
ENTRYPOINT ["dotnet", "test", "Steve.Core.Configuration.Tests/Steve.Core.Configuration.Tests.csproj", "--verbosity=normal", "/p:CollectCoverage=true", "/p:Threshold=95", "/p:ThresholdType=line", "/p:Exclude=\"[Steve.Core.Testing]*\""]

请注意排除开关,该开关应该停止Steve.Core.Testing DLL的覆盖结果,并将其包含在Steve.Core.Configuration的结果中,该结果是测试的主要依赖项,并且对项目进行了单元测试

我的撰写文件如下所示,并位于解决方案文件旁边:

version: '3.6'

services:
  # Dependencies:
  steve.core.ldap.tests.ldap:
    image: osixia/openldap
    container_name: steve.core.ldap.tests.ldap
    environment:
      LDAP_ORGANISATION: Steve
      LDAP_DOMAIN: steve.com
      LDAP_ADMIN_PASSWORD: Password1
  steve.core.data.mysql.tests.database:
    image: mysql
    container_name: steve.core.data.mysql.tests.database
    command: mysqld --default-authentication-plugin=mysql_native_password
    environment:
      - MYSQL_ROOT_PASSWORD=Password1
      - MYSQL_DATABASE=testdb
  steve.core.data.sqlserver.tests.database:
    image: microsoft/mssql-server-linux
    container_name: steve.core.data.sqlserver.tests.database
    environment:
      - MSSQL_SA_PASSWORD=Password1
      - ACCEPT_EULA=Y
      - MSSQL_PID=Developer  
  steve.core.email.tests.smtp:
    image: mailhog/mailhog 
    container_name: steve.core.email.tests.smtp  

  # Steve.Core.Configuration:
  steve.core.configuration.tests:
    image: steve.core.configuration.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Configuration.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Data.MySql:
  steve.core.data.mysql.tests:
    image: steve.core.data.mysql.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Data.MySql.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Data.SqlServer:
  steve.core.data.sqlserver.tests:
    image: steve.core.data.sqlserver.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Data.SqlServer.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Data:
  steve.core.data.tests:
    image: steve.core.data.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Data.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Email:
  steve.core.email.tests:
    image: steve.core.email.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Email.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Encryption:
  steve.core.encryption.tests:
   image: steve.core.encryption.tests:tests
   build:
     context: .
     dockerfile: Steve.Core.Encryption.Tests/Dockerfile
   environment:
     - TEAMCITY_PROJECT_NAME

  # Steve.Core.Execution:
  steve.core.execution.tests:
    image: steve.core.execution.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Execution.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Extensions:
  steve.core.extensions.tests:
    image: steve.core.extensions.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Extensions.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Files:
  steve.core.files.tests:
    image: steve.core.files.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Files.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Ldap:
  steve.core.ldap.tests:
    image: steve.core.ldap.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Ldap.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Maths:
  steve.core.maths.tests:
    image: steve.core.maths.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Maths.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

  # Steve.Core.Time:
  steve.core.time.tests:
    image: steve.core.time.tests:tests
    build:
      context: .
      dockerfile: Steve.Core.Time.Tests/Dockerfile
    environment:
      - TEAMCITY_PROJECT_NAME

当它在TeamCity中运行时,即使在12个项目中有236个测试,它也只能报告两个项目中的7个测试。

如果能提供帮助,我很乐意通过电子邮件将TeamCity构建的输出发送给电子邮件。

请问有人知道如何让我的测试全部重新运行吗?

谢谢

史蒂夫。

1 个答案:

答案 0 :(得分:0)

因此,唯一的解决方案是将每个单元测试项目拆分为自己的撰写文件,该文件包含该测试DLL所需的依赖项。 (例如,用于测试电子邮件DLL的mailhog,用于测试数据库DLL的SQL Server等)。然后,TeamCity使用单个脚本分别运行它们,如下所示:

docker-compose -f docker-compose-configuration-tests.yml up --force-recreate --abort-on-container-exit --build
docker-compose -f docker-compose-configuration-tests.yml down --volumes --remove-orphans

docker-compose -f docker-compose-data-mysql-tests.yml up --force-recreate --abort-on-container-exit --build
docker-compose -f docker-compose-data-mysql-tests.yml down --volumes --remove-orphans

...

每个人都有其自己的Dockerfile,该文件构建测试DLL并设置DLL异常以进行单元测试。 TeamCity在一个构建步骤中吐出所有测试的结果,上面我的问题中提到的regex代码覆盖失败情况,然后正确地检测出未达到x%覆盖率的测试项目并破坏了构建。

现在要弄清楚如何将代码检查(例如FxCop和StyleCop的现代等效项)集成到我的构建过程中...