在C#中测试Environment.Exit()

时间:2016-05-20 07:44:38

标签: c# unit-testing integration-testing exit-code

在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或其他任何测试都没有问题。

3 个答案:

答案 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();
    }
}

For more information about ambient contexts, see here.

答案 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();
}