How to set up individual tracing / logging with SpecFlow - c#

For my SpecFlow tests, I want to setup an individual logging / tracing of the test progress during the execution of tests. E.g. i want to write
all passed / failed steps
started / ended scenarios
started / ended features
to the windows event log (in order to synchronize it with event log messages generated by other system components during the test).
I tried to use the [BeforeFeature], [BeforeScenario], [BeforeStep] Hooks for doing that, but it turned out that I do not have all the required information within this hooks. E.g. i do not know how to get the current text line of the current step executed (including line information, etc.) or the result (failed / passed) of the current step.
Is there a way to get this information within those hooks or in any other way during the execution of the test?
If not:
Is there a way to customize the trace output created by Specflow in any other way?

In order to provide a custom implementation of ITestTracer you should create a plugin for SpecFlow.
Create a class library project with a name CustomTracer.SpecflowPlugin. CustomTracer is your name of choice for plugin.
Then put the following code into your new assembly
[assembly: RuntimePlugin(typeof(CustomTracer.SpecflowPlugin.CustomTracerPlugin))]
namespace CustomTracer.SpecflowPlugin
{
public class CustomTracerPlugin : IRuntimePlugin
{
public void RegisterDependencies(ObjectContainer container)
{
}
public void RegisterCustomizations(ObjectContainer container, RuntimeConfiguration runtimeConfiguration)
{
container.RegisterTypeAs<CustomTracer, ITestTracer>();
}
public void RegisterConfigurationDefaults(RuntimeConfiguration runtimeConfiguration)
{
}
}
public class CustomTracer : ITestTracer
{
// Your implementation here
}
}
Compile assembly and put in the project folder where your specs are (where .csprog file is).
Edit app.config, specFlow section to include:
<plugins>
<add name="CustomTracer" path="." type="Runtime"/>
</plugins>
That is it. Your plugin should load and your custom ITracer implementation should be called during scenario execution. You can even debug it, if you run scenarios under debug.

Finally, after some investigation, i found out that you can replace the DefaultTraceListener by your own implementation by implementing the Interface ITraceListener:
public class foo: TechTalk.SpecFlow.Tracing.ITraceListener
{
public void WriteTestOutput(string message)
{
EventLog.WriteEntry("mysource", "output: " + message);
}
public void WriteToolOutput(string message)
{
EventLog.WriteEntry("mysource", "specflow: " + message);
}
}
And modifying your App.config configuration by adding this to the "specflow" section:
<trace traceSuccessfulSteps="true"
traceTimings="false"
minTracedDuration="0:0:0.1"
listener="MyNamespace.foo, MyAssemblyName"/>
However, this is more a "workaround" for me since I don't have typed information (e.g. of the StepInstance class) and I have to rely or modify the output formatting of SpecFlow.
I would prefer replacing the TestTracer (ITestTracer) implementation by my own one in some way, but i did not find a way to do this. Does anyone now how to do it?

In Specflow 2.1 its a little different.
Instead of:
public class CustomTracerPlugin : IRuntimePlugin
{
public void RegisterDependencies(ObjectContainer container)
{
}
public void RegisterCustomizations(ObjectContainer container, RuntimeConfiguration runtimeConfiguration)
{
container.RegisterTypeAs<CustomTracer, ITestTracer>();
}
public void RegisterConfigurationDefaults(RuntimeConfiguration runtimeConfiguration)
{
}
}
Do this:
public class CustomTracerPlugin : IRuntimePlugin
{
public void Initialize(RuntimePluginEvents runtimePluginEvents, RuntimePluginParameters runtimePluginParameters)
{
runtimePluginEvents.CustomizeTestThreadDependencies += (sender, args) => { args.ObjectContainer.RegisterTypeAs<CustomTracer, ITestTracer>(); };
}
}
Every thing else is the same as Vladimir Perevalovs answer.

To register a custom ITestTracer in SpecFlow 3.x:
[assembly: RuntimePlugin(typeof(CustomTracerPlugin))]
public class CustomTracerPlugin : IRuntimePlugin
{
public void Initialize(
RuntimePluginEvents runtimePluginEvents,
RuntimePluginParameters runtimePluginParameters,
UnitTestProviderConfiguration unitTestProviderConfiguration)
{
runtimePluginEvents.CustomizeGlobalDependencies +=
(s, ea) => ea.ObjectContainer.RegisterTypeAs<CustomTracer, ITestTracer>();
}
}
public class CustomTracer : ITestTracer
{
...
}

Related

How to setup NLog with Sustainsys.Saml2

I have an ASP.Net Web Forms app where I just integrated Sustainsys.Saml2 library.
I've never used any sort of logging mechanism and I'm trying to figure out how to add or create an ILoggerAdapter for the library stated on their troubleshooting page.
I've decided to use NLog (please feel free to recommend a different one) and either I'm not understanding this well, or am not using the right keyword to search for what I need/want, or their isn't a lot of documentation on it.
Currently, I'm using the HttpModule version of Sustainsys.Saml2. Any other information available upon request.
Any help would be great.
Currently, I'm configuring the Sustainsys.Saml2 library through both web.config and the global.asax files. Here's the class my global.asax calls:
public class Saml2Config {
private static bool _alreadyInitialized;
private static readonly object Lock = new object();
public static void Initialize() {
if (_alreadyInitialized) {
return;
}
lock (Lock) {
if (_alreadyInitialized) {
return;
}
var domain = PageHelper.GetDomainURL(true);
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.EntityId.Id = $"{domain}/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ModulePath = "/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ReturnUrl = new Uri($"{domain}/mybarry");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.PublicOrigin = new Uri($"{domain}");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NullLoggerAdapter();
_alreadyInitialized = true;
}
}
}
The interface is pretty straightforward
public interface ILoggerAdapter
{
void WriteInformation(string message);
void WriteError(string message, Exception ex);
void WriteVerbose(string message);
}
I would implement it as follows:
public class NLogAdapter : ILoggerAdapter
{
private static Logger Logger = LogManager.GetLogger("Saml2");
public void WriteInformation(string message)
{
Logger.Info(message);
}
public void WriteError(string message, Exception ex)
{
Logger.Error(ex, message);
}
public void WriteVerbose(string message)
{
Logger.Debug(message);
}
}
And finally set it:
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NLogAdapter();
The ILoggerAdapter contains methods for different loglevels. Make an adapter class that implements ILoggerAdapter and writes to NLog. Then set SPOptions.Logger to an instance of your adapter class.
If you want an example, you can check out the adapter for Asp.Net Core that logs to the Asp.Net Core logging system and is the default for the Sustainsys.Saml2.AspNetCore2 package: https://github.com/Sustainsys/Saml2/blob/master/Sustainsys.Saml2.AspNetCore2/AspNetCoreLoggerAdapter.cs
For the Sustainsys.Saml2.HttpModule library the default is the NullLoggerAdapter which simply discards any logs. Only reason to use it is to not have to nullcheck the Logger property everywhere it is used (that code was written before the ?. syntax was introduced.)

Passing values to Specflow Scenario methods from feature file

I have a custom tag defined in my Hook.cs file like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade()
{
// Code execution here
}
What I want to do is I want to change its method definition like
[BeforeScenario("AfterUpgradeTag")]
public void BeforeScenarioAfterUpgrade(bool flag)
{
if(flag)
// Code execution here
else
//Do a clean up
}
And I want to use this in feature file as something like
#AfterUpgradeTag(bool val = false)
I have searched alot for this. I want to know is this possible using Specflow or if there are any alternatives
I am not sure if you can pass parameters like that in feature file but you can utilize tags to achieve your goal
In feature file do this
#upgrade #false
Scenario: testing upgrade
In binding class
public static ScenarioContext _scenarioContext;
and binding class constructor
public BindingClass(ScenarioContext scenarioContext)
{
_scenarioContext = scenarioContext;
}
and your BeforeScenario method is defined like this in the class BindingClass
[BeforeScenario("upgrade")]
public void BeforeScenarioUpgradeFalseorTrue()
{
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("false"))
{
log.Info("upgrade is false..");
}
if (BindingClass._scenarioContext.ScenarioInfo.Tags.Contains("true"))
{
log.Info("upgrade is true..");
}
}
when you want to pass true in feature file just do
#upgrade #true
Scenario: testing upgrade
You can follow the documentation from specflow to achieve this.

Why are my plugin-types only being registered partially in this case?

I am trying to implement a plugin architecture for our WPF program following the proposed implementation here. I want my plugins to reside in a separate folder from the main program folder. I have gotten it to only partially work. Here is the code:
The plan is for each plugin to provide its own StructureMap registry to override the default StructureMap regstry.
The plugin I am currently working on has the following registry and as you can see, I am overriding the registry for the plugin-type IPrintProgramExecutor to intercept and use AutomationController instead. And it works as expected:
public class PluginRegistry : Registry
{
public PluginRegistry()
{
this.ForConcreteType<AutomationController>()
.Configure
.Ctor<IPrintProgramExecutor>().Is(c=> c.GetInstance<PrintProgramExecutor>())
.Singleton();
this.For<IAutomationController>().Use(c => c.GetInstance<AutomationController>()).Singleton();
this.For<IPrintProgramExecutor>().Use(c => c.GetInstance<IAutomationController>()).Singleton();
//this.ForConcreteType<AutomationPlugin>()
// .Configure
// .Singleton();
this.For<IPluginBase>().Use<AutomationPlugin>();
}
}
AutomationPlugin currently is this stub:
public class AutomationPlugin : IPluginBase
{
public ViewModelBase ViewModel {
get { return viewModel; }
private set { viewModel = value; }
}
public ResourceDictionary View { get; }
private ViewModelBase viewModel { get; set; }
private ResourceDictionary viewDictionary = new ResourceDictionary();
public AutomationPlugin()
{
// do something meaningfull!
}
}
with IPluginBase:
public interface IPluginBase
{
ViewModelBase ViewModel { get; }
ResourceDictionary View { get; }
}
The class to add the registry is this, where pluginPath is the path to the extension folder:
public class PluginRegistryAdder : Registry
{
public PluginRegistryAdder(string pluginPath)
{
Scan( x =>
{
x.AssembliesFromPath(pluginPath);
x.LookForRegistries();
});
}
}
The class to actually tie in the plugin registry using the code above is this:
public static class ExtensionManager
{
public static void RegisterPluginsInDic(string pluginPath, IContainer container)
{
var pluginRegistries = new PluginRegistryAdder(pluginPath);
container.Configure(_ => _.IncludeRegistry(pluginRegistries));
var whatIHave = container.WhatDoIHave(typeof(IPluginBase));
var plugins = container.Model.GetAllPossible<IPluginBase>(); // the IEnumerable plugins is empty although I am registering `AutomationPlugin` for it. Why?!
}
}
Now, as mentioned above, the interception for the plugin-type IPrintProgramExecutor works as expected. But for some reason container.Model.GetAllPossible<IPluginBase>() and container.WhatDoIHave(typeof(IPluginBase)) do not find any registered types for the plugin-type IPluginBase. I have tried calling these methods for IPrintProgramExecutor and surely enough they return the concrete type. I have been looking quite a while for the reason and cannot find it.
Any ideas why? Could it have to do with the fact, that I am calling container.Configure(...) twice and perhaps the fact that I already register something for IPringProgramExecutor the first time I call container.Configure(...)? Help is greatly appreciated!
Updates:
After switching to my laptop, the interception, that previously worked, does not work anymore. Furthermore, I am now getting an exception, that one of the assemblies I am trying to register in PluginRegistry is not found:
StructureMap.StructureMapException: Unable to create an instance for Registry type 'Extensions.Automation.PluginRegistry'. Please check the inner exception for details
---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileNotFoundException: Could not load file or assembly 'Automation.Servers.Interfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
at Extensions.Automation.PluginRegistry..ctor()
...
All in all, it seems like a path-problem that I cannot figure out. I suspect, that at run-time the program attempts to load the DLLs in the Plugin-registry from the main path, but the corresponding DLLs/asseblies reside in the Extension-folder. How can I see from where StructureMap attempts to load the assembly Automation.Servers.Interfaces for debugging? Hope somebody can help me out. I am slowly loosing it.
You should be able to see where StructureMap is trying to probe for assemblies with the type scanning diagnostics: http://structuremap.github.io/diagnostics/type-scanning/
You do have the ability to specify the folder instead of relying on the AppDomain/AppContext.

MAF (System.Addin) property of serializable type in contract?

We are testing the MAF addin to use as our addin framework. But we get stuck at a basic issue. Can we use serializable types as IContract parameters?
Both the contract and the parameter type is defined in the same assembly:
public interface IHostContract : IContract
{
void SetCurrent(TheValue tagValue); // does not work
void SetCurrentSimple(double value); // works fine
}
[Serializable]
public sealed class TheValue
{
public int Id { get; set; }
public double Value { get; set; }
}
We are able to get everything up and running. Calling the SetCurrent results in an exception:
AppDomainUnloadedException :
The application domain in which the thread was running has been unloaded.
Server stack trace:
at System.Threading.Thread.InternalCrossContextCallback(Context ctx, IntPtr ctxID, Int32 appDomainID, InternalCrossContextDelegate ftnToCall, Object[] args)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.DoTransitionDispatch(Byte[] reqStmBuff, SmuggledMethodCallMessage smuggledMcm, SmuggledMethodReturnMessage& smuggledMrm)
at System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
Loading and running of plugins:
public void Run(string PluginFolder)
{
AddInStore.Rebuild(PluginFolder);
Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(Plugins.IPlugin), PluginFolder);
foreach (var token in tokens)
{
Console.WriteLine("Found addin: " + token.Name + " v" + token.Version);
try
{
var plugin = token.Activate<Plugins.IPlugin>(AddInSecurityLevel.FullTrust);
plugin.PluginHost = this;
plugin.Start();
plugin.Stop();
}
catch (Exception exception)
{
Console.WriteLine("Error starting plugin: " + exception.Message);
}
}
}
Plugin:
[System.AddIn.AddIn("Plugin1", Version = "1.0.0")]
public class Plugin1 : IPlugin
{
private int started;
public Plugin1()
{
Console.WriteLine("Plugin 1 created");
}
public void Start()
{
Console.WriteLine("Plugin 1 started: {0}", started);
started++;
var tagValue = new TheValue { Id = 1, Value = 4.32 };
PluginHost.SetCurrent(tagValue);
}
public void Stop()
{
Console.WriteLine("Plugin 1 stopped");
}
public IPluginHost PluginHost { get; set; }
}
You need to follow the guidelines for lifetime management. In each contract-to-view adapter you need to store a ContractHandle. This is necessary for the lifetime management of the proxies that System.AddIn implicitly creates (remember that System.AddIn is based on .NET Remoting).
Taken from MSDN:
The ContractHandle is critical to lifetime management. If you fail to
keep a reference to the ContractHandle object, garbage collection will
reclaim it, and the pipeline will shut down when your program does not
expect it. This can lead to errors that are difficult to diagnose,
such as AppDomainUnloadedException. Shutdown is a normal stage in the
life of a pipeline, so there is no way for the lifetime management
code to detect that this condition is an error.
If you decide to use System.AddIn in your application then you need the PipelineBuilder. In the discussion board you will find help on how to make it work with VS2010 (it is quite simple). I guess it will not be hard to make it work with VS2012 as well. This tool will take care all the System.AddIn intricacies for you. All you will need to do is create the contracts and PipelineBuilder will create the rest of the pipeline for you. It will also make sure that you follow the guidelines on how to build your contracts which is the most important thing with System.AddIn.
Before you decide on an add-in framework, don't forget to check out MEF. MEF can be used with Autofac and provide versioning through adapters. IMHO, the only reason that anyone should choose System.AddIn is for the isolation feature. But note that 100% isolation is only when the add-ins are loaded in a different process from the host.

StructureMap doesn't appear to be ready during HttpModule constructor -- is this correct?

This question is more to confirm my diagnosis of an issue we encountered--or to find alternative explanations.
We have an HTTPModule which intercepts every request made to our webforms application. It's job is to translate specific querystring parameters which our integration partners send.
More importantly, it was wired to StructureMap like this:
public class SomeModule : IHttpModule
{
public SomeModule()
{
ObjectFactory.BuildUp(this);
}
public IDependency Dependency { get; set; }
}
During a previous release it appeared that the module wasn't being injected by the time it executed it's request-processing. That led to some (ugly) defensive check being added like:
public class SomeModule : IHttpModule
{
public SomeModule()
{
ObjectFactory.BuildUp(this);
if (SomeDependency == null)
{
// HACK: Not sure why this corrects the issue!
Dependency = ObjectFactory.GetInstance<ISomeDependency>();
}
}
public IDependency Dependency { get; set; }
}
You'll notice the HACK comment -- it resolved the issue but without good reason.
Well, this same module has been re-purposed on another site--and the previous hack no longer worked. After looking at it for some time I made the change to move the StructureMap call outside the constructor, and lo-and-behold, it works.
public class SomeModule : IHttpModule
{
public IDependency Dependency { get; set; }
public void IHttpModule.Init(HttpApplication context)
{
Initialize();
// the rest of the code
}
private bool _initialized;
private void Initialize()
{
if (_initialized)
{
return;
}
ObjectFactory.BuildUp(this);
_initialized = true;
}
}
So, my I have a few questions around this behavior:
My suspicion is that StructureMap was not fully initialized/configured when the HttpModule constructor was being called -- agree/disagree, any insight?
I haven't found any reference materials that state when to expect StructureMap to be initialized and ready to service requests. Is there any such documentation?
I wouldn't expect the behavior to be very predictable when you're trying to build up a type in its constructor. Many actions that are normally considered safe are not considered safe in a constructor (like using a virtual property in a class).
Where you moved the code to looks much better and I would leave it there. If you can't have the container create the instance itself (and therefore are forced to build it up) some sort of Initialize method is the preferred place to do the build up action.
To answer the question you have at the end of your post:
It is up to the developer to determine when StructureMap is initialized. In a web application, this is almost always done in the Global.asax in Application_Start(). In that case, I would expect the container to be ready when your module is called.

Categories

Resources