在 C# 中测试 Environment.Exit()
Test Environment.Exit() in C#
C# 中是否有某种等价于 ExpectedSystemExit
in Java? I have an exit in my code and would really like to be able to test it. The only thing I found in C# is a not really nice workaround.
的东西?
示例代码
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
测试代码
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.UserHasRights().Returns(false);
???
}
我正在使用 VS 框架进行测试(+ NSubstitute 用于模拟),但切换到 nunit 或其他任何测试都不是问题。
您应该使用依赖注入来为正在测试的 class 提供一个提供环境出口的接口。
例如:
public interface IEnvironment
{
void Exit(int code);
}
我们还假设您有一个调用 UserHasRights()
:
的接口
public interface IRightsService
{
bool UserHasRights();
}
现在假设您要测试的 class 看起来像这样:
public sealed class RightsChecker
{
readonly IRightsService service;
readonly IEnvironment environment;
public RightsChecker(IRightsService service, IEnvironment environment)
{
this.service = service;
this.environment = environment;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
environment.Exit(1);
}
}
}
现在您可以使用模拟框架来检查是否在正确的条件下调用了 IEnvironment .Exit()。例如,使用 Moq
它可能看起来有点像这样:
[TestMethod]
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<IEnvironment>();
var rightsChecker = new RightsChecker(rightsService.Object, enviromnent.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
环境背景和交叉关注点
像Environment.Exit()
这样的方法可以被认为是一个横切关注点,你可能希望避免为它传递一个接口,因为你最终可能会得到额外的构造函数参数的爆炸式增长. (注意:横切关注点的典型例子是DateTime.Now
。)
为了解决这个问题,您可以引入一个 "Ambient context" - 一种允许您使用静态方法的模式,同时仍然保留对它进行单元测试调用的能力。当然,这些东西应该谨慎使用,并且只用于真正的横切关注点。
例如,您可以像这样为 Environment
引入环境上下文:
public abstract class EnvironmentControl
{
public static EnvironmentControl Current
{
get
{
return _current;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
_current = value;
}
}
public abstract void Exit(int value);
public static void ResetToDefault()
{
_current = DefaultEnvironmentControl.Instance;
}
static EnvironmentControl _current = DefaultEnvironmentControl.Instance;
}
public class DefaultEnvironmentControl : EnvironmentControl
{
public override void Exit(int value)
{
Environment.Exit(value);
}
public static DefaultEnvironmentControl Instance => _instance.Value;
static readonly Lazy<DefaultEnvironmentControl> _instance = new Lazy<DefaultEnvironmentControl>(() => new DefaultEnvironmentControl());
}
普通代码只调用 EnvironmentControl.Current.Exit()
。通过此更改,IEnvironment
参数从 RightsChecker
class:
中消失
public sealed class RightsChecker
{
readonly IRightsService service;
public RightsChecker(IRightsService service)
{
this.service = service;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
EnvironmentControl.Current.Exit(1);
}
}
}
但是我们仍然保留单元测试它被调用的能力:
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<EnvironmentControl>();
EnvironmentControl.Current = enviromnent.Object;
try
{
var rightsChecker = new RightsChecker(rightsService.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
finally
{
EnvironmentControl.ResetToDefault();
}
}
如果您的目标是避免额外的 classes/interfaces 只是为了支持测试,您如何看待通过 属性 注入进行的 Environment.Exit 操作?
class RightsChecker
{
public Action AccessDeniedAction { get; set; }
public RightsChecker(...)
{
...
AccessDeniedAction = () => Environment.Exit();
}
}
[Test]
public TestCheckRightsWithoutRights()
{
...
bool wasAccessDeniedActionExecuted = false;
rightsChecker.AccessDeniedAction = () => { wasAccessDeniedActionExecuted = true; }
...
Assert.That(wasAccessDeniedActionExecuted , Is.True);
}
我最终创建了一个新方法,然后我可以在测试中对其进行模拟。
代码
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
internal virtual void Exit()
{
Environment.Exit(1);
}
单元测试
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.When(svc => svc.Exit()).DoNotCallBase();
...
service.CheckRights();
service.Received(1).Exit();
}
C# 中是否有某种等价于 ExpectedSystemExit
in Java? I have an exit in my code and would really like to be able to test it. The only thing I found in C# is a not really nice workaround.
示例代码
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
测试代码
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.UserHasRights().Returns(false);
???
}
我正在使用 VS 框架进行测试(+ NSubstitute 用于模拟),但切换到 nunit 或其他任何测试都不是问题。
您应该使用依赖注入来为正在测试的 class 提供一个提供环境出口的接口。
例如:
public interface IEnvironment
{
void Exit(int code);
}
我们还假设您有一个调用 UserHasRights()
:
public interface IRightsService
{
bool UserHasRights();
}
现在假设您要测试的 class 看起来像这样:
public sealed class RightsChecker
{
readonly IRightsService service;
readonly IEnvironment environment;
public RightsChecker(IRightsService service, IEnvironment environment)
{
this.service = service;
this.environment = environment;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
environment.Exit(1);
}
}
}
现在您可以使用模拟框架来检查是否在正确的条件下调用了 IEnvironment .Exit()。例如,使用 Moq
它可能看起来有点像这样:
[TestMethod]
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<IEnvironment>();
var rightsChecker = new RightsChecker(rightsService.Object, enviromnent.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
环境背景和交叉关注点
像Environment.Exit()
这样的方法可以被认为是一个横切关注点,你可能希望避免为它传递一个接口,因为你最终可能会得到额外的构造函数参数的爆炸式增长. (注意:横切关注点的典型例子是DateTime.Now
。)
为了解决这个问题,您可以引入一个 "Ambient context" - 一种允许您使用静态方法的模式,同时仍然保留对它进行单元测试调用的能力。当然,这些东西应该谨慎使用,并且只用于真正的横切关注点。
例如,您可以像这样为 Environment
引入环境上下文:
public abstract class EnvironmentControl
{
public static EnvironmentControl Current
{
get
{
return _current;
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
_current = value;
}
}
public abstract void Exit(int value);
public static void ResetToDefault()
{
_current = DefaultEnvironmentControl.Instance;
}
static EnvironmentControl _current = DefaultEnvironmentControl.Instance;
}
public class DefaultEnvironmentControl : EnvironmentControl
{
public override void Exit(int value)
{
Environment.Exit(value);
}
public static DefaultEnvironmentControl Instance => _instance.Value;
static readonly Lazy<DefaultEnvironmentControl> _instance = new Lazy<DefaultEnvironmentControl>(() => new DefaultEnvironmentControl());
}
普通代码只调用 EnvironmentControl.Current.Exit()
。通过此更改,IEnvironment
参数从 RightsChecker
class:
public sealed class RightsChecker
{
readonly IRightsService service;
public RightsChecker(IRightsService service)
{
this.service = service;
}
public void CheckRights()
{
if (!service.UserHasRights())
{
EnvironmentControl.Current.Exit(1);
}
}
}
但是我们仍然保留单元测试它被调用的能力:
public static void CheckRights_exits_program_when_user_has_no_rights()
{
var rightsService = new Mock<IRightsService>();
rightsService.Setup(foo => foo.UserHasRights()).Returns(false);
var enviromnent = new Mock<EnvironmentControl>();
EnvironmentControl.Current = enviromnent.Object;
try
{
var rightsChecker = new RightsChecker(rightsService.Object);
rightsChecker.CheckRights();
enviromnent.Verify(foo => foo.Exit(1));
}
finally
{
EnvironmentControl.ResetToDefault();
}
}
如果您的目标是避免额外的 classes/interfaces 只是为了支持测试,您如何看待通过 属性 注入进行的 Environment.Exit 操作?
class RightsChecker
{
public Action AccessDeniedAction { get; set; }
public RightsChecker(...)
{
...
AccessDeniedAction = () => Environment.Exit();
}
}
[Test]
public TestCheckRightsWithoutRights()
{
...
bool wasAccessDeniedActionExecuted = false;
rightsChecker.AccessDeniedAction = () => { wasAccessDeniedActionExecuted = true; }
...
Assert.That(wasAccessDeniedActionExecuted , Is.True);
}
我最终创建了一个新方法,然后我可以在测试中对其进行模拟。
代码
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
internal virtual void Exit()
{
Environment.Exit(1);
}
单元测试
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.When(svc => svc.Exit()).DoNotCallBase();
...
service.CheckRights();
service.Received(1).Exit();
}