我试图在我的组织中采用BDD,而且由于C#.Net是我们的主要开发模式,Specflow是我们最好的选择"任何Cucumber"。
然而,我过去曾是一名Spring爱好者,但在我的公司,我们将Autofac用于应用程序的各个部分。但是,我找不到任何可以解释""" Autofac可用于触发" Specflow的BDD测试并提供依赖关系的必要连接。
我计划让Autofac负责实例化,连接和执行所有内容,而不是执行Specflow,并且让调用Autofac的方法遍布各处,即使用Autofac作为服务定位器而不是DI / IoC容器。甚至可以这样做,还是我以错误的方式看待它并且有更好的方法来实现同样的目标?或者我应该完全依赖于Specflow"内部容器"对于DI而言,完全忘记了Autofac?
可能的方法:
优点/缺点:
我不确定如何实现其中任何一项,如果让Specflow处理DI并忘记Autofac或让Autofac解雇所有内容或者是否存在某些中间地带?
当前BDD设置:Specflow,Selenium / PhantomJS,Xunit。希望与Autofac结合使用。
答案 0 :(得分:10)
我计划让Autofac负责实例化,布线和 执行所有操作而不是执行Specflow并拥有方法 呼叫Autofac随处可见。
Visual Studio
或ReSharper
开始测试(我假设你不想要失去那个)。让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>()
覆盖。
CommonServiceLocator
的开发人员之一
我应该将此库用于我的应用程序吗?
通常,这个问题的答案是否定的。一旦你决定了 适合您项目的容器,并没有很多好处 从可以切换的方式编写整个应用程序 容器。对于必须适应其他生态系统和游戏的图书馆 很好地与其他图书馆,它是一个重要的功能,但对于 应用程序额外的抽象层实际上并没有给你带来好处 得多。
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。