我最近一直在关注Docker,以及如何使用TeamCity在Docker容器中运行.NET Core单元测试作为构建管道的一部分。我将其添加为我的Dockerfile中的最后一行,以便能够运行测试:
ENTRYPOINT ["dotnet", "test", "--verbosity=normal"]
然后在组合文件中引用这些Dockerfiles,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% |
+-----------------------+--------+--------+--------+
...所以它基于23.5%的位失败,即使有问题的DLL是100%。这实际上使得使用正则表达式失败条件检查非常困难。
为了使事情进一步复杂化,我使用单个动态Dockerfile在所有程序集中运行所有测试,原因有两个:
这意味着我不愿意将测试分开,以便每个测试都有自己的Dockerfile - 我知道这将允许我使用Exclude / Include标志来获得所需的行为。
有没有人有任何其他想法,我可以解决这个问题吗?
我希望我可以在每个测试项目的级别添加一个文件来告诉它要做哪些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]*\""]
请注意,排除开关应该停止Steve.Core.Testing DLL的覆盖结果,该结果包含在Steve.Core.Configuration的结果中,这是测试的主要依赖项,并且项目正在进行单元测试。
我的compose文件看起来像这样,并且存在于解决方案文件旁边:
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构建的输出。
有谁知道我怎么能让我的测试全部再次运行呢?
谢谢,
史蒂夫。
因此,唯一的解决方案是将每个单元测试项目拆分为自己的compose文件,该文件包含仅测试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在单个构建步骤中吐出所有测试的结果,然后我在上面的问题中提到的正则表达式代码覆盖失败条件正确地检测到没有达到x%覆盖率并打破构建的测试项目。
现在来研究如何将代码检查(例如FxCop和StyleCop的现代等价物)集成到我的构建过程中......