代码之家  ›  专栏  ›  技术社区  ›  Konstantin Konstantinov

f实际项目的依赖注入模拟

  •  4
  • Konstantin Konstantinov  · 技术社区  · 8 年前

    这个问题是基于一个伟大的F/DI相关职位: https://fsharpforfunandprofit.com/posts/dependency-injection-1/

    我试着把问题贴在那里。然而,由于网站上的一些小故障,这些帖子似乎无法再注册。所以,这里是:

    我想知道在那篇文章中描述的场景如何工作/转化为一个更真实的例子。下面的数字离天空有点远,所以请根据需要进行调整。

    考虑一些相当小的基于C代码的DI/TDD/EF代码优先项目:

    组合根:20个接口,每个接口10个方法(平均)。好吧,每个接口的方法可能太多了,但不幸的是,随着代码的发展,它们往往会膨胀。我看到了更多。其中10个是没有任何IO的内部服务(func world中没有database/“pure”函数),5个是内部IO(本地数据库和类似数据库),最后5个是外部服务(如外部数据库或调用某些远程第三方服务的任何其他服务)。

    每个接口都有一个生产级实现,有4个注入接口(平均),每个接口使用5个成员,每个实现总共使用20个方法(平均)。

    测试有几个级别:单元测试、集成测试(两个级别)、验收测试。

    单元测试:所有调用都使用适当的模拟设置进行模拟(例如使用一些标准工具,如moq)。所以,至少有20*10=200个单元测试。通常会有更多,因为测试了几个不同的场景。

    集成测试(1级):所有没有IO的内部服务都是真实的,所有与IO相关的内部服务都是伪造的(通常在内存数据库中),所有外部服务都被代理到一些伪造/模拟。基本上,这意味着所有内部IO服务,如某些InternalIOService:isomeInternalIOService被fakeSomeInternalIOService:isomeInternalIOService替换,所有外部IO服务,如某些ExternalIOService:isomeExternalIOService被fakeSomeExternalIOService:isomeExternalIOService替换。因此,有5个伪造的内部IO和5个伪造的外部IO服务,以及与上述相同数量的测试。

    集成测试(2级):所有外部服务(包括现在与本地数据库相关的服务)都是真实的,所有外部服务都被代理到其他一些伪造/模拟中,从而允许测试外部服务失败。基本上,这意味着所有的外部IO服务,像一些ExternalIOService:isomeExternalIOService,都被breakableFakeSomeExternalIOService:isomeExternalIOService所取代。有5种不同的(可断开的)外部IO假服务。假设我们有大约100个这样的测试。

    验收测试:一切都是真实的,但是配置文件指向一些外部服务的测试版本。假设有大约50个这样的测试。

    我想知道这将如何转化为F世界。很明显,很多事情 非常 不同和 有些事情甚至可能不存在于F世界 !

    谢谢!

    我不想找确切的答案。有一些想法的“方向”就足够了。

    2 回复  |  直到 8 年前
        1
  •  7
  •   Aaron M. Eshbach    8 年前

    +-----------+      +---------------+      +---------------+      +------------+
    | Read data | ---> | Processing #1 | ---> | Processing #2 | ---> | Write data |
    +-----------+      +---------------+      +---------------+      +------------+
    

    +----------+      +----------------+      +----------+      +------------+      +----------+
    | Some I/O | ---> | A bit of logic | ---> | More I/O | ---> | More logic | ---> | More I/O |
    +----------+      +----------------+      +----------+      +------------+      +----------+
    

    the series by Mark Seemann

        2
  •  3
  •   Grundoon    8 年前

    module BuyWorkflow =
    
        type Dependencies = {
           SaveSomething : Something -> AsyncResult<unit,DbError>
           LoadSomething : Key -> AsyncResult<Something,DbError>
           SendEmail : EmailMessage -> AsyncResult<unit,EmailError>
           ...
           }
    
        // define the workflow 
        let buySomething (deps:Dependencies) = 
            asyncResult {
               ...
               do! deps.SaveSomething ...
               let! something = deps.LoadSomething ...
            }
    

    module Workflows =
    
        // set up pure services
        let internalServiceA = ...
        let internalServiceB = ...
        let internalServiceC = ...
    
        // set up workflows
        let homeWorkflow = homeWorkflow internalServiceA.method1 internalServiceA.method2 
        let buyWorkflow = buyWorkflow internalServiceB.method2 internalServiceC.method1 
        let sellWorkflow = ...
    

    module Api =
    
        // load from configuration
        let dbConnectionA = ...
        let dbConnectionB = ...
    
        // set up impure services
        let externalServiceA = externalServiceA(dbConnectionA)
        let externalServiceB = externalServiceB(dbConnectionB)
        let externalServiceC = ...
    
        // set up workflows
        let homeWorkflow = Workflows.homeWorkflow externalServiceA.method1 externalServiceA.method2 
        let buyWorkflow = Workflows.buyWorkflow externalServiceB.method2 externalServiceC.method1 
        let sellWorkflow = ...
    

    Api

    // setup routes (using Suave/Giraffe style)
    let routes : WebPart =
      choose [
        GET >=> choose [
          path "/" >=> Api.homeWorkflow 
          path "/buy" >=> Api.buyWorkflow 
          path "/sell" >=> Api.sellWorkflow 
          ]
      ]