美文网首页
面向协议不是银弹

面向协议不是银弹

作者: __pencilCool__ | 来源:发表于2016-12-11 09:09 被阅读13次

    面向协议不是银弹

    先明白几个测试的概念

    double 可以理解为置换,它是所有模拟测试对象的统称,我们也可以称它为替身。一般来说,当你创建任意一种测试置换对象时,它将被用来替代某个指定类的对象。

    stub 可以理解为测试桩,它能实现当特定的方法被调用时,返回一个指定的模拟值。如果你的测试用例需要一个伴生对象来提供一些数据,可以使用 stub 来取代数据源,在测试设置时可以指定返回每次一致的模拟数据。

    spy 可以理解为侦查,它负责汇报情况,持续追踪什么方法被调用了,以及调用过程中传递了哪些参数。你能用它来实现测试断言,比如一个特定的方法是否被调用或者是否使用正确的参数调用。当你需要测试两个对象间的某些协议或者关系时会非常有用。

    mock 与 spy 类似,但在使用上有些许不同。spy 追踪所有的方法调用,并在事后让你写断言,而 mock 通常需要你事先设定期望。你告诉它你期望发生什么,然后执行测试代码并验证最后的结果与事先定义的期望是否一致。

    fake 是一个具备完整功能实现和行为的对象,行为上来说它和这个类型的真实对象上一样,但不同于它所模拟的类,它使测试变得更加容易。一个典型的例子是使用内存中的数据库来生成一个数据持久化对象,而不是去访问一个真正的生产环境的数据库。

    实践中,这些术语常常用起来不同于它们的定义,甚至可以互换。稍后我们在这篇文章中会看到一些库,它们自认为自己是 "mock 对象框架",但是其实它们也提供 stub 的功能,而且验证行为的方式也类似于我描述的 "spy" 而不是 "mock"。所以不要太过于陷入这些词汇的细节;我下这些定义更多的是因为要在高层次上区分这些概念,并且它对考虑不同类型测试对象的行为会有帮助。

    如果你对不同类型的模拟测试对象更多的细节讨论感兴趣,Martin Fowler 的文章 "Mocks Aren't Stubs" 被认为是关于这个问题的权威讨论。

    开始

    class Webservice {
        func loadUser() -> User? {
            let json = self.load(URL(string: "/users/current")!)
            return User(json: json)
        }
        
        func loadEpisode() -> Episode? {
            let json = self.load(URL(string: "/episodes/latest")!)
            return Episode(json: json)
        }
        
        private func load(_ url: URL) -> [AnyHashable:Any] {
            URLSession.shared.dataTask(with: url)
            // etc.
            return [:] // should come from the server
        }
    }
    
    • load -- afnetworking 或者你封装的通用网络调用接口
    • loaduser -- networkapimanager
    • user -- model
    • episode -- model
    • Webservice -- datasource or datacenter

    目前为止还不错。但是如果你要测试apimanager

    • 你要用一个东西替换网络请求。

    • or pass in a mock URLSession using dependency injection.

    (这句话没理解 ??? 给URLSession 写断言?)

    总结起来就是 为了验证 a 的功能, a调用了b 。

    • 那么通过替换b的实现 ,交付给a一个仿真的数据。我们能够得到进一步测试a的正确性。
    • 或者是直接在b里面写断言,判断b的输入结果和输出结果是否符合预期。

    We could also define a protocol that URLSession conforms to and then pass in a test instance.

    其实就是临时给b添加了一个检验方法而已。还是和mock方法。这个还算优雅吧。

    稍微好测试点的改动

    struct Resource<A> {
        let url: URL
        let parse: ([AnyHashable:Any]) -> A
    }
    
    class Webservice {
        let user = Resource<User>(url: URL(string: "/users/current")!, parse: User.init)
        let episode = Resource<Episode>(url: URL(string: "/episodes/latest")!, parse: Episode.init)
        
        private func load<A>(resource: Resource<A>) -> A {
            URLSession.shared.dataTask(with: resource.url)
            // load asynchronously, parse the JSON, etc. For the sake of the example, we directly return an empty result.
            let json: [AnyHashable:Any] = [:] // should come from the server
            return resource.parse(json)
        }
    }
    

    这是对资源的封装 ,通过泛型,定义了一组输入和输出。

    • 输入资源:url
    • 输出资源:对象A

    现在我们测试对象user 和 episode 就不用想着mock 什么东西啦。

    虽然感觉是这样子的,但是本质还是没理解: 因为输入输出都有了,就是一个结构而已,没什么好测的。 那么问题来了, 什么东西要测,什么东西不测呢?结构本省没有什么过程调用。

    load 函数我们还是要测的

    protocol FromJSON {
        init(json: [AnyHashable:Any])
    }
    
    struct Resource<A: FromJSON> {
        let url: URL
    }
    
    class Webservice {
        let user = Resource<User>(url: URL(string: "/users/current")!)
        let episode = Resource<Episode>(url: URL(string: "/episodes/latest")!)
        
        private func load<A>(resource: Resource<A>) -> A {
            URLSession.shared.dataTask(with: resource.url)
            // load asynchronously, parse the JSON, etc. For the sake of the example, we directly return an empty result.
            let json: [AnyHashable:Any] = [:] // should come from the server
            return A(json: json)
        }
    }
    

    上面看上去简单,但是缺少灵活性(上面是一种高内聚的改动)
    上面的代码,怎么定义一个resource 包含 User数组呢。

    The protocol makes things simpler, but I think it doesn’t pay for itself, because it dramatically decreases the ways in which we can create a Resource

    struct Resource<A> {
        let url: URL
        let parse: ([AnyHashable:Any]) -> A
    }
    
    protocol FromJSON {
        init(json: [AnyHashable:Any])
    }
    
    struct Resource<A: FromJSON> {
        let url: URL
    }
    

    灵活性丧失在哪里?

    相关文章

      网友评论

          本文标题:面向协议不是银弹

          本文链接:https://www.haomeiwen.com/subject/uyzumttx.html