当需要静态成员/方法时,如何在MSpec中强制执行测试隔离?

时间:2013-08-09 01:55:18

标签: c# bdd mspec

确定。我试图解决为什么MSpec使用静态方法/变量。 (不完全是静态方法,但是使用成员变量委托,它实际上是相同的。)

这使得无法重用上下文。那或通过并确保手动重置所有静态变量。这对测试隔离没有强制执行。如果一个测试设置了一些变量,而下一个测试就检查了它,那么当它不应该传递时,它会通过。

这开始变得非常烦人。我在一个“因为”语句中所做的只是留在那里,而不是仅仅因为它共享相同的上下文而进行其他随机测试。

编辑 -

问题是,我如何“执行”测试隔离。例如,请查看下面的规范,分享FooContext。如果should_not_throw通过,我们会猜测一下吗?

public class FooContext
{
    Establish context = () => Subject = new Foo();

    public static Foo Subject;
    public static int result;
    public static Exception ex;
}

public class When_getting_an_int_incorrectly : FooContext
{
    Because of = () => ex = Exception.Catch(() => result = Subject.GetInt(null)); 

    It should_throw = () => ex.ShouldNotBeNull();
}

public class When_getting_an_int_correctly : FooContext
{
    Because of = () => ex = Exception.Catch(() => result = Subject.GetInt(0));

    It should_not_throw = () => ex.ShouldBeNull();
}

2 个答案:

答案 0 :(得分:2)

这是技术上和历史上的限制。

  • 您需要静态字段以在代理之间共享信息(建立,因为,它,清理)。
  • MSpec试图模仿rspec,所以我认为Aaron认为代表是合适的并且发布了你今天在2008年或2009年看到的语法。这种语法今天仍然存在。

至于上下文共享/上下文基类:从您所声明的情况来看,您似乎过度使用了这个概念。您应该始终初始化Establish中的静态字段,因此全局状态将成为非问题。应该充分考虑上下文共享,因此,引用它,它不会随机发生。尝试使用辅助方法进行复杂的设置,并在Establishs中更详细(我会说明确)。它将有助于使您的规范更具可读性。

答案 1 :(得分:1)

罗,看哪。我想提出我的(部分)解决问题的方法(强制执行夹具和设置隔离)。那并同时解决了管道代码的问题。

我基本上将一个自动插入容器放在夹具的实例中,并确保为每个规格重新创建夹具。如果需要其他一些设置,只需继承或添加到灯具。

(注意这会使用结构图和结构图/ moq / automocking容器。我相信它对于不同的容器/模拟框架都是一样的。)

/// <summary>
/// This is a base class for all the specs. Note this spec is NOT thread safe. (But then
/// I don't see MSpec running parallel tests anyway)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <remarks>
/// This class provides setup of a fixture which contains a) access to class under test
/// b) an auto mocking container and c) enforce a clean fixture for every spec.
/// </remarks>
public abstract class BaseSpec<T>
    where T : class
{
    public static TestFixture Fixture;
    private Establish a_new_context = () =>
        {
            Fixture = new TestFixture();
            MockedTypes = new Dictionary<Type, Action>();
        };

    /// <summary>
    /// This dictionary holds a list of mocks that need to be verified by the behavior.
    /// </summary>
    private static Dictionary<Type, Action> MockedTypes;
    /// <summary>
    /// Gets the mock of a requested type, and it creates a verify method that is used
    /// in the "AllMocksVerified" behavior.
    /// </summary>
    /// <typeparam name="TMock"></typeparam>
    /// <returns></returns>
    public static Mock<TMock> GetMock<TMock>()
        where TMock : class
    {
        var mock = Mock.Get(Fixture.Context.Get<TMock>());

        if (!MockedTypes.ContainsKey(typeof(TMock)))
            MockedTypes.Add(typeof(TMock), mock.VerifyAll);

        return mock;
    }

    [Behaviors]
    public class AllMocksVerified
    {
        private Machine.Specifications.It should_verify_all =
        () =>
        {
            foreach (var mockedType in MockedTypes)
            {
                mockedType.Value();
            }
        };
    }

    public class TestFixture
    {
        public MoqAutoMocker<T> Context { get; private set; }

        public T TestTarget
        {
            get { return Context.ClassUnderTest; }
        }

        public TestFixture()
        {
            Context = new MoqAutoMocker<T>();
        }
    }
}

这是一个示例用法。

    public class get_existing_goo : BaseSpec<ClassToTest>
    {
        private static readonly Goo Param = new Goo();

        private Establish goo_exist =
            () => GetMock<Foo>()
                      .Setup(a => a.MockMethod())
                      .Returns(Param);

        private static Goo result;

        private Because goo_is_retrieved =
            () => result = Fixture.Context.ClassUnderTest.MethodToTest();

        private It should_not_be_null =
            () => result.ShouldEqual(Param);
    }

基本上,如果需要共享某些内容,请将其放在fixture本身的实例中。这“强制”分离......一些什么。

在这方面,我仍然更喜欢Xunit。