You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用单元测试检查Clojure代码的架构合规性?

Enforcing Architectural Boundaries in Clojure (Like ArchUnit for Java)

Great question! Drawing inspiration from Building Evolutionary Architectures and Clean Architecture to enforce layer dependencies (e.g., ensuring controllers only reference use-cases and never entities) is a smart way to keep your codebase maintainable. While Clojure doesn't have a direct 1:1 equivalent to ArchUnit, there are several effective tools and approaches to achieve this:

1. Clj-Kondo with Custom Linting Rules

Clj-Kondo is a powerful static analyzer for Clojure that lets you define custom linting rules to catch architectural violations early. It's lightweight, fast, and integrates with most editors/CI pipelines.

Example Custom Rule for Your Boundary

Create or update your .clj-kondo/config.edn file to add a rule that blocks controllers namespaces from referencing entities:

{:lint-as {clojure.core/require clj-kondo.lint-as/defun}
 :hooks {:analyze-call {clojure.core/require my.namespace.dependency-checker/check-require}}
 :config {:allowed-dependencies {"com.controller.*" #{"com.use-cases.*"}}}}

Then implement the checker function in my.namespace.dependency-checker:

(ns my.namespace.dependency-checker
  (:require [clj-kondo.hooks-api :as api]))

(defn check-require [{:keys [node]}]
  (let [current-ns (api/current-ns)
        required-nses (map (comp api/sexpr second) (:children node))
        allowed (get-in (api/config) [:config :allowed-dependencies current-ns])]
    (when allowed
      (doseq [ns required-nses]
        (when-not (some #(re-matches (re-pattern %) ns) allowed)
          (api/reg-finding!
           {:message (str "Namespace " current-ns " is not allowed to reference " ns)
            :type :architectural-violation
            :node node}))))))

Run Clj-Kondo on your codebase, and it will flag any controllers namespace that imports entities directly.

2. Eastwood Static Analyzer

Eastwood is another popular static analysis tool for Clojure that supports custom checkers. You can build a custom checker to validate namespace dependencies.

Example Custom Checker

Define a checker function that scans namespaces and validates their dependencies:

(ns my.eastwood.checkers.dependency-enforcer)

(defn check-dependencies [report-map]
  (let [controllers-nses (filter #(re-find #"^com\.controller\." %) (:namespaces report-map))]
    (doseq [ns controllers-nses]
      (let [required-nses (->> ns find-ns ns-requires keys (map str))
            forbidden (filter #(re-find #"^com\.entities\." %) required-nses)]
        (doseq [f forbidden]
          (swap! report-map :add-finding
                 {:type :architectural-dependency-violation
                  :message (str "Controller namespace " ns " references forbidden entity namespace " f)
                  :filename (-> ns find-ns meta :file)
                  :line-number 1}))))))

Configure Eastwood to use this checker in your eastwood.clj config, and it will report violations during analysis.

3. Custom Unit Tests with Namespace Utilities

If you prefer a test-driven approach without third-party tools, you can write unit tests using Clojure's built-in namespace functions or the clojure.tools.namespace library to inspect dependencies.

Example Unit Test

(ns com.architecture-tests.dependency-checks
  (:require [clojure.test :refer :all]
            [clojure.tools.namespace.find :as ns-find]
            [clojure.tools.namespace.parse :as ns-parse]))

(deftest controllers-should-not-reference-entities
  (let [controller-nses (ns-find/find-namespaces-in-dir (clojure.java.io/file "src/com/controller"))
        entity-ns-pattern #"^com\.entities\."]
    (doseq [ns controller-nses]
      (require ns)
      (let [required-nses (-> ns find-ns ns-requires keys (map str))
            forbidden-refs (filter #(re-find entity-ns-pattern %) required-nses)]
        (is (empty? forbidden-refs)
            (str "Controller namespace " ns " has forbidden references to: " (clojure.string/join ", " forbidden-refs)))))))

Running this test will fail if any controllers namespace directly references an entities namespace, just like your example code.

All these approaches let you enforce the clean architecture boundaries you're aiming for, keeping your codebase aligned with the principles from those great books.

内容的提问来源于stack exchange,提问作者Michel Uncini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:49:33