4

好的。我试图弄清楚为什么 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();
}
4

2 回答 2

2

这是一个技术和历史限制。

  • 您需要静态字段来在代表之间共享信息(Establish、Because、It、Cleanup)。
  • MSpec 试图模仿 rspec,所以我认为 Aaron 认为代表很合适,并在 2008 年或 2009 年发布了您今天看到的语法。这种语法今天仍然存在。

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

于 2013-08-10T00:47:11.170 回答
1

瞧瞧。我想介绍我的(部分)解决方案(强制夹具和设置隔离)。这并同时解决了管道代码的问题。

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

(注意这使用结构图和结构图/起订量/自动模拟容器。我确信对于不同的容器/模拟框架都是一样的。)

/// <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);
    }

基本上,如果需要共享某些东西,请将其放在夹具本身的实例中。这种“强制”分离……有些什么。

在这方面我还是更喜欢 Xunit。

于 2013-09-23T01:35:21.420 回答