大量设置代码:它是否表示存在问题以及存在哪些策略来处理它

时间:2009-07-27 03:52:58

标签: unit-testing refactoring

我的项目的一部分使用COM与iTunes交互。相关测试的目标是验证我的对象要求iTunes API将一组轨道的所有专辑图片导出到文件中。

我已经成功编写了一个可以证明我的代码正在执行此操作的测试,但是为了实现这一点,我不得不将iTunes实现的大部分内容存在,而在单元测试中需要预测thi是我关心的存根设置代码与执行实际测试的代码的比率

我的问题:

  1. 是否存在更多存根设置代码,然后代码表示我的代码中的另一个潜在问题>
  2. 有很多设置代码,我不相信每次测试重复它是一个好主意。重构此代码的最佳方法是什么,以便此设置代码与其他需要使用存根的测试分开,但可供其他测试使用。
  3. 这种接缝就像之前可能会提出的问题一样,所以如果我创建了一个副本,我会事先鼓掌

    供参考,这是我关注的完整单元测试

    [Fact]
        public void Add_AddTrackCollection_AsksiTunesToExportArtForEachTrackInCollectionToAFile()
        {
            var trackCollection = MockRepository.GenerateStub<IITTrackCollection>(null);
            var track = MockRepository.GenerateStub<IITTrack>(null);
            var artworkCollection = MockRepository.GenerateStub<IITArtworkCollection>(null);
            var artwork = MockRepository.GenerateMock<IITArtwork>(null);
            var artworkCache = new ArtworkCache();
            trackCollection.Stub<IITTrackCollection, int>(collection => {return collection.Count; }).Return(5);
            trackCollection.Stub<IITTrackCollection, IITTrack>(collection => { return trackCollection[0]; }).IgnoreArguments().Return(track);
            track.Stub<IITTrack, IITArtworkCollection>(stub => { return stub.Artwork; }).Return(artworkCollection);
            artworkCollection.Stub<IITArtworkCollection, int>(collection => { return collection.Count; }).Return(1);
            artworkCollection.Stub<IITArtworkCollection, IITArtwork>(collection => { return artworkCollection[0]; }).IgnoreArguments().Return(artwork);
            artwork.Expect<IITArtwork>(stub => { stub.SaveArtworkToFile(null); }).IgnoreArguments().Repeat.Times(trackCollection.Count-1);
            artwork.Replay();
            artworkCache.Add(trackCollection);
            artwork.VerifyAllExpectations();
    
            //refactor all the iTunes fake-out that isn't specific to this test into its own method and call that from ctor.
    

2 个答案:

答案 0 :(得分:5)

需要大量的设置代码表明您的测试代码与复杂的接口进行了深入的交互;这不一定是一个问题(它本身不是一个“代码味道”)虽然它可能暗示你最终可能会在该复杂界面前放置facade并与进行交互 (那时测试外观可能需要一些设置,但是你的“实质性”应用程序代码将更简单,更容易测试),至少如果你能识别出你可能正在使用的任何重复的交互模式。

广泛的单元测试框架(例如junitunittest和&amp; c)认识到需要重复的设置代码,从而使用setUp方法定义“测试用例”类(以及相应的tearDown,如果你需要撤消那里的东西)。如果您有其他不同的测试组,但仍需要一些常见的设置代码,而且还有一些设置,即每组但与其他组不同,您可以通过从适当的抽象基类继承,共享“公共设置代码”,调用全局功能,或以您喜欢的语言另外适用。

答案 1 :(得分:2)

解决您的两个问题:

  1. 在两种常见的单元测试模式(排列/动作/断言和四阶段测试)中,几乎总会有比编码更多的编排码,因为根据定义,Act应该只包含一个声明。但是,尝试尽可能减少编排代码仍然是一个好主意。

  2. 将安装程序代码重构为可重用代码的方法不止一种。正如Alex在他的回答中所写,许多单元测试框架都支持设置方法。这被称为隐式夹具设置,在我看来应该避免的事情,因为它不能很好地传达意图。相反,我更喜欢显式设置方法,通常封装为Fixture Object

  3. 通常,对复杂安装代码的需求应始终考虑您是否可以采用不同的方式对API进行建模。情况并非总是这样,但是当它出现时,您通常会得到比您开始时更好,更简洁的API。这是TDD的优势之一。

    对于因为输入很复杂而设置很复杂的情况,我会推荐AutoFixture,这是一个通用的测试数据生成器。

    我在这个答案中使用的许多模式都在xUnit Test Patterns中描述,这是一本很好的书。