2014-09-24 50 views
2

我有一个类处理帐户的东西。它提供了登录,重置密码和创建新账户的方法。TDD - 我做得对吗?

我通过构造函数注入依赖关系。我有验证每个依赖项的引用的测试,如果引用为null,它会引发一个ArgumentNullException。

Account类通过只读属性公开每个这些依赖关系,然后我有测试来验证在构造函数上传递的引用是否与属性返回相同。我这样做是为了确保参考资料是由班级持有的。 (我不知道这是否也是一种好的做法。)

第一个问题:这是TDD的一个好习惯吗?我问这个,因为这个类到目前为止有6个依赖关系,并且它非常重复,而且测试也很长,因为我必须模拟每个测试的所有依赖关系。我所做的只是每次复制和粘贴,只需更改正在测试的依赖项的引用即可。第二个问题:我的账户创建方法执行如验证通过的模型,将数据插入3个不同的表或第四个表(如果存在某组值并发送电子邮件)。我应该在这里测试什么?到目前为止,我有一个测试,检查模型验证是否得到执行,如果调用每个存储库的Add方法,并且在这种情况下,我使用模拟存储库的Moq的回调方法来比较每个添加到存储库的属性我通过模型传递的。

喜欢的东西:

userRepository 
     .Setup(r => r.Add(It.IsAny<User>())) 
     .Callback<User>(u => 
      { 
       Assert.AreEqual(model.Email, u.Email); 
       Assert.IsNotNull(u.PasswordHash); 
       //... 
      }) 
     .Verifiable(); 

正如我所说,这些测试越来越长,我认为它不会伤害测试任何东西我可以,但我不知道这是值得的,因为它编写测试需要时间。

+2

“这是很好的TDD吗?”真的是错误的问题;更有用的问题是“这是否适合我和我的项目?”像TDD这样的方法论,尽管有什么狂热分子会让你觉得这是一种达到目的的手段,而不是自己的目的。 – Casey 2014-09-24 03:57:48

+0

-1这不是一个TDD的问题,它是一个单元测试的设计问题。 – gurun 2014-09-26 13:58:28

+0

这是我的2美分。我不会将您的依赖作为属性公开。我看到了这种被开发者滥用的事情。在过去他们使用依赖关系的属性暴露他们的类内的依赖关系(根本不漂亮)。如果组装SUT类变得单调乏味,可以使用工厂方法创建SUT类,或者更好地使用测试生成器模式。 – Andrew 2014-09-26 20:39:09

回答

4

测试的目的是发现错误。

你真的会有一个属性存在的错误,但没有初始化为构造函数的值吗?另外,由于您使用的是TDD,因此您应该在编写代码之前编写测试,并且代码应该仅用于通过测试。而不是对属性进行不必要的测试,编写一个测试来证明正在使用注入的依赖关系。为了或者这样的测试通过,依赖关系需要存在,它需要是正确的类型,并且需要在特定的场景中使用。

在我的示例中,依赖关系将会因为需要而存在,而不是因为某些人工单元测试要求它存在。

1

通常在单元测试中(特别是TDD中的),您不会测试您正在测试的类中的每一条语句。 TDD单元测试的主要目的是测试该类的业务逻辑,而不是初始化的东西。

换句话说,您给出了场景(记住要包括边缘案例)作为输入并检查结果,结果可以是属性的最终值和/或方法的返回值。

你不想在你的类中测试每一个可能的代码路径的原因是因为如果你以后决定重构你的类,你只需要对你的TDD单元测试做最小的改变,因为它们是应该对实际实施(尽可能)是不可知的。

注意:有其他类型的单元测试,如代码覆盖率测试,旨在测试您的类中的每个代码路径。但是,我个人发现这些测试不切实际,并且在TDD中肯定不受鼓励。

3

你说写这些测试感觉重复。我说你感觉TDD的主要好处。事实上,这并不是写软件而是编写更好的软件,而不是编写更好的软件,因为TDD并不能保证(至少不是固有的)。 TDD迫使您考虑所有设计决策并做出设计决策。的。时间。 (并减少调试时间。)如果您在进行TDD时感到疼痛,通常是因为设计决定回来咬你。接下来是切换到重构帽子并改进设计的时候了。

现在在这种特殊情况下,它只是您的测试设计,但您也必须为这些设计做出设计决定。

至于测试属性是否设置。如果我正确地理解了你,你只是为了测试而暴露这些属性?在那种情况下,我建议不要这样做。假设你有一个构造函数参数的类,有一个测试断言construtor应该扔在空参数:

public class MyClass 
{ 
    public MyClass(MyDependency dependency) 
    { 
     if (dependency == null) 
     { 
      throw new ArgumentNullException("dependency"); 
     } 
    } 
} 

[Test] 
public void ConstructorShouldThrowOnNullArgument() 
{ 
    Assert.Catch<ArgumentNullException>(() => new MyClass(null)); 
} 

(的TestFixture类略)

现在,当你开始写一个测试的实际被测班的业务方法,各部分将开始融合在一起。

[Test] 
public void TestSomeBusinessFunctionality() 
{ 
    MyDependency mockedDependency; 

    // setup mock 
    // mock calls on mockedDependency 

    MyClass myClass = new MyClass(mockedDependency); 

    var result = myClass.DoSomethingOrOther(); 

    // assertions on result 
    // if necessary assertion on calls on mockedDependency 
} 

在这一点上,你将不得不从构造函数注入依赖分配给字段,所以你可以在方法用到它。如果你设法让测试通过而不使用依赖......呃,呃,显然你并不需要它开始。或者,也许,你只会开始需要它进行下一个测试。

关于其他问题。当测试方法或类的所有权限变得麻烦时,TDD告诉你方法/类正在做很多事情,并且可能想分解成容易测试的部分。例如。一个用于验证,一个用于映射,一个用于执行存储调用。

虽然这可能会导致过度工程!所以要留意这一点,你会开始感觉什么时候拒绝更加间接的冲动。 ;)

为了测试属性是否正确映射,我建议使用存根或自制的具有简单属性的假对象。这样你可以简单地比较源和目标属性,不必像发布的那样进行冗长的设置。