Azure 持久 HTTPStart 方法中的单元测试 (Rhino) DBUp

Unit Test (Rhino) DBUp in Azure Durable HTTPStart Method

技术栈

  1. 用于数据库升级的 DBUP
  2. Azure Durable for activities
  3. Rhino 模拟单元测试。

情况

目前,我已将我的数据库升级 (DBUp) 语句放在 HTTPStart 方法中作为我的持久 azure 函数的入口点。

DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build(); 

问题

这种方法的问题是 DBUp 使用静态 class 来升级数据库,而我不能使用 Rhino 在静态 class 上模拟方法。

问题

我考虑过将 DBUp 部分包装在非静态 class 中,但随后我需要模拟构造函数初始化。不确定这是否可行

代码 - 升级 DB

的助手 Class
public class DBUPHelper
    {
        public bool UpgradeDB()
        {
            bool status = true;
            var connectionString = "Data Source=localhost;Initial Catalog=master;Integrated Security=True;Connect Timeout=15";
            var upgrader =
                DeployChanges.To
                    .SqlDatabase(connectionString)
                    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
                    .LogToConsole()
                    .Build();

            var result = upgrader.PerformUpgrade();

            if (!result.Successful)
            {
                status = false;
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine(result.Error);
                Console.ResetColor();
            }
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Success!");
            Console.ResetColor();
            return status;
        }
    }

代码 - 调用 Helper 的 HTTPStart 方法 class

private static ILogger logObj;
           [FunctionName("HttpStart")]
           public static async Task<HttpResponseMessage> Run(
               [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
               [OrchestrationClient] DurableOrchestrationClientBase starter,
               string functionName,
               ILogger log, ExecutionContext context)
       {
           HttpResponseMessage response = null;            
           var config = new ConfigurationBuilder()
           .SetBasePath(context.FunctionAppDirectory)
           .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
           .AddEnvironmentVariables()
           .Build();
           Helper.Helper helper = new Helper.Helper(config.GetConnectionString("ConnString"););
           if (helper.UpgradeDB())
           {
               log.LogInformation("DB Upgraded Successfully");
               logObj = log;
               try
               {
                   var provider = new MultipartMemoryStreamProvider();
                   await req.Content.ReadAsMultipartAsync(provider);
                   Application policy = await GeneratePolicyObject(provider);
                   string instanceId = await starter.StartNewAsync(functionName, policy);
                   log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
                   response = starter.CreateCheckStatusResponse(req, instanceId);
                   response.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(10));
               }
               catch (Exception ex)
               {
                   response = new HttpResponseMessage();
                   log.LogCritical(ex.ToString());
                   log.LogCritical(ex.InnerException.ToString());
                   log.LogCritical(ex.StackTrace);
                   response.Content = new StringContent(ex.ToString());
                   response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
               }
           }
           else log.LogCritical("DB Upgrade Failed. Check logs for exception");
           return response;
       }

查看突出显示的区域。我想模拟构造函数初始化,以便在单元测试时不会发生数据库调用。

谁能帮忙,拜托。

问候塔伦

使用抽象来避免与实现问题的紧密耦合。

public interface IDBHelper {
    bool UpgradeDB();
}

public class DBUPHelper: IDBHelper {
    //...code omitted for brevity
}

此外,由于被测方法是静态的,因此公开了静态 field/property

public static class MyFunction {
    //At run time this will use default helper
    public static IDBHelper Helper = new DBUPHelper();

    private static ILogger logObj;
    [FunctionName("HttpStart")]
    public static async Task<HttpResponseMessage> Run(
        [HttpTrigger(AuthorizationLevel.Function, methods: "post", Route = "orchestrators/{functionName}")] HttpRequestMessage req,
        [OrchestrationClient] DurableOrchestrationClientBase starter,
        string functionName,
        ILogger log, ExecutionContext context)
    {
       HttpResponseMessage response = null;      
       if (helper.UpgradeDB()) {
           log.LogInformation("DB Upgraded Successfully");
           logObj = log;
           try
           {
               var provider = new MultipartMemoryStreamProvider();
               await req.Content.ReadAsMultipartAsync(provider);
               Application policy = await GeneratePolicyObject(provider);
               string instanceId = await starter.StartNewAsync(functionName, policy);
               log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
               response = starter.CreateCheckStatusResponse(req, instanceId);
               response.Headers.RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromSeconds(10));
           }
           catch (Exception ex)
           {
               response = new HttpResponseMessage();
               log.LogCritical(ex.ToString());
               log.LogCritical(ex.InnerException.ToString());
               log.LogCritical(ex.StackTrace);
               response.Content = new StringContent(ex.ToString());
               response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
           }
       }
       else log.LogCritical("DB Upgrade Failed. Check logs for exception");
       return response;
    }
}

隔离测试时可以替换

public async Task TestFunction {
    //Arrange
    var helper = MockRepository.GenerateMock<IDBHelper>();        
    MyFunction.helper = helper; //<<--override default helper with mock
    helper.Stub(_ => _.UpgradeDB()).Return(false);//or true is that is what you desire

    //...arrange other parameters / dependencies

    //Act
    var actual = await MyFunction.Run(...);

    //Assert
    //...
}