单元测试:program.cs Main 中的 try 语句
Unit testing: try statement in program.cs Main
我的项目如下所示。我想创建一个单元测试来验证当 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();
}
}
}
在不更改任何代码的情况下,有两种方法可以做到这一点:
实际上运行程序使用Process
class and examine the exit code of your program (exposed in the Process.ExitCode
属性)。在这种情况下,class 是 internal
而 Main
是 private
的事实无关紧要。
使用反射获取 Main
方法并调用它,检查 return 值。
如果其中 none 是可口的,您可以将 Main
更改为 internal
并使用 [InternalsVisibleTo]
属性使您的单元测试无需调用该方法倒影。
对于所有这 3 种情况,如果退出代码为 0,则测试通过;如果退出代码为 2,则测试失败(假设没有其他可能的退出代码)。
在最好的情况下,您会将 Log
依赖项注入到 Main
或您尝试测试的任何 class 中。然后您将测试您的 Log
方法在您开发的任何情况下都被调用。您还可以注入配置和文件访问依赖项,而不是紧密耦合,这样您就不必在物理媒体上实际进行 read/write 访问来测试您的代码。目前你不能做真正的单元代码测试因为你有紧密耦合的外部依赖,所以你的代码在技术上正在做集成测试。
我建议阅读 Autofac DI container in console app. (I don't have any affiliate with 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% 有效。
现在,当我想测试 worker 时,我几乎可以测试任何东西:
(我假设您想使用 nSubstitute and 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);
}
}
我的项目如下所示。我想创建一个单元测试来验证当 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();
}
}
}
在不更改任何代码的情况下,有两种方法可以做到这一点:
实际上运行程序使用
Process
class and examine the exit code of your program (exposed in theProcess.ExitCode
属性)。在这种情况下,class 是internal
而Main
是private
的事实无关紧要。使用反射获取
Main
方法并调用它,检查 return 值。
如果其中 none 是可口的,您可以将 Main
更改为 internal
并使用 [InternalsVisibleTo]
属性使您的单元测试无需调用该方法倒影。
对于所有这 3 种情况,如果退出代码为 0,则测试通过;如果退出代码为 2,则测试失败(假设没有其他可能的退出代码)。
在最好的情况下,您会将 Log
依赖项注入到 Main
或您尝试测试的任何 class 中。然后您将测试您的 Log
方法在您开发的任何情况下都被调用。您还可以注入配置和文件访问依赖项,而不是紧密耦合,这样您就不必在物理媒体上实际进行 read/write 访问来测试您的代码。目前你不能做真正的单元代码测试因为你有紧密耦合的外部依赖,所以你的代码在技术上正在做集成测试。
我建议阅读 Autofac DI container in console app. (I don't have any affiliate with 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% 有效。 现在,当我想测试 worker 时,我几乎可以测试任何东西: (我假设您想使用 nSubstitute and 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);
}
}