【问题标题】:Unit testing methods with dependencies (with no interfaces)具有依赖关系的单元测试方法(没有接口)
【发布时间】:2017-02-13 19:34:19
【问题描述】:

从单元测试开始,我想知道如何测试我们的旧代码,看起来像这样:

public Player DetermineWinner(Player a, Player b)
{
   if(a.Age>b.Age)
    ....  
   ///few more conditions checking properties of both players
}

class Player
{
  public Player (DBConnection c, world w, DateTime logTime)
  {} //not easy to mock...
}

如何模拟它?我知道如果 Player 实现了一个接口,我可以简单地创建一个模拟并在单元测试中使用所需的值传递它,但这里不是这种情况。 Player 类是使用各种参数实例化的,因此我不能在单元测试期间简单地创建一个实例并通过它——它取决于各种外部对象。 我需要模拟 Player 对象并同时设置其属性,以便测试具有确定性。最好的方法是什么?

下次我应该使用接口来解耦吗?

【问题讨论】:

  • 将使用一些外部资源(至少DbConnection)的类Player的所有依赖项放在接口后面或使其方法虚拟。然后您可以使用“模拟”依赖项测试 Player 类。其他方法使用一些“不严格”的模拟框架来为你做这件事
  • 这里的真正问题不是“如何模拟”,而是被测试代码的设计非常糟糕。具体来说,Player 类不应依赖于DBConnection。相反,它的构造函数应该只获取真正属于玩家的数据项,并在其他地方完成读取/写入玩家数据的数据库访问。
  • 您的 Visual Studio 版本是多少?

标签: c# unit-testing mocking


【解决方案1】:

您可以在不更改代码的情况下对代码进行单元测试,方法是使用 Typemock 之类的框架,它可以让您模拟具体依赖项而无需添加接口,并且当您选择要模拟的类时(在本例中为“Player”,typemock还会自动模拟其中的所有依赖项)。

在你给定的例子中:

public class UnitTest1
    {
        [TestMethod]
        public void TestDetermineWinner_B_IsTheWinner()
        {
            var P1 = Isolate.Fake.Instance<Player>();
            var P2 = Isolate.Fake.Instance<Player>();

            Isolate.WhenCalled(() => P1.Age).WillReturn(0);
            Isolate.WhenCalled(() => P2.Age).WillReturn(1);

            var result = new ClassUnderTest().DetermineWinner(P1, P2);

            Assert.AreEqual(P2, result);
        }
    }
    public class ClassUnderTest
    {
        public Player DetermineWinner(Player a, Player b)
        {
            if (a.Age > b.Age) { return a; }
            return b;
        ///few more conditions checking properties of both players
        }
    }
    public class Player
    {
        public Player(DbConnection c, world w, DateTime logTime)
        { } //not easy to mock...

        public int Age { get; internal set; }
    }

【讨论】:

  • 你有点忘了提到你必须为 Typemock 付费 :)
  • 但是为什么你需要为让你的生活更轻松的东西付费呢?
  • 因为购买 VS 企业版更加谨慎 -> 您可以获得相同的功能以及大量其他功能(如历史调试、ADO.net 跟踪等)
【解决方案2】:

我认为你不明白嘲笑的真正含义。

模拟对象与该类的“真实”实现无关。它只是“看起来”像那个类的一个对象。

但是您(分别是模拟框架)可以控制该对象的行为。好吧,除非该字段是 private(参见 here)。所以:当您的 Player 字段不是私有的时,您没有问题。

如果没有模拟框架 - 你真的无能为力。如果有的话,您可能会模拟所有需要作为被测类构造函数参数的对象。

换句话说:最后,你需要一些类“X”的对象“x”;当使用“真实”类“X”给你带来很多麻烦时,你必须用看起来像“X”的东西来代替它。

最坏的情况,你可能有两个不同版本的 X 类;但这会使事情变得相当复杂。

【讨论】:

  • 是的,它看起来就像一个,但是我如何在单元测试期间创建它(假设没有可用的框架)?例如,如果使用 my sn-p,我将不胜感激。
  • 为您提供一些更新,但我担心没有太多可以帮助您的。如果你不能修改源代码,你不能使用模拟框架,那么你就有点坏了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多