自我短路测试模式是否违反单一职责原则?

5
我多次使用过自我隔离的单元测试模式。最近有人质疑它违反了SRP原则,因为测试类现在可以因为两个原因之一而进行更改:当测试发生变化或者实现接口的方法签名发生变化时。经过一段时间的思考,似乎这是一个正确的评估,但我想听听其他人的意见。你怎么看?
参考链接: http://www.objectmentor.com/resources/articles/SelfShunPtrn.pdf
4个回答

5
我的看法是,测试类在技术上违反了SRP原则,但并没有违反SRP的精神。不使用自我屏蔽的替代方法是使用与测试类分离的模拟类。
使用独立的模拟类可能会让你认为它是完全自包含的,并且符合SRP,然而对模拟类属性的语义耦合仍然存在。因此,实际上我们没有实现任何有意义的分离。
以PDF中的例子为例:
public class ScannerTest extends TestCase implements Display
{
  public ScannerTest (String name) {
    super (name);
  }
  public void testScan () {
    // pass self as a display
    Scanner scanner = new Scanner (this);
    // scan calls displayItem on its display
    scanner.scan ();
    assertEquals (new Item (“Cornflakes”), lastItem);
  }
  // impl. of Display.displayItem ()
  void displayItem (Item item) {
    lastItem = item;
  }
  private Item lastItem;
}

现在我们来创建一个模拟对象:
public class DisplayMock implements Display
{
  // impl. of Display.displayItem ()
  void displayItem (Item item) {
    lastItem = item;
  }

  public Item getItem() {
     return lastItem;
  }
  private Item lastItem;
}

public class ScannerTest extends TestCase
{
  public ScannerTest (String name) {
    super (name);
  }
  public void testScan () {
    // pass self as a display
    DisplayMock dispMock = new DisplayMock();
    Scanner scanner = new Scanner (dispMock );
    // scan calls displayItem on its display
    scanner.scan ();
    assertEquals (new Item (“Cornflakes”), dispMock.GetItem());
  }
}

在实际应用中(我个人认为),TestClassDisplayMock之间的耦合度高比违反TestClass的SRP更加严重。此外,使用模拟框架后,这个问题完全消失了。 编辑:我刚刚在Robert C. Martin的优秀书籍《C#敏捷原则、模式与实践》中简要提到了自测试转接器模式。以下是书中的片段:

我们可以通过使用数据库的抽象接口来完成这一点。此抽象接口的一个实现使用真实的数据库。另一个实现是编写的测试代码,用于模拟数据库的行为并检查是否正确地进行了数据库调用。图29-5显示了结构。 PayrollTest模块通过对其进行调用并实现Database接口来测试PayrollModule。这使得PayrollTest能够捕获Payroll对数据库所做的调用。这允许PayrollTest确保Payroll的行为正常。它还允许PayrollTest模拟许多难以创建的数据库故障和问题。这是一种称为自测试转接器的测试模式,有时也称为模拟或欺骗。

图29-5。PayrollTest SELF-SHUNTs数据库

因此,发明SRP的人(在同一本书中详细讨论)使用自测试转接器模式没有任何顾虑。考虑到这一点,我认为在使用此模式时,您可以避免面向对象警察(OOP)。

3

在我看来,这是一种违规行为,但只是很小的一种。

你的测试类现在既是测试类,也是你要测试的依赖项。

然而,这是一件坏事吗?对于一些简单的测试,可能不是。随着测试用例数量的增加,你可能需要重构并使用模拟类来分离一些关注点。(正如你贴出的链接所说,自我引用是通向模拟的一个过渡阶段)。但如果测试用例的数量保持静态和较低,那么问题在哪里呢?

我认为需要一点实用主义。它是否违反了SRP原则?是的,但我猜可能没有你正在测试的系统中的某些代码违反得严重。你需要采取什么措施吗?不需要,只要代码清晰易懂,对我来说这总是最重要的。SRP只是一条指导原则,而不是一条规则。


说得好。正如我在另一条评论中所暗示的那样,我并不反对使用它,但我只是想确认它是否“技术上”违反了SRP原则。 - Javid Jamae

1
如果被实现或代理的接口发生了变化,测试套件很有可能也需要相应变化。因此,我并不认为这违反了单一职责原则。

这绝对是个好观点,但这是一个谬误的论点。仅仅因为测试很可能会发生变化并不意味着使用这种模式就不会导致测试类违反 SRP 原则。话虽如此,从实践角度来看,我并不反对使用这种模式,即使可能会违反 SRP 原则。 - Javid Jamae

1

我更喜欢对我创建的模拟/存根有更多的控制权。当我尝试使用自我隔离模式时,最终使我的测试类变得更加复杂。通过在测试方法中将模拟作为本地变量创建,我最终获得了更清晰的代码。

顺便说一句,除非您使用像C#(或Python或等效物)这样强大的东西,否则当您更改接口时,您的测试代码将会发生变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接