Unit testing 大量的设置代码:它是否表明存在问题,以及存在哪些策略来处理它
我项目的一部分使用COM与iTunes交互。该测试的目标是验证我的对象是否要求iTunesAPI将一组曲目的所有专辑插图导出到一个文件中 我已经成功地编写了一个测试,可以证明我的代码正在这样做,但是为了完成这一点,我不得不删除iTunes实现的一部分,而这是在单元测试中可以预期的。我关心的是存根设置代码与实际测试代码的比率 我的问题是:Unit testing 大量的设置代码:它是否表明存在问题,以及存在哪些策略来处理它,unit-testing,refactoring,Unit Testing,Refactoring,我项目的一部分使用COM与iTunes交互。该测试的目标是验证我的对象是否要求iTunesAPI将一组曲目的所有专辑插图导出到一个文件中 我已经成功地编写了一个测试,可以证明我的代码正在这样做,但是为了完成这一点,我不得不删除iTunes实现的一部分,而这是在单元测试中可以预期的。我关心的是存根设置代码与实际测试代码的比率 我的问题是: 有更多的存根设置代码而不是代理代码这一事实是否表明我的代码中存在另一个潜在问题> 有很多设置代码,我不认为每次测试都重复它是个好主意。重构此代码的最佳方法是什么
[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.
[事实]
public void Add_AddTrackCollection_asksitunestoexportforeachtrackincollectiontofile()
{
var trackCollection=MockRepository.generateSub(null);
var track=MockRepository.generateSub(null);
var artworkCollection=MockRepository.generateSub(null);
var artwork=MockRepository.GenerateMock(null);
var artworkCache=new artworkCache();
trackCollection.Stub(collection=>{return collection.Count;}).return(5);
trackCollection.Stub(collection=>{return trackCollection[0];}).IgnoreArguments().return(track);
track.Stub(Stub=>{return Stub.Artwork;}).return(artworkCollection);
存根(collection=>{return collection.Count;}).return(1);
artworkCollection.Stub(collection=>{return artworkCollection[0];}).IgnoreArguments().return(artwork);
Expect(stub=>{stub.SaveArtworkToFile(null);}).IgnoreArguments().Repeat.Times(trackCollection.Count-1);
artwork.Replay();
添加(trackCollection);
artwork.VerifyAllExpections();
//将所有不特定于此测试的iTunes fake out重构到其自己的方法中,并从actor调用该方法。
对大量设置代码的需求表明,您的测试代码与复杂的接口进行了深入的交互;这不一定是一个问题(它本身不是一种“代码气味”),尽管它可能暗示最终您可能会在复杂的接口前面放置一个(那时测试facade可能需要一些设置,但您的“实质性”应用程序代码将越来越容易测试),至少如果您能够识别您可能正在使用的任何重复交互模式
广泛使用的单元测试框架,如,&c,认识到需要重复的设置代码,因此使用
setup
方法定义“测试用例”类(以及相应的tearDown
方法,以防需要撤销其中的某些内容)。如果您有其他不同的测试组,这些测试组还需要一些通用设置代码,但也需要每个组的一些设置,但与其他组不同,则您可以共享“通用设置代码”通过从适当的抽象基类继承、调用全局函数或使用您喜爱的语言进行其他适当的操作。要解决这两个问题:
一般来说,对复杂的设置代码的需求应该总是让你考虑是否可以对你的API进行不同的建模。这种情况并不总是如此,但是当它出现的时候,你通常会得到比你开始使用的更好和更简洁的API。这是TDD的优点之一。
对于由于输入复杂而设置复杂的情况,我建议使用通用测试数据生成器我在本回答中使用的许多模式都在中进行了描述,这是一本优秀的书。我知道显式测试设置方法,但由于您所述的原因,我并不喜欢这些方法。我认为在这种情况下,最好重新设计API,将对iTunes的依赖性抽象一点