kajstrom / dependency-constraints

在测试套件中创建约束,以防止不想要的依赖。

1.0.1 2018-03-13 19:24 UTC

README

Tracis CI Version PHP version

DependencyConstraints

DependencyConstraints 是一个用于创建项目中模块之间依赖约束的静态代码分析工具。它旨在与 PHPUnit 或 Codeception 等测试库一起使用。

DependencyConstraints 的核心思想是缩短发现降低应用程序架构的变化的反馈循环。而不是在六个月后才发现你或团队中的其他人将不希望的耦合添加到应用程序中,你可以创建测试来作为此类变化的防护。

DependencyConstraints 受到 JDepend 和《构建演化式架构》一书中引入的 Fitness Functions 的启发。

入门

使用 Composer 安装 DependencyConstraints。

composer require kajstrom/dependency-constraints --dev

安装 DependencyConstraints 后,您可以使用它开始创建测试。建议只创建一个 DependencyConstraints 实例,因为它将遍历目标目录中的所有 PHP 文件。

此外,您应该在您的 src/classes 目录中使用 DependencyConstraints。不需要 DependencyConstraints 解析 vendor 目录等,才能查找外部类的使用。

使用 PHPUnit 的测试示例。

    class MyDependencyTest extends TestCase
    {
        /** @var  DependencyConstraints */
        private static $dc;
    
        public static function setUpBeforeClass()
        {
            self::$dc = new DependencyConstraints("/path/to/myproject/src");
        }
    
        public function testModuleAIsNotDependentOnModuleX()
        {
            $moduleA = self::$dc->getModule("MyProject\\ModuleA");
    
            $this->assertFalse(
                $moduleA->dependsOnModule("MyProject\\ModuleX"),
                $moduleA->describeDependenciesTo("MyProject\\ModuleX"),
            );
        }
    }

什么被认为是依赖

DependencyConstraints 假定以下为依赖项

  • 使用来自另一个模块(命名空间)的类、接口、特质或类的实例。
  • 使用来自另一个模块的函数。
  • 使用来自另一个模块的常量。

目前,全局作用域的类、函数和常量不被认为是依赖项。

使用子模块中的内容不被认为是依赖项。

例如

在 "MyProject\ModuleB" 中使用 "MyProject\ModuleA\SomeClass" 是一个依赖项。

在 "MyProject\ModuleA" 中使用 "MyProject\ModuleA\SubModule\SomeClass" 不是依赖项。

潜在用例

在分层架构中,您可能希望防止您的软件的其他层与表示层耦合。

public function testBusinessLayerDoesNotDependOnPresentationLayer()
{
    $dc = new DependencyConstraints("path/to/my/src");
    $business = $dc->getModule("MyProject\\Business");
    
    $this->assertFalse(
        $business->dependsOnModule("MyProject\\Presentation"),
        $business->describeDependenciesTo("MyProject\\Presentation")
    );
}

也许您有一个模块化单体,并希望确保某个模块不会与其他模块耦合。

public function testModuleADoesNotDependUponModuleB()
{
    $dc = new DependencyConstraints("path/to/my/src");
    $moduleA = $dc->getModule("MyProject\\ModuleA");
    
    $this->assertFalse(
        $moduleA->dependsOnModule("MyProject\\ModuleB"),
        $moduleA->describeDependenciesTo("MyProject\\ModuleB")
    );
}

或者,您可能想在六边形架构中使某些外部库不与应用程序层交互。

public function testApplicationLayerDoesNotDependOnSymfonyHttpFoundation()
{
    $dc = new DependencyConstraints("path/to/my/src");
    $application = $dc->getModule("MyProject\Application");
    
    $this->assertFalse(
        $application->dependsOnModule("Symfony\\HttpFoundation"),
        $application->describeDependenciesTo("Symfony\\HttpFoundation")
    );
}

您还可以检查对特定类的依赖。也许您正在重构它,但它总是在新地方出现!

public function testModuleDoesNotDependOnSingleton()
{
    $dc = new DependencyConstraints("path/to/my/src");
    $module = $dc->getModule("MyProject\\Module");
    
    $this->assertFalse($module->hasDependencyOn("MyProject\\Utils\\SingletonThatSeemedAGoodIdeaBackThen");
}

局限性

作为一个静态代码分析工具,DependencyConstraints 不能捕捉到所有内容。假设有人想做一些这样的事情

$className = "MyProject\\Module\\Class";
$instance = new $className;

在分析源代码时,这不会被视为依赖项。

为什么不使用 Pdepend?

Pdepend 为代码库的质量提供了非常有用的指标,但它不允许测试模块之间的依赖关系。