在C#中是否有某种类似于Java的ExpectedSystemExit
?我的代码中有一个退出,非常希望能够测试它。我在C#中找到的唯一一件事就是不太好workaround。
示例代码
public void CheckRights()
{
if(!service.UserHasRights())
{
Environment.Exit(1);
}
}
测试代码
[TestMethod]
public void TestCheckRightsWithoutRights()
{
MyService service = ...
service.UserHasRights().Returns(false);
???
}
我正在使用VS框架进行测试(+ NSubstitute进行模拟)但是切换到nunit或其他任何测试都没有问题。
答案 0 :(得分:9)
您应该使用依赖注入为正在测试的类提供一个提供环境退出的接口。
例如:
public interface IEnvironment
{
void Exit(int code);
}
我们还假设您有一个用于调用UserHasRights()
的界面:
public interface IRightsService
{
bool UserHasRights();
}
现在假设您要测试的类看起来像这样:
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
。)
要解决此问题,您可以引入“环境上下文” - 一种模式,允许您使用静态方法,同时仍保留对其进行单元测试调用的功能。当然,这些事情应该谨慎使用,仅用于真正的跨领域问题。
例如,您可以为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
类消失:
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();
}
}
答案 1 :(得分:0)
如果您的目标是避免额外的类/接口只是为了支持测试,那么您对通过Property Injection的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);
}
答案 2 :(得分:0)
我最终创建了一个新方法,然后我可以在测试中进行模拟。
<强>代码强>
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();
}