方法的单元测试'结构'?

时间:2011-06-22 13:35:43

标签: unit-testing black-box white-box

很抱歉很长的帖子......

在介绍一个棕色的野外项目时,我对某些单元测试和想法有疑问。假设您有一个存储过程,包装存储过程和开发人员指南中的某些指南(规则),描述如何构建此类。该课程可能如下所示:

public class PersonRepository
{
public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
{
    using (new SomeProfiler("someKey"))
    {
        var sp = Ioc.Resolve<IPersonStoredProcedure>();

        sp.addNameArguement(personName);
        sp.addCityArguement(cityName);

        return sp.invoke();
    }
} }

现在,我当然会编写一些集成测试,测试可以调用SP,并且行为符合预期。但是,我会编写断言的单元测试:

  • 具有输入参数“someKey”的SomeProfiler的构造函数称为
  • PersonStoredProcedure的构造函数称为
  • 使用参数personName
  • 调用存储过程的addNameArgument方法
  • 使用参数cityName
  • 调用存储过程的addCityArgument方法
  • 在存储过程中调用invoke方法 -

如果是这样,除了行为之外,我可能会测试方法的整个结构。我最初的想法是它有点矫枉过正。但是,关于团队执行的编码实践,这些测试确保了统一且“正确”的结构,并且正确调用了下一层(从DAL到DB,从BLL到DAL等)。

在我的例子中,这些类型的测试是针对应用程序的每一层执行的。

跟进问题 - SomeProfiler类的使用对我来说有点像常规 - 而是为此创建显式测试,可以通过使用静态代码分析或单元测试+反射创建约定样式测试吗?

提前致谢。

2 个答案:

答案 0 :(得分:0)

我认为你最初的想法是正确的 - 这是一种矫枉过正。虽然您可以使用反射来确保该类具有您期望的方法,但我不确定您是否希望以这种方式进行测试。

也许不是单元测试,你应该使用一些工具,如FxCop / StyleCop或nDepend,以确保特定程序集/ dll中的所有类都具有这些属性。

说过我是“只需要你需要的代码”的信徒为什么要测试一个方法存在,要么你在代码中的某个地方使用它,那么你可以测试特定的情况,或者你没有 - 所以这是无关紧要的。

答案 1 :(得分:0)

单元测试应该关注行为,而不是实现。因此,编写测试以验证是否设置或传入某些参数并不会给测试策略增加太多价值。

由于提供的示例似乎与您的数据库进行通信,因此它无法真正被视为“单元测试”,因为它必须与具有其他设置和前提条件的物理依赖项进行通信,例如环境的可用性,数据库模式,现有数据,存储过程等。您编写的任何测试实际上也验证了这些前提条件。

在目前情况下,对这些类型的测试最好的选择是测试类提供的行为 - 在存储库中调用方法,然后验证结果是否符合预期。但是,你会突然意识到这里有一个隐藏的成本 - 数据库在测试运行之间保持状态,你需要额外的设置或拆卸逻辑来确保数据库处于一个众所周知的状态。

虽然我意识到这个问题的目的是测试一个“黑匣子”,但很明显,你的API中有一些隐藏的魔法。我对解决众所周知的状态问题的偏好是使用内存数据库,该数据库的范围限定为当前测试,这使我与环境因素隔离开来,并使我能够并行化我的集成测试。我敢打赌,在目前的设计下,没有“接缝”以编程方式引入数据库配置,因此你“陷入困境”。根据我的经验,魔法会伤害。

然而,对现有设计的轻微改变解决了这个问题,“神奇”消失了:

public class PersonRepository : IPersonRepository
{
      private ConnectionManager _mgr;

      public PersonRepository(ConnectionManager mgr)
      {
         _mgr = mgr;
      }

      public PersonCollection FindPersonsByNameAndCity(string personName, string cityName)
      {
           using (var p = _mgr.CreateProfiler("somekey"))
           {
                 var sp = new PersonStoredProcedure(p);

                 sp.addArguement("name", personName);
                 sp.addArguement("city", cityName);

                 return sp.invoke();
           }
      }
}