2009-08-03 180 views
8

假设你有一个方法:单元测试(是或否)

public void Save(Entity data) 
{ 
    this.repositoryIocInstance.EntitySave(data); 
} 

你写单元测试呢?

public void TestSave() 
{ 
    // arrange 
    Mock<EntityRepository> repo = new Mock<EntityRepository>(); 
    repo.Setup(m => m.EntitySave(It.IsAny<Entity>()); 

    // act 
    MyClass c = new MyClass(repo.Object); 
    c.Save(new Entity()); 

    // assert 
    repo.Verify(m => EntitySave(It.IsAny<Entity>()), Times.Once()); 
} 

因为以后如果要更改方法的实现做更多的“复杂”的东西,如:

public void Save(Entity data) 
{ 
    if (this.repositoryIocInstance.Exists(data)) 
    { 
     this.repositoryIocInstance.Update(data); 
    } 
    else 
    { 
     this.repositoryIocInstance.Create(data); 
    } 
} 

...单元测试会失败,但它可能不会打破你的应用程序...

问题

应我甚至懒得上的方法创建单元测试,没有任何返回类型*或**不改变内部模拟以外的任何内容?

回答

2

这是真的,你的测试取决于你的实现,这是你应该避免的东西(虽然它有时候并不是那么简单),并不一定是坏的。 但是,如果您的更改不会破坏代码,这些测试预计会中断。

你可以有很多方法是:按预期

  • 创建一个测试,真正进入到数据库并检查是否状态改变(这不会单元测试了)
  • 创建一个伪造数据库并在内存中执行操作(针对repositoryIocInstance的另一个实现)的测试对象,并验证状态是否按预期更改。更改存储库接口也会导致更改此对象。但是你的界面不应该改变太多,对吧?
  • 参见本由于价格太贵一切,用你的方法,这可能会产生对后期不必要的开断试验(但一旦机会是低,它是确定承担的风险)
1

一般的经验法则是,你测试所有的东西,可能会打破。如果您确定,该方法足够简单(并且保持简单)不会成为问题,因此可以通过测试将其解决。

第二件事,你应该测试方法的合同,而不是实施。如果测试在更改后失败,但不是应用程序,那么测试测试不是正确的。测试应涵盖对您的应用程序很重要的案例。这应该确保,对不中断应用程序的方法的每次更改都不会使测试失败。

+0

在这种情况下,此方法不需要单元测试,因为不会中断应用程序,测试会中断。在这种情况下,我正在测试imeplementation,而不是在方法范围内不存在的合同。 – 2009-08-03 10:34:17

1

不返回任何结果的方法仍会改变应用程序的状态。在这种情况下,您的单元测试应该测试新状态是否符合预期。

+0

单元测试中的新状态完全取决于模拟......这就是为什么我有第二个想法。如果我所有的方法确实存在于知识库中,并且在测试中被模拟,那么它就会基于模拟而不是真实的生活情况产生可预测的结果。它会一直通过。 – 2009-08-03 10:32:41

+0

你不应该测试模拟,而是测试与模拟交互的操作。比方说,你有一个调用库的方法为`M()`的对象`X`。你想测试一下调用存储库的方式,所以你编写了一个模型库,然后调用你的`X.M()`方法。测试应该触发模拟中的变化,这将证明你的方法`M()`按需要工作。当你有一个真正的存储库实现,然后你测试该实现(而不是模拟) - 然后你将测试存储库。 – 2009-08-03 11:35:19

+0

@jeyoung:但这是一个单独的单元测试。这是对存储库的单元测试,而不是根据上面的代码调用存储库的类...但是。事情是我不测试DAL,因为它是生成代码。并期望按照它应该的工作。我不打算单元测试它。 – 2009-08-03 14:14:01

1

“单元测试会失败,但它可能不会打破你的应用程序”

这是 - 实际上 - 真正重要的是要知道的。这看起来可能很烦人,但是当别人开始维护你的代码时,他们可能会对Save和(不太可能)破坏应用程序做出非常糟糕的改变。

诀窍是优先。

首先测试重要的东西。当事情缓慢时,为微不足道的东西添加测试。

+0

您可能在最后一句话中是正确的(对此为+1)。这里的主要问题是测试会变得无效,而不是代码,这将演变成更好的版本。 – 2009-08-12 14:20:35

6

不要忘记,单元测试不仅仅是测试代码。这是关于允许您在行为发生变化时确定

所以你可能有一些微不足道的东西。但是,您的实施更改,您可能会有副作用。你想让你的回归测试套件告诉你。

例如通常人们说你不应该测试setter/getters,因为它们很琐碎。我不同意,不是因为他们是复杂的方法,而是有人可能会因疏忽,胖手指等情况而无意中改变他们。

鉴于我刚才所说的,我肯定会对上述内容进行测试(通过嘲讽,并且/或者值得设计你的课程,考虑到可测试性并让他们报告状态等。)

+0

我同意你对回归测试的看法。而且我也会说集成测试也会显示一些非工作部分。但由于我没有为我的DAL创建单元测试(它生成),我想我不应该创建单元测试,除非我改变我的方法有一些返回类型(如布尔)来报告状态。 – 2009-08-03 10:37:14

1

当有ISN”在一个方法中断言,你基本上断言异常不会被抛出。

我也在努力解决如何测试public void myMethod()的问题。我想如果你决定添加一个可测试性的返回值,那么返回值应该代表所有必要的重要事实,以查看应用程序状态的变化。

public void myMethod() 

成为

public ComplexObject myMethod() { 
DoLotsOfSideEffects() 
return new ComplexObject { rows changed, primary key, value of each column, etc }; 
} 

,而不是

public bool myMethod() 
    DoLotsOfSideEffects() 
    return true; 
2

简短的回答你的问题是:,你一定要测试方法类似。

我假设它是重要那Save方法实际上是保存了的数据。如果你不为此编写单元测试,那么你怎么知道?

其他人可能会出现并删除调用EntitySave方法的代码行,并且任何单元测试都不会失败。后来,你想知道为什么项目永远不会持续......

在你的方法,你可以说任何人删除该行只会这样做,如果他们有恶意的意图,但事情是:简单的事情不'不一定要保持简单,你最好在事情变得复杂之前编写单元测试。

它是而不是 Save方法在Repository上调用EntitySave的实现细节 - 它是预期行为的一部分,也是非常关键的部分,如果我可以这样说的话。你想确保数据实际上被保存。

仅仅因为一个方法没有返回值并不意味着它不值得测试。一般来说,如果您观察到良好的命令/查询分离(CQS),则预计任何无效方法都会更改的某些内容

有时候某些东西是类本身,但有时候,它可能是其他东西的状态。在这种情况下,它改变了Repository的状态,这就是你应该测试的。

这就是所谓的测试Inderect输出,而不是更正常直接输出(返回值)。

诀窍是编写单元测试,以防止它们经常被打破。当使用Mocks时,很容易意外地写出指定测试,这就是为什么大多数动态模拟(如Moq)默认为存根模式,其中调用给定方法的次数无关紧要。

所有这一切,以及更多,在优秀的xUnit Test Patterns解释。

3

问问你自己两个问题。 “这个单元测试的手册相当于什么?”和“是否值得自动化?”。在你的情况下,它会是这样的:

什么是手动等效? - 启动调试器 - 步入“保存”方法 - 步到明年,确保你IRepository.EntitySave里面执行

是否值得自动化?我的答案是“不”。代码是100%显而易见的。 从数百个类似的废物测试中,我没有看到一个会变得有用的单个测试。

相关问题