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

时间:2015-09-04 00:14:33

标签: c# autofac ioc-container specflow

我试图在我的组织中采用BDD,而且由于C#.Net是我们的主要开发模式,Specflow是我们最好的选择"任何Cucumber"。

然而,我过去曾是一名Spring爱好者,但在我的公司,我们将Autofac用于应用程序的各个部分。但是,我找不到任何可以解释""" Autofac可用于触发" Specflow的BDD测试并提供依赖关系的必要连接。

我计划让Autofac负责实例化,连接和执行所有内容,而不是执行Specflow,并且让调用Autofac的方法遍布各处,即使用Autofac作为服务定位器而不是DI / IoC容器。甚至可以这样做,还是我以错误的方式看待它并且有更好的方法来实现同样的目标?或者我应该完全依赖于Specflow"内部容器"对于DI而言,完全忘记了Autofac?

可能的方法:

  1. 让Autofac实例化并连接所有内容并运行Specflow测试(不确定这是否可行/推荐)。
  2. 让Specflow全局实例化Autofac,它与其余代码的必要依赖关系相连。可能的步骤定义可能会使用Autofac作为工厂来获得他们需要的东西。
  3. 优点/缺点:

    1. 第一种方法是理想的,因为它可以防止从Specflow到Autofac的任何依赖。前者忘记了后者。完全透明。首选,但不确定如何去做。
    2. 后一种方法"可以"如果Autofac可以由Specflow全局实例化一次以供稍后使用。但这会导致在将两个库耦合在一起的步骤定义中对Autofac进行大量调用。不太可取。
    3. 我不确定如何实现其中任何一项,如果让Specflow处理DI并忘记Autofac或让Autofac解雇所有内容或者是否存在某些中间地带?

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

2 个答案:

答案 0 :(得分:10)

  

我计划让Autofac负责实例化,布线和   执行所有操作而不是执行Specflow并拥有方法   呼叫Autofac随处可见。

  • 我真的不知道如何实现这一目标,仍然能够从Visual StudioReSharper开始测试(我假设你不想要失去那个)。
  

让Specflow全局实例化与之相连的Autofac   其余代码的必要依赖项。可能的   步骤定义可能会使用Autofac作为工厂来获得什么   他们需要。

  • 这正是我正在做的和我推荐的。
  

但这会导致在步骤中大量调用Autofac   定义将两个库连接在一起。

  • 我建议您不要直接调用AutoFac方法来解决步骤定义类中所需的类型。我会在基类中创建一个方法,或者将一个对象绑定到具有所述方法的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的实例,我们希望能够通过Specflow Context 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在下面的评论中说道

  

如果您使用服务位置,请尝试使用CommonServiceLocator而不是   创建自己的界面

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

{p> Quoting ctavaresCommonServiceLocator的开发人员之一

  

我应该将此库用于我的应用程序吗?

     

通常,这个问题的答案是否定的。一旦你决定了   适合您项目的容器,并没有很多好处   从可以切换的方式编写整个应用程序   容器。对于必须适应其他生态系统和游戏的图书馆   很好地与其他图书馆,它是一个重要的功能,但对于   应用程序额外的抽象层实际上并没有给你带来好处   得多。

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

答案 1 :(得分:2)

从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