(如何)Specflow 测试用例可以 运行 使用 Autofac 作为 IoC 容器?

(How) Can Specflow test cases be run with Autofac as the IoC container?

我正在尝试在我的组织中采用 BDD,并且由于 C#.Net 是我们的主要开发模式,Specflow 是我们 "anything Cucumber" 的最佳选择。

不过,我过去是 Spring 的狂热爱好者,但在我的公司,我们在应用程序的各个部分使用 Autofac。但是,我无法找到任何资源来解释 "how" Autofac 可用于 "trigger" Specflow 的 BDD 测试并提供必要的依赖关系连接。

我计划让 Autofac 负责实例化、连接和执行所有内容,而不是执行 Specflow,并且让调用 Autofac 的方法随处可见,即使用 Autofac 作为服务定位器而不是 DI/IoC 容器。甚至可以做到这一点,还是我以错误的方式看待它并且有更好的方法来实现相同的目标?或者我应该纯粹依赖 Specflow 的 "internal container" 来进行 DI 而完全忘记 Autofac?

可能的方法:

  1. 让 Autofac 实例化并连接所有内容并 运行 Specflow 测试(不确定这是否 possible/recommended)。
  2. 让 Specflow 全局实例化 Autofac,它与代码其余部分的必要依赖项连接在一起。可能步骤定义可能会使用 Autofac 作为工厂来获得他们需要的东西。

Pros/Cons:

  1. 第一种方法是理想的,因为它可以防止从 Specflow 到 Autofac 的任何依赖性。前者忘记了后者。完全透明。首选,但不确定如何去做。
  2. 如果 Autofac 可以由 Specflow 全局实例化一次以供以后使用,则后一种方法 "could" 有效。但这会导致在将两个库耦合在一起的步骤定义中对 Autofac 进行大量调用。不可取。

我不确定如何实现它们中的任何一个,是让 Specflow 处理 DI 而忘记 Autofac 还是让 Autofac 启动所有东西更好,或者是否有一些中间立场?

当前的 BDD 设置:Specflow,Selenium/PhantomJS,Xunit。希望与 Autofac 结合。

I plan to have Autofac be responsible for instantiating, wiring and executing everything instead of executing Specflow and have methods calling Autofac littered everywhere.

  • 我真的不明白你如何实现这一目标并且仍然能够从 Visual StudioReSharper 运行 你的测试(我假设你不想要失去那个)。

Have Specflow globally instantiate Autofac which is wired with the necessary dependencies for the remainder of the code. Possible that step definitions may land up using Autofac as a factory to get what they need.

  • 这正是我正在做的,也是我推荐的。

But this would lead to lots of calls to Autofac from within the step definitions coupling the two libraries together.

  • 我不建议通过直接调用 AutoFac 的方法来解析步骤定义 class 中所需的类型。我会在基础 class 中创建一个方法,或者将一个对象绑定到具有所述方法的 specflow 的 mini DI Container

我要做的是创建一个名为 IServiceLocator 的服务定位器接口(是的,我知道 service locator is an antipattern

public interface IServiceLocator
{
    T Get<T>();
}

然后我们将使用 Autofac 创建此接口的实现(请记住,您可以将其替换为另一个实现)

public class AutoFacServiceLocator: IServiceLocator
{
    private readonly IContainer _container;

    public AutoFacServiceLocator(IContainer container)
    {
        _container = container;
    }

    public T Get<T>()
    {
        //Here you add your resolution logic
    }
}

事实上,对于每个场景我们都需要一个 IServiceLocator 的实例,我们希望能够通过 SpecflowContext Injection.

获得它
[Binding]
public class Hooks
{
    private readonly IObjectContainer _objectContainer;

    public Hooks(IObjectContainer objectContainer)
    {
        _objectContainer = objectContainer;
    }

    [BeforeScenario]
    public void RegisterServiceLocator()
    {
        var container = CreateContainer();

        var serviceLocator = new AutoFacServiceLocator(container);

        _objectContainer.RegisterInstanceAs<IServiceLocator>(serviceLocator);
    }

    private IContainer CreateContainer() { /*Create your container*/}
}

最后是用法

[Binding]
public class Steps
{
    private readonly IServiceLocator _serviceLocator;

    public Steps(IServiceLocator serviceLocator)
    {
        _serviceLocator = serviceLocator;
    }

    [Given(@"I have entered (.*) into the calculator")]
    public void GivenIHaveEnteredIntoTheCalculator(int p0)
    {
        Foo foo = _serviceLocator.Get<Foo>();
    }
}

更新:

travis-illig在下面的评论中说

If you go with service location, try CommonServiceLocator rather than creating your own interface

我真的不认为有必要使用 CommonServiceLocator。对服务定位器的调用应与控制器中的构造函数注入相匹配,这意味着这些调用应由方法 Get<T>().

覆盖

Quoting ctavaresCommonServiceLocator 的开发者之一

Should I use this library for my applications?

Typically, the answer to this question is no. Once you've decided on a container that suits your project, there's not a whole lot of benefit from writing your whole application in a way that can switch containers. For libraries that must fit into other ecosystems and play nicely with other libraries, it's an important feature, but for applications the extra layer of abstraction really doesn't buy you much.

CommonServiceLocator 用于库和框架,因为 PhD 的测试项目是他和他的团队的我不建议引入更多依赖项。

从 SpecFlow v2.1 开始,现在可以集成不同的 IoC 容器以进行依赖注入。

已经有一个 SpecFlow.Autofac 包,由 Gaspar Nagy 创建:https://github.com/gasparnagy/SpecFlow.Autofac

Gaspar 提供了有关如何使用此包(或为不同的 IoC 容器编写一个包)的信息:http://gasparnagy.com/2016/08/specflow-tips-customizing-dependency-injection-with-autofac/

如果是 Autofac,NuGet 包会为您完成繁重的工作,您只需提供有关如何创建容器构建器的详细信息:

public static class TestDependencies
{
    [ScenarioDependencies]
    public static ContainerBuilder CreateContainerBuilder()
    {
        // create container with the runtime dependencies
        var builder = Dependencies.CreateContainerBuilder();

        //TODO: add customizations, stubs required for testing

        //auto-reg all types from our assembly
        //builder.RegisterAssemblyTypes(typeof(TestDependencies).Assembly).SingleInstance();

        //auto-reg all [Binding] types from our assembly
        builder.RegisterTypes(typeof(TestDependencies).Assembly.GetTypes().Where(t => Attribute.IsDefined(t, typeof(BindingAttribute))).ToArray()).SingleInstance();

        return builder;
    }
}

(在上面的这个片段中,Dependencies.CreateContainerBuilder() 正在重用应用程序中的构建器,并通过测试环境的注册对其进行补充。有关详细信息,请参阅 https://github.com/gasparnagy/SpecFlow.Autofac/tree/master/sample/MyCalculator。)