如果在Docker下运行的.NET核心单元测试的代码覆盖率低于90%,则破坏TeamCity中的构建

如果在Docker下运行的.NET核心单元测试的代码覆盖率低于90%,则破坏TeamCity中的构建,docker,.net-core,teamcity,code-coverage,xunit,Docker,.net Core,Teamcity,Code Coverage,Xunit,我最近一直在研究Docker,以及如何使用TeamCity在Docker容器中运行.NET核心单元测试,作为构建管道的一部分。我将此添加为Dockerfile中的最后一行,以便能够运行测试: ENTRYPOINT ["dotnet", "test", "--verbosity=normal"] 然后,这些DockerFile将在compose文件中引用,TeamCity将在命令行中使用docker compose构建并运行这些文件 我已经成功地完成了这项工作。下一个挑战是,如果单元/集成测试覆

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

ENTRYPOINT ["dotnet", "test", "--verbosity=normal"]
然后,这些DockerFile将在compose文件中引用,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+))%'
+-----------------------+--------+--------+--------+
| Module                | Line   | Branch | Method |
+-----------------------+--------+--------+--------+
| Steve.Core.Extensions | 23.5%  | 40%    | 40%    |
+-----------------------+--------+--------+--------+
| Steve.Core.Files      | 100%   | 100%   | 100%   |
+-----------------------+--------+--------+--------+
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

...
但是,当被测试的DLL引用单独测试的其他DLL时,会变得很棘手,因为coverlet.msbuild会报告所有“接触”DLL的覆盖率度量。例如,我有一个名为Steve.Core.Files.Tests的测试项目,它测试Steve.Core.Files。但是,Steve.Core.Files反过来引用Steve.Core.Extensions。我在自己的测试DLL中单独测试Steve.Core.Extensions,所以在测试文件时,我不关心该DLL的结果。TeamCity中的输出如下所示:

has a line coverage '((\d+(\.\d*)?)|(\.\d+))%' below specified threshold '((\d+(\.\d*)?)|(\.\d+))%'
+-----------------------+--------+--------+--------+
| Module                | Line   | Branch | Method |
+-----------------------+--------+--------+--------+
| Steve.Core.Extensions | 23.5%  | 40%    | 40%    |
+-----------------------+--------+--------+--------+
| Steve.Core.Files      | 100%   | 100%   | 100%   |
+-----------------------+--------+--------+--------+
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

...
…因此,基于23.5%的位,它会失败,即使所讨论的DLL是100%。这实际上使得使用正则表达式故障条件检查异常非常困难

为了使事情进一步复杂化,我使用一个动态Dockerfile在所有程序集中运行所有测试,原因有二:

  • 我不希望每次添加更多项目和测试时都必须更改Dockerfile和docker compose文件(以及TeamCity)

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

  • 这意味着我不愿意将测试拆分,以便每个测试都有自己的Dockerfile——我知道这将允许我使用排除/包括标志来获得所需的行为

    有没有人有其他的想法,我可以如何解决这个问题

    我希望我可以在每个测试项目级别添加一个文件,告诉它哪些DLL需要覆盖—这将是最好的解决方案。否则,由于我在项目和测试项目之间使用了严格的命名约定,我是否可以在
    dotnet test
    命令中添加一个开关,以仅测试与测试程序集同名的程序集减去末尾的.Tests位

    提前感谢;谢谢你的帮助

    干杯

    史蒂夫

    2018年9月7日更新:

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

    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]*\""]
    
    请注意exclude开关,该开关用于停止Steve.Core.Configuration的结果中包含的Steve.Core.Testing DLL的覆盖率结果,这是测试的主要依赖项,以及正在进行单元测试的项目

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

    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中运行时,它只报告来自两个项目的7个测试(出于某种奇怪的原因),尽管12个项目中有236个测试

    如果有帮助,我很乐意通过电子邮件发送TeamCity构建的输出

    有人知道我怎样才能让我的测试再次运行吗

    谢谢


    Steve.

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

    has a line coverage '((\d+(\.\d*)?)|(\.\d+))%' below specified threshold '((\d+(\.\d*)?)|(\.\d+))%'
    
    +-----------------------+--------+--------+--------+
    | Module                | Line   | Branch | Method |
    +-----------------------+--------+--------+--------+
    | Steve.Core.Extensions | 23.5%  | 40%    | 40%    |
    +-----------------------+--------+--------+--------+
    | Steve.Core.Files      | 100%   | 100%   | 100%   |
    +-----------------------+--------+--------+--------+
    
    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在一个构建步骤中吐出所有测试的结果,我在上面的问题中提到的正则表达式代码覆盖率失败情况会正确地检测到未达到x%覆盖率的测试项目并破坏构建


    现在,为了解决如何将代码检查(例如,FxCop和StyleCop的现代等价物)集成到我的构建过程中…

    因此,将Dockerfile拆分为特定于测试项目的文件是可行的,因为每个文件都有自己的Dockerfile和正确的排除项。(奇怪的是,当我试图只包含我感兴趣的程序集时,使用包含并没有起作用,但是,排除那些错误地出现在报告中的程序集确实起作用。)。这意味着每次都必须在docker compose文件中添加一行,告诉它需要将新项目添加到正在测试的项目列表中,并且在测试项目级别需要一个新的dockerfile。有人有更好的方法吗?我认为我的解决方案是拆分docker文件并向docker compose流程添加多个阶段,但没有。TeamCity现在只显示测试的一个子集,而不是所有测试。我将用更多的信息更新这个问题……我已经意识到为什么我只通过了测试的一部分。我使用以下命令启动测试
    docker compose-f docker-compose-run-tests.yml up--force recreate--abort on container exit--build
    。但是,