У меня есть проект, который начинается, как показано ниже. Я хочу создать модульный тест, чтобы проверить, что происходит, когда appsetting.Environment.json не существует или файл DataSource:JsonPath не существует.

Я использую xUnit, но только начинаю понимать,

В этом сценарии он должен выдать FileNotFoundException и выйти.

internal class Program
{
    private static int Main(string[] args)
    {
        public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ENVIRONMENT") ?? "Production"}.json", optional: true)
            .Build();

        try
        {
            Log.Information($"{DateTime.Now.ToLongTimeString()} Starting...");

            var jsonFileName = Configuration["DataSource:JsonPath"];
            if (!File.Exists(jsonFileName)) throw new FileNotFoundException(jsonFileName);

            //code if file exist...
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Terminated unexpectedly");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }
}
0
KaptainJ 24 Сен 2018 в 19:46

2 ответа

Лучший ответ

Не меняя код, это можно сделать двумя способами:

  • Фактически запустите программу с помощью Process и изучите код выхода вашей программы (представленный в Process.ExitCode свойство). В этом случае не имеет значения тот факт, что класс - internal, а Main - private.

  • Используйте отражение, чтобы получить метод Main и вызвать его, исследуя возвращаемое значение.

Если ни один из них не подходит, вы можете изменить свой Main на internal и использовать [InternalsVisibleTo], чтобы позволить модульному тесту вызывать этот метод без отражения.

Во всех трех случаях ваш тест проходит успешно, если код выхода равен 0, и терпит неудачу, если код выхода равен 2 (при условии, что других возможных кодов выхода нет).

1
Kit 24 Сен 2018 в 17:47

В лучшем случае у вас была бы зависимость Log, введенная в Main или в любой другой класс, который вы пытаетесь протестировать. Затем вы должны проверить, вызываются ли ваши методы Log при любых обстоятельствах, которые вы разработали. Вам также будет введена зависимость конфигурации и доступа к файлам вместо того, чтобы быть тесно связанными, поэтому вам не нужно фактически выполнять реальный доступ для чтения / записи на физическом носителе для тестирования вашего кода. В его нынешнем виде вы не можете проводить реальное тестирование модульного кода , потому что у вас есть сильно связанные внешние зависимости, поэтому код технически выполняет интеграционное тестирование.

Я бы посоветовал прочитать контейнер Autofac DI в консольном приложении. (У меня нет аффилированного лица с Autofac, я просто предпочитаю его другим фреймворкам DI. Таких много некоторые исследования, если вы решите их использовать). Если бы я реализовал это, это могло бы выглядеть примерно так:

internal class Program
{
    private static int Main(string[] args)
    {
      var container = CreateContainer();
      var worker = container.resolve<IWorker>();
      worker.Execute();
    }

}

internal class Worker : IWorker
{
    private IConfiguration _configuration;
    private IFileSystem _filesystem;
    private ILog _log;
    private IDateTimeFactory _datetimeFactory;

    public Worker(
      IConfiguration configuration,
      IFileSystem filesystem,
      ILog log,
      IDateTimeFactory _datetimeFactory)
    {
      _configuration = configuration;
      _filesystem = filesystem;
      _log = log;
      _datetimeFactory = datetimeFactory;
    }

    public void Execute()
    {
        try
        {
            _log.Information($"{_datetimeFactory.Now.ToLongTimeString()} Starting...");

            var jsonFileName = _configuration["DataSource:JsonPath"];
            if (!_filesystem.Exists(jsonFileName)) 
              throw new FileNotFoundException(jsonFileName);

            //code if file exist...
        }
        catch (Exception ex)
        {
            _log.Fatal(ex, "Terminated unexpectedly");
            return 1;
        }
        finally
        {
            _log.CloseAndFlush();
        }
    }
}

Ниже приведен общий пример, я не могу гарантировать, что он работает на 100%. Теперь, когда я хочу протестировать воркера, я могу протестировать буквально все: (Я предполагаю, что вы хотите протестировать с помощью nSubstitute и nUnit)

[TestFixture]
public WorkerTests
{
  [Test]
  public void Execute_ConfigurationFileNotFound_LogsException()
  {
     var config = Substitute.For<IConfiguration>();
     var filesystem = Substitute.For<IFileSystem>();
     var log = Substitute.For<ILog>();
     var datetime = Substitute.For<IConfiIDateTimeFactory>();

     var worker = new Worker(
       config,
       filesystem,
       log,
       datetime)

     // setup the mock'd file system to always return false for Exists()
     // That is what we're testing
     filesystem.Exists(string.Empty).ReturnsForAnyArgs(false);

     worker.Execute();

     Assert.That(log.Received().Fatal(), Is.True);
  }
}
0
Erik Philips 24 Сен 2018 в 19:28