DBUp модульного тестирования (Rhino) в долговременном HTTPS-старте Azure
Технологический стек
- DBUP для обновления БД
- Azure Durable для занятий
- Rhino издевается над юнит-тестированием.
ситуация
В настоящее время я поместил свои инструкции по обновлению БД (DB Up) в метод HTTPStart в качестве точки входа в мою надежную функцию лазури.
DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
проблема
Проблема с этим подходом состоит в том, что DB Up использует статический класс для обновления БД, и я не могу использовать Rhino для макетирования методов в статическом классе.
Вопрос
Я думал об обёртывании части DB Up в нестатический класс, но тогда мне нужно было бы имитировать инициализацию конструктора. Не уверен, что это сработает
Код - вспомогательный класс, который обновляет БД
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;
}
}
Код - метод HTTPStart, вызывающий класс Helper
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;
}
Смотрите выделенную область. Я хочу смоделировать инициализацию конструктора, чтобы вызовы БД не происходили во время модульного тестирования.
Может кто-нибудь помочь, пожалуйста.
С уважением Тарун
1 ответ
Используйте абстракцию, чтобы избежать тесной связи с проблемами реализации.
public interface IDBHelper {
bool UpgradeDB();
}
public class DBUPHelper: IDBHelper {
//...code omitted for brevity
}
Кроме того, поскольку тестируемый метод является статическим, выставьте статическое поле / свойство
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
//...
}