单元测试 – 如何在测试F#时模拟出丰富的依赖项
如何使我的F#应用程序可测试?该应用程序主要使用F#函数和记录编写.
我知道How to test functions in f# with external dependencies并且我知道各种博客帖子显示当你的界面只有一个方法时这是多么容易. 函数按模块分组,类似于我在C#类中对方法进行分组的方式. 我的问题是如何在运行测试时替换某些“抽象”.我需要这样做,因为这些抽象读/写DB,通过网络与服务交谈等.这种抽象的一个例子是下面的存储和提取人和公司的存储库(及其评级). 如何在测试中替换此代码?函数调用是硬编码的,类似于C#中的静态方法调用. 我有一些可能性,但不确定我的想法是否太过我的C#背景. >我可以将我的模块实现为接口和类.虽然这仍然是F#,但我觉得这是一种错误的方法,因为我失去了很多好处.这也是在http://fsharpforfunandprofit.com/posts/overview-of-types-in-fsharp/争论的 一个带有副作用的示例模块我需要在测试期间模拟出来: namespace PeanutCorp.Repositories module PersonRepo = let findPerson ssn = use db = DbSchema.GetDataContext(ConnectionString) query { for ratingId in db.Rating do where (Identifier.Identifier = ssn) select (Some { Id = Identifier.Id; Status = Local; Timestamp = Identifier.LastChecked; }) headOrDefault } let savePerson id ssn timestamp status rating = use db = DbSchema.GetDataContext(ConnectionString) let entry = new DbSchema.Rating(Id = id,Id = ClientId.Value,Identifier = id,LastChecked = timestamp,Status = status,Rating = rating ) db.Person.InsertOnSubmit(entry) ... let findCompany companyId = ... let saveCompany id companyId timestamp status rating = ... let findCachedPerson lookup identifier = ...
如果这是真的,那就是那些客户已经拥有的依赖数.反转控件(是:IoC)只会使显式而非隐式.
鉴于上述情况,还没有发生过这种情况吗?
你不能“保持”应用程序的功能,因为它不是. PersonRepo模块包含的函数不是referentially transparent.依赖于此类函数的任何其他函数也自动不是引用透明的. 如果大多数应用程序过渡依赖于此类PersonRepo函数,则意味着它几乎没有(如果有的话)是引用透明的.这意味着它不是功能性的.由于这个原因,单元测试也很困难. (反过来也是如此:Functional design is intrinsically testable,) 最终,功能设计还需要处理无法透明透明的功能.惯用的方法是将这些函数推送到系统的边缘,这样函数的核心就是纯粹的.这实际上与Hexagonal Architecture非常相似,但在例如Haskell,它通过IO Monad正式化.最好的Haskell代码是纯粹的,但在边缘,函数在IO的上下文中工作. 为了使代码库可测试,您需要反转控制,就像IoC用于OOP测试一样. F#为您提供了一个很好的工具,因为它的编译器强制您在定义它之前不能使用任何东西.因此,您需要做的“唯一的事情”是将所有不纯的功能放在最后.这确保了所有核心功能都不能使用不纯函数,因为它们在那时没有定义. 棘手的部分是弄清楚如何使用尚未定义的函数,但我在F#中的首选方法是将函数作为参数传递. 而不是从另一个函数使用PersonRepo.savePerson,该函数应该采用具有客户端函数所需签名的函数参数: let myClientFunction savePerson foo bar baz = // Do something interesting first... savePerson (Guid.NewGuid ()) foo DateTimeOffset.Now bar baz // Then perhaps something else here... 然后,当您撰写应用程序时,可以使用.撰写myClientFunction let myClientFunction = myClientFunction PersonRepo.savePerson 如果要对myClientFunction进行单元测试,可以提供savePerson的Test Double实现.您甚至不必使用动态模拟,因为唯一的要求是savePerson具有正确的类型. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |