您是否使用TestInitialize或测试类构造函数来准备每个测试?为什么?

时间:2008-12-02 16:15:27

标签: .net unit-testing constructor

这个问题涉及使用MSTest在Visual Studio中进行单元测试(这很重要,因为MSTest的execution order)。标记为[TestInitialize]的方法和测试类构造函数都将在每个测试方法之前运行。

所以,问题是,你在这些领域中倾向于做什么?你是否避免在两者中进行某些活动?你的理由是什么:风格,技术,迷信?

10 个答案:

答案 0 :(得分:49)

构造函数只是该语言提供的结构。每个测试框架似乎都有自己的受控生命周期“初始化”。你可能只会使用构造函数来改变你的本地人。

MSTest:每个TestMethod都会获得一个测试类的全新实例。这可能是唯一可以在构造函数,初始化程序或测试方法中改变局部变量并且不影响其他测试方法的情况。

public class TestsForWhatever
{
    public TestsForWhatever()
    {
        // You get one of these per test method, yay!
    }

    [TestInitialize] 
    public void Initialize() 
    {
        // and one of these too! 
    }

    [TestMethod]
    public void AssertItDoesSomething() { }

    [TestMethod]
    public void AssertItDoesSomethingElse() { }
}

MSpec:您的所有断言(Establish)只能获得一个BecauseIt。所以,不要在断言中改变你的本地人。并且不依赖于基本上下文中的本地变异(如果使用它们)。

[Subject(typeof(Whatever))]
public class When_doing_whatever
{
    Establish context = () => 
    { 
        // one of these for all your Its
    };

    Because of = () => _subject.DoWhatever();

    It should_do_something;
    It should_do_something_else;
}

答案 1 :(得分:17)

以下是我在TestInitialize中找到的一些优点。

  • 在实例化测试类之前,无法访问某些环境变量(例如TestContext)。
  • 可以通过标记基本TestInitialize方法abstract来实现派生类的实现。
  • 可以轻松覆盖基础TestInitialize方法,并确定是在派生的impl之前,之后还是根本调用基本impl。相反,如果从基本测试类派生测试类,则在无参数构造函数的情况下,无论您是否打算使用基本ctor,都将调用它。
  • 其明确的定义使意图清晰,并补充了TestCleanup方法。你可能会争辩说你可以为每个构造函数创建一个destructor,但不能保证MS Test会像你期望的那样处理析构函数。

答案 2 :(得分:15)

使用TestInitialize()或ClassInitialize()而不是测试类实例或静态构造函数的主要优点是它的显式特性。它清楚地表明您在测试之前正在进行一些设置。持续这样做可以长期提高可维护性。

答案 3 :(得分:4)

我更喜欢使用[TestInitialize]方法来执行被测对象及其参数的实例化。如果需要实例化测试基类(通常是我创建或刷新存储库的位置等),我只在构造函数中执行工作。这有助于我将测试框架代码和测试代码在逻辑上和物理上分开。

答案 4 :(得分:3)

What’s the difference between using the constructor in VS Testing framework vs. TestInitialize() attribute?

也提出了这个问题(稍后)

FWIW我认为“类构造函数”是指instance constructor(不是static constructor)。

我相信您提出的同样问题同样可以询问静态构造函数与ClassInitialize ...

答案 5 :(得分:1)

您测试的对象不需要在[TestInitialize]方法中实例化。您可以在测试方法[Test]中测试对象的构造函数。

[TestInitialize]中的对象可以是设置持久存储或准备测试对象将在测试中使用的值。

答案 6 :(得分:1)

我说使用构造函数,除非你需要readonly

  1. 如果你能保持简单,为什么不呢。构造函数比魔法属性更简单。
  2. 您可以使用{{1}}这是测试初始化​​中的一件大事,您希望为他们不应该更改的测试准备内容(理想情况下,您准备的内容也是不可变的)。

答案 7 :(得分:0)

这取决于场景。如果你有一个测试类,并且出于一些奇怪的原因,如果你需要在另一个测试类上创建它的实例,你将需要使用构造函数。

否则测试初始化​​更符合概念。首先,与上面写的相同的原因,第二个MS可以在该属性上引入更多的功能,你将从中受益,你将坚持使用构造函数。

答案 8 :(得分:0)

我知道我参加晚会很晚,但是有了async,这就是[TestInitialize]的另一个原因(当问这个问题时这个问题没有延续)。允许您执行async操作来设置(例如加载文件),这在构造函数中是不可能的:

        private string approver;        

        [TestInitialize]
        public async Task Initialize()
        {
            approver = File.ReadAllTextAsync("approver.json");
        }

答案 9 :(得分:0)

我希望有人仍然需要。这是我的解决方案,如何对类构造函数进行单元测试。 我正在单元测试类服务中,如果debuggingService为null,则会引发异常。

DebuggingStepTests类构造函数

private readonly IDebuggingService debuggingService;

public string StepName { get; set; }

public DebuggingStep(IDebuggingService _debuggingService)
{
    _log.Starting();
    StepName = "DebuggingStep";

    debuggingService = _debuggingService 
        ?? throw new ArgumentException("DebuggingStep init failure due to => IDebuggingService null");
}

UnitTests看起来像这样

    [Fact]
public void TestDebuggingStepConstructorWhen_InitServiceIsNull_ResultArgumentException() 
{
    //Arrange
    var arrange = new Action(() => 
    {
        new DebuggingStep(null);
    });

    //Act

    //Arrange
    Assert.Throws<ArgumentException>(arrange);
}

和实际结果: enter image description here

希望这对某人有帮助