【问题标题】:How do I enforce test isolation in MSpec when static members/methods are required?当需要静态成员/方法时,如何在 MSpec 中强制执行测试隔离?
【发布时间】:2013-08-09 01:55:18
【问题描述】:

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

【问题讨论】:

  • 尤其是与 XUnit 测试库相比,MSpec 显得一团糟。
  • 您的问题,正如所写的那样,有点咄咄逼人。但是,我可以看到您那里有有价值的内容。您的主要关注点是什么: A) 为什么 MSpec 使用自定义委托? B)如何在 MSpec 中重用上下文。或 C)如何在 MSpec 中隔离副作用和全局状态。你也许可以从中得到几个问题。
  • 如果您提供一些代码示例来说明您的问题,您可能会得到更有帮助的答案。
  • 问题已编辑。添加了示例代码。
  • @AnthonyMastrean C) 我感兴趣的是如何隔离副作用等。

标签: c# bdd mspec


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

    /// <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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-11
      • 1970-01-01
      • 1970-01-01
      • 2011-09-19
      • 1970-01-01
      相关资源
      最近更新 更多