Azure DevOps 托管构建控制器 - 是否支持 Azure 存储模拟器?

Azure DevOps Hosted Build Controller - Is the Azure Storage Emulator supported?

我想 运行 使用 Azure 存储模拟器而不是来自 Azure DevOps 构建的真实存储的单元/集成测试。

模拟器作为 Azure SDK 的一部分安装在 Hosted Build Controller 上,位于其通常位置 (C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe) .

但是,模拟器在构建控制器上处于未初始化状态。尝试从命令行 运行 命令 Init 时,出现以下错误:

This operation requires an interactive window station

是否有已知的解决方法或计划在 Azure DevOps 构建中支持模拟器?

不,Hosted Build Controller 在交互模式下不运行,因此模拟器无法在该环境下工作。详情见Hosted build controller for XAML builds问答。

Q: Do you need to run your build service in interactive mode?

A: No. Then you can use the hosted build controller.

我建议您在交互模式下设置本地构建控制器和 运行 构建服务器。详见Setup Build Server and Setup Build Controller

似乎答案可能来自 Visual Studio 在线方面。如果有人有类似问题,有一个 User Voice 条目。

不太确定为什么模拟器没有非交互模式,就我个人而言,UI 99% 的时间我都不使用它。有一个通用的 User Voice 条目可以使 Azure 存储更易于单元测试。

如前所述,您无法 运行 Azure 存储模拟器。不过,您可以 运行 Azurite 一种开源替代方案。

请注意:Azurite 可以模拟 blob、表和队列。然而,我只以这种方式使用过 blob 存储模拟。

在构建配置的开头添加一个 nuget 步骤,该步骤 运行 是一个自定义 nuget 命令 install Azurite -version 2.2.2。然后添加一个命令行步骤 运行s start /b $(Build.SourcesDirectory)\Azurite.2.2.2\tools\blob.exe.

它运行与 Azure 存储模拟器位于同一端口,因此您可以使用标准连接字符串。

尽管这里的所有答案都是相反的,但我已经 运行 VS2017 托管构建代理上的 Azure 存储模拟器使用了一年多。

诀窍是先初始化SQL LocalDB(模拟器使用它),然后启动模拟器。您可以使用运行的命令行任务执行此操作:

sqllocaldb create MSSQLLocalDB
sqllocaldb start MSSQLLocalDB
sqllocaldb info MSSQLLocalDB

"C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe" start

如果您想在 C# 集成测试代码中直接启动 Azure 存储模拟器,您可以将其放入测试初始化​​(启动)代码中(示例是对于 xUnit):

[Collection("Database collection")]
public sealed class IntegrationTests
{
    public IntegrationTests(DatabaseFixture fixture)
    {
        this.fixture = fixture;
    }

    [Fact]
    public async Task TestMethod1()
    {
        // use fixture.Table to run tests on the Azure Storage
    }

    private readonly DatabaseFixture fixture;
}

public class DatabaseFixture : IDisposable
{
    public DatabaseFixture()
    {
        StartProcess("SqlLocalDB.exe", "create MSSQLLocalDB");
        StartProcess("SqlLocalDB.exe", "start MSSQLLocalDB");
        StartProcess("SqlLocalDB.exe", "info MSSQLLocalDB");
        StartProcess(EXE_PATH, "start");

        var client = CloudStorageAccount.DevelopmentStorageAccount.CreateCloudTableClient();
        Table = client.GetTableReference("tablename");
        InitAsync().Wait();
    }

    public void Dispose()
    {
        Table.DeleteIfExistsAsync().Wait();
        StartProcess(EXE_PATH, "stop");
    }

    private async Task InitAsync()
    {
        await Table.DeleteIfExistsAsync();
        await Table.CreateAsync();
    }

    static void StartProcess(string path, string arguments, int waitTime = WAIT_FOR_EXIT) => 
        Process.Start(path, arguments).WaitForExit(waitTime);

    public CloudTable Table { get; }

    private const string EXE_PATH = 
    "C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe";
    private const int WAIT_FOR_EXIT = 60_000;
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
    // This class has no code, and is never created. Its purpose is simply
    // to be the place to apply [CollectionDefinition] and all the
    // ICollectionFixture<> interfaces.
}