Clojure是否需要依赖注入以使代码更易于测试?

14

我记得看过一篇文章,说Ruby实际上不需要依赖注入(DI)或依赖注入框架,因为类是开放的。因此,你可以简单地重写依赖项的构造函数,使其返回一个虚拟对象。

我非常新手Clojure和函数式编程。我想知道Clojure是否需要依赖注入,或者它可以因为类似/其他原因而不需要它。这里有一个具体的例子可以使用(随意指出我的设计不符合Clojure的惯用法):

想象一下,你正在开发一个网络爬虫/蜘蛛程序。 它需要遍历你下载的网页。这是一个带有副作用的操作。每次查询时,网页都可能会更改,你的互联网连接可能会断开等等。它找到网页上的所有链接,访问每个链接,然后以相同的方式遍历它。

现在,您想编写一个测试,模拟http客户端,以便它返回一个硬编码的字符串响应。如何在测试中调用程序的-main并防止其使用真正的 http客户端?


请参见:https://dev59.com/XGcs5IYBdhLWcg3wJgtV - noahlz
3个回答

17

with-redefs宏在clojure.core中非常有用,用于存根函数。

这里是一个简短的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"

由于Clojure程序主要由函数而不是对象组成,因此动态重新绑定函数取代了DI框架在测试目的中所做的很多事情。


11

在Clojure中,您通常使用替代方法实现依赖注入的等效功能:

  • 动态绑定 - 例如,在测试函数中通过执行(binding [*out* some-writer-object] ...)来重定向标准输出。
  • 高阶函数 - 可以通过将其他函数作为参数传递来提供一种依赖注入形式。
  • 数据配置 - 在Clojure中,传递包含配置参数(甚至是用于配置自定义行为的函数)的映射相当习惯。

所有这些都是语言本身的组成部分。因此,您绝对不需要像"DI框架"这样的东西。依我看,需要DI框架实际上只是弥补了语言本身功能不足的缺点。


3
假设你正在开发一个网络爬虫/蜘蛛程序。它需要遍历您下载的网页,这是一个带有副作用的操作。网页在每次查询时都可能会改变,您的互联网连接可能会中断等。它会找到网页上的所有链接,访问每个链接,然后以相同的方式遍历它们。
我不认为这里有必要进行依赖注入。以下是我如何解决此问题并保持可测试性的方式:将实现分为至少两个函数。其中一个函数获取并返回网页,另一个函数解析响应。在您的主函数中,您将拥有类似于 (-> "http://google.com" fetch parse) 的东西。
然后,要测试parse,可以简单地跳过fetch方法,并直接将虚假网页数据提供给parse方法。
(deftest test
     (is (= {:something "blah"} (parse "<html><head><title>Fake webpage etc..</title></head></html>"))))

只要你能把问题清晰地分解成函数,我认为你不需要像 DI 这样复杂的东西来进行测试。
我对 Clojure 还很新,所以我不熟悉其他人在这里提到的宏和技术,但是上述方法目前为止已经为我服务得很好了。

1
这是很好的建议,我认为这是我会做的事情,但我认为那更像是单元测试。有时候你需要一个验收测试来检查不仅仅是一部分。 - Daniel Kaplan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接