简体   繁体   English

clojure是否需要依赖注入才能使代码更易于测试?

[英]Does clojure need dependency injection to make code more testable?

I remember reading an article about how Ruby doesn't really need DI or DI frameworks because the classes are open. 我记得读过一篇关于Ruby如何不真正需要DI或DI框架的文章,因为这些类是开放的。 As a result, you can simply rewrite a dependency's constructor so that it returns a fake object. 因此,您可以简单地重写依赖项的构造函数,以便它返回一个伪对象。

I'm very new to Clojure and functional programming. 我是Clojure和函数式编程的新手。 I'm wondering if Clojure needs dependency injection or it can forgo it for similar/other reasons. 我想知道Clojure是否需要依赖注入,或者它可以出于类似/其他原因而放弃它。 Here's a concrete example to work with (feel free how to point out how my design is non-idiomatic of Clojure): 这是一个可以使用的具体示例(如何指出我的设计是非惯用的Clojure):

Imagine you're developing a web crawler/spider. 想象一下,你正在开发一个网络爬虫/蜘蛛。 It needs to traverse a webpage you've downloaded. 它需要遍历您下载的网页。 This is an action with side-effects. 这是一个带有副作用的动作。 The webpage could change on every query, your internet connection could cut out, etc. It finds all the links on the webpage, visits each one, and then traverses it in the same way. 网页可能会在每个查询中发生变化,您的互联网连接可能会被删除等。它会找到网页上的所有链接,访问每个链接,然后以相同的方式遍历它。

Now, you want to write a test that mocks out the http client so it returns a hard coded string response instead. 现在,您想要编写一个模拟http客户端的测试,以便它返回一个硬编码的字符串响应。 How do you call the program's -main in a test and prevent it from using the real http client? 如何在测试中调用程序的-main并阻止它使用真正的 http客户端?

The with-redefs macro in clojure.core is very useful for stubbing out functions. clojure.core中的with-redefs宏对于with-redefs函数非常有用。

Here's a short REPL session to demonstrate this: 这是一个简短的REPL会话来证明这一点:

user=> (defn crawl [url]
  #_=>   ;; would now make http connection and download content
  #_=>   "data from the cloud")
#'user/crawl
user=> (defn -main [& args]
  #_=>   (crawl "http://www.google.com"))
#'user/-main
user=> (-main)
"data from the cloud"
user=> (with-redefs [crawl (fn [url] "fake data")]
  #_=>   (-main))
"fake data"

Since a Clojure program is composed (mostly) of functions, not objects, dynamic rebinding of functions replaces a good deal of what a DI framework would do for testing purposes. 由于Clojure程序(大部分)由函数而不是对象组成,因此函数的动态重新绑定取代了DI框架为测试目的所做的大量工作。

In Clojure you usually achieve the equivalent of dependency injection with alternative methods: 在Clojure中,您通常使用替代方法实现相当于依赖注入:

  • Dynamic binding - eg useful for redirecting standard output in test functions by doing (binding [*out* some-writer-object] ...) 动态绑定 - 例如,通过执行(binding [*out* some-writer-object] ...)重定向测试函数中的标准输出非常有用
  • Higher order functions - can be used to provide a form of dependency injection by passing other functions as parameters 高阶函数 - 可以通过将其他函数作为参数传递来提供依赖注入的形式
  • Configuration with data - it's fairly idiomatic in Clojure to pass around maps which contain configuration parameters (or even functions to configure custom behaviour). 使用数据进行配置 - 在Clojure中传递包含配置参数(甚至是配置自定义行为的函数)的映射是相当惯用的。

All of these are integral to the language itself. 所有这些都是语言本身的组成部分。 So you definitely don't need anything like a "DI framework". 所以你绝对不需要像“DI框架”那样的东西。 IMHO, needing a framework for DI is really just compensating for a lack of sufficient features in the language itself. 恕我直言,需要DI的框架实际上只是补偿语言本身缺乏足够的功能。

Imagine you're developing a web crawler/spider. 想象一下,你正在开发一个网络爬虫/蜘蛛。 It needs to traverse a webpage you've downloaded. 它需要遍历您下载的网页。 This is an action with side-effects. 这是一个带有副作用的动作。 The webpage could change on every query, your internet connection could cut out, etc. It finds all the links on the webpage, visits each one, and then traverses it in the same way. 网页可能会在每个查询中发生变化,您的互联网连接可能会被删除等。它会找到网页上的所有链接,访问每个链接,然后以相同的方式遍历它。

I don't see a need for dependency injection here. 我不认为这里需要依赖注入。 Here's how I think I'd tackle this problem whilst maintaining testability: separate out the implementation into a minimum of two functions. 以下是我认为在保持可测试性的同时解决这个问题的方法:将实现分成至少两个函数。 One function fetches and returns the webpage, and the other parses the response. 一个函数获取并返回网页,另一个函数解析响应。 In your main function, you'd have something like (-> "http://google.com" fetch parse) . 在您的主要功能中,您可以使用(-> "http://google.com" fetch parse)

Then, to test parse , you can simply bypass the fetch method and feed fake webpage data directly into the parse method. 然后,要测试parse ,您可以简单地绕过fetch方法并将伪网页数据直接输入到parse方法中。

(deftest test
     (is (= {:something "blah"} (parse "<html><head><title>Fake webpage etc..</title></head></html>"))))

So long as you're careful about breaking down problems into clear functions, I don't think you need anything sophisticated like DI to test. 只要你小心将问题分解成清晰的功能,我认为你不需要像DI这样复杂的东西来测试。

I'm new to Clojure, so I'm not familiar with the macros and techniques mentioned by others in here, but the above methodology has served me fine thus far. 我是Clojure的新手,所以我不熟悉其他人在这里提到的宏和技术,但到目前为止,上述方法对我来说还不错。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM