Folien zum Vortrag “Aspektorientierte Programmierung mit PostSharp”

Monday, June 06, 2011 3:04:33 PM (W. Europe Daylight Time, UTC+02:00)

Auf der .NET Cologne 2011 und dem .NET Day Franken 2011 habe ich jeweils nicht nur mit Neovelop als Sponsor teilgenommen, sondern auch einen Vortrag gehalten. In beiden Fällen ging es um das Thema “Aspektorientierte Programmierung mit PostSharp”.

Die Folien dazu waren auf beiden Konferenzen fast identisch, deshalb hier nur die Version vom .NET Day Franken:

Aspektorientierte Programmierung mit PostSharp (Präsentation als  PDF)

Beide Vorträge waren sehr gut besucht und haben mir sehr viel Spaß gemacht. Es gab interessante Fragen und gutes Feedback.

Kick it on dotnet-kicks.de

Using AOP to run xUnit BDD Extensions in a TFS 2010 build

Friday, March 25, 2011 8:33:13 PM (W. Europe Standard Time, UTC+01:00)

For the past 2 years, I’ve been using Björn Rochels xUnit BDD extensions to write the unit tests for my applications. The xUnit BDD extensions are a great framework that makes it easy to write well structured unit tests. They even include an automocking framework, so that mocks are created automatically for all the dependencies that need to be injected into the system under test via constructor injection. For some strange reason, I cannot find any good introductory articles about the bdd extensions in English, but you can take a look at one of the samples to get an idea how they work.

This post was not meant to be an introduction to the xunit bdd extensions, but I’ll quickly summarize how they work:

A unit test usually has three parts: arrange, act and assert. In the arrange part, you do everything you need to to to setup the test scenario. In the act part, you call the method that you want to test. And finally, in the arrange part, you verify that the Sut (system under test) actually did what it was supposed to do.

The xUnit BDD extensions make it easy to follow that structure: in your specification, you override the method EstablishContext for the arrange part of the test. For the act part of the test, you override the Because-method, and for every observation that you want to assert, you create a method that has the Observation attribute (actually, you could simply use xUnits FactAttribute, Observation is just an alias for Fact). You don’t have to write any code to create the Sut. The bdd extensions do that automatically for you, even if your Sut needs parameters for its constructor. If you want to create the Sut yourself for some reason, you can do so by overriding the CreateSut method.

While writing tests is really easy and fun with the xUnit BDD extensions, integrating them into a TFS build is not. As the name implies, xUnit BDD extensions are based on the xUnit framework. It’s possible to run xUnit tests in TFS, but running xUnit tests in TFS usually requires modifications to the projects MSBuild files or the TFS build process template. None of the integration approaches I found supported additional functionality like code coverage or unit test impact anlysis, and getting the test results into TFS also requires additional work.

When you use MSTest, you get all these integration features for free and don’t have to modify any build files. However, MSTest does not make it as easy as the BDD extensions to write the tests.

What I wanted was to find a way to combine the best of both worlds. I wanted to use xUnit BDD Extensions, and I wanted them to automatically be integrated into my TFS builds.

When I started to rewrite NLocalize, I began to use PostSharp for the first time. PostSharp is a framework for aspect oriented programming. Now I don’t want to explain AOP in this post, as it’s already getting quite long and I did not even really begin to write about what I wanted to write about in the first place Winking smile. So if you don’t know AOP, take a look a the samples on the PostSharp web site.

So how can PostSharp help with my unit testing problem? The basic idea is, that I take a xunit bdd specification and wrap it in an MSTest test, so that it can be run by the MSTest test runner.

How would that look like? Let’s look at one of the specifications from NLocalize:

[Concern(typeof(WizardController))]    
    public class When_the_current_step_is_valid_and_the_next_command_is_executed : InstanceContextSpecification<WizardController>
    {
        private IWizardStep[] steps;
        protected override void EstablishContext()
        {
            base.EstablishContext();
            steps = new IWizardStep[]
                {
                    An<IWizardStep>(),
                    An<IWizardStep>(),
                    An<IWizardStep>(),
                };
            steps[0].WhenToldTo(s => s.IsValid).Return(true);
            steps[0].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
            steps[1].WhenToldTo(s => s.IsValid).Return(true);
            steps[1].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
            steps[2].WhenToldTo(s => s.IsValid).Return(true);
            steps[2].WhenToldTo(s => s.ShouldDisplayStep).Return(true);
        }
        protected override void PrepareSut()
        {
            base.PrepareSut();
            Sut.Steps = steps;
        }
        protected override void Because()
        {
            Sut.NextCommand.Execute(null);
        }
        [Observation]
        private void Should_enable_the_previous_command()
        {
            Sut.PreviousCommand.CanExecute(null).ShouldBeTrue();
        }
        [Observation]
        private void Should_move_to_the_next_step()
        {
            Sut.CurrentStep.ShouldBeEqualTo(steps[1]);
        }
    }

That’s one of the specifications for a wizard control. It verifies that the wizard actually moves to the next step when the user clicks next, and that the “previous”-button is enabled.

To make this test runnable by MSTest, we have to rewrite every observation as MSTest test. We somehow have to get MSTest to use xUnit BDD extensions to create the Sut. For each test, we need to make sure that EstablishContext is executed. We need to mark each observation with the TestMethod-attribute. We also need to mark the specification with the TestClass-attribute.

The test should still be executable by the xUnit test runner (I’m using ReSharpers test runner to run my test locally, as it is way more usable than the Visual Studio test runner). So any modifications made to enable the test to be run by MSTest should not have any side effects when I execute the test with a xUnit test runner.

I solved my problem with a PostSharp aspect. The aspect makes several changes to a xUnit specification when it is applied to the specification class:

  • It searches for all methods that have the ObservationAttribute, and adds a TestMethodAttribute to each of those methods.
  • It adds a TestClassAttribute to the specification class.
  • It injects a new method into the specification class that has the TestInitializeAttribute. This method is used to call the InitializeContext method of the xunit BDD specification (this method calls EstablishContext and CreateSut).
  • It injects a new method into the specification class that has the ClassCleanupAttribute. This method is used to call the CleanupSpecification method of the xunit BDD specification.
  • It initializes the xUnit runner configuration.

Now all I have to do to execute my specifications with MSTest is that I have to add the MSTestRunnable attribute to my specifications. I also have to make sure that my observations are public (that is not required by the xUnit BDD extensions, but MSTest test methods must be public).

For MSTest, my tests now look like any other MSTest test. That means that my tests are supported automatically by TFS and just work. I don’t have to modify my build files or my TFS build process templates. Code Coverage and Test Impact Analysis also work just like with other MSTest tests.

Here is the code for my aspect:

namespace Neovelop.TestAspects
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using PostSharp.Aspects;
    using PostSharp.Aspects.Advices;
    using PostSharp.Reflection;
    using Xunit;
    /// <summary>
    /// The MSTestRunnable-Attribute can be used to to modify a xUnit BDD Extension specification so that it can be called by the MSTest test runner.
    /// </summary>
    [Serializable]
    public sealed class MSTestRunnableAttribute : InstanceLevelAspect, IAspectProvider, IContextSpecification
    {
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented",
            Justification = "Reviewed. Suppression is OK here.")]
        public void InitializeContext()
        {
        }
        [SuppressMessage("Microsoft.StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented",
            Justification = "Reviewed. Suppression is OK here.")]
        public void CleanupSpecification()
        {
        }
        /// <summary>
        /// This method adds TestMethodAttributes to all xunit BDD observations, and a TestClassAttribute to the specification class.
        /// </summary>
        /// <param name="targetElement">
        /// The target element.
        /// </param>
        /// <returns>
        /// Returns a list of aspects that should be provided to the targetElement.
        /// </returns>
        public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
        {
            var targetType = (Type)targetElement;
            Debug.WriteLine(targetType.Name);
            var testMethod = new ObjectConstruction(typeof(TestMethodAttribute).GetConstructor(Type.EmptyTypes));
            var introduceTestMethodAspect = new CustomAttributeIntroductionAspect(testMethod);
            foreach (MethodInfo mi in GetObservations(targetType))
            {
                yield return new AspectInstance(mi, introduceTestMethodAspect);
            }
            var testClass = new ObjectConstruction(typeof(TestClassAttribute).GetConstructor(Type.EmptyTypes));
            var introduceTestClassAspect = new CustomAttributeIntroductionAspect(testClass);
            yield return new AspectInstance(targetElement, introduceTestClassAspect);        
        }
        /// <summary>
        /// This method calls the InitializeContext method of the xUnit BDD specification during the initialization of the MSTest-test.
        /// </summary>
        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Ignore, Visibility = Visibility.Public)]
        [CopyCustomAttributes(typeof(TestInitializeAttribute))]
        [TestInitialize]
        public void Initialize()
        {
            ((IContextSpecification)this.Instance).InitializeContext();
        }
        /// <summary>
        /// This method calls the CleanupSpecification method of the xUnit BDD specification during the ClassCleanup of the MSTest-test.
        /// </summary>
        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail, Visibility = Visibility.Public)]
        [CopyCustomAttributes(typeof(TestCleanupAttribute))]
        [ClassCleanup]
        public void MSTestCleanup()
        {
            ((IContextSpecification)this.Instance).CleanupSpecification();
        }
        /// <summary>
        /// This method ensures that the xUnit configuration is initialized.
        /// </summary>
        /// <param name="type">
        /// The type that is initialized.
        /// </param>
        public override void RuntimeInitialize(Type type)
        {
            base.RuntimeInitialize(type);
            var config =
                typeof(RunnerConfiguration).GetField(
                    "Instance", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic).GetValue(null) as
                IRunnerConfiguration;
            Xunit.RunnerConfigurationAttribute.DefaultConfiguration.Configure(config);
        }
        /// <summary>
        /// This method returns all xUnit observation methods in the given target type.
        /// </summary>
        /// <param name="targetType">
        /// The target type.
        /// </param>
        /// <returns>
        /// Returns all xUnit observation methods in the given target type.
        /// </returns>
        private static IEnumerable<MethodInfo> GetObservations(Type targetType)
        {
            return targetType.GetMethods().Where(m => m.GetCustomAttributes(true).OfType<ObservationAttribute>().Any());
        }        
    }
}

Feel free to use this code in your own projects, but if you do, you use it at your own risk, I make no guarantees that this code is bug free.

It won’t behave in exactly the same way as when the test is executed by xUnit, but so far it worked for all of my tests. I even discovered a subtile bug in my code because a test passed when executed by xUnit but failed when it was executed by MSTest.

Kick it on dotnet-kicks.de

Segelfliegen – mein neues, altes Hobby

Friday, September 17, 2010 3:30:36 PM (W. Europe Daylight Time, UTC+02:00)

Seit einigen Wochen habe ich ein neus, altes Hobby: Segelfliegen. Alt deshalb, weil ich vor 8-10 Jahren schon einmal einen Segelflugschein angefangen habe. Allerdings bin ich damals leider nicht rechtzeitig vor Studium und Bund fertig geworden. Sehr ärgerlich, vor allem weil ich mit dem praktischen Teil der Ausbildung zu 90% fertig war. Seit damals wollte ich immer mal wieder Fliegen, neben dem Studium war dafür aber einfach keine Zeit.

Vor kurzem habe ich mich dann entschieden, wieder zu fliegen. Zunächst musste ich dafür einen geeigenten Verein finden. In der Nähe von Bielefeld gibt es da gleich mehrere Möglichkeiten: da wäre der Flugplatz in Bielefeld, der Flugplatz in Melle, und der Segelflugplatz in Oerlinghausen (der Flugplatz mit den weltweit meisten Segelflugstarts pro Jahr!).

Oerlinghausen bietet neben einer zweistelligen Anzahl Vereinen auch noch eine Segelflugschule. Es ist allerdings von den drei Alternativen am weitesten entfernt. Außerdem wüsste ich gar nicht, wie ich bei so einer Auswahl an Vereinen den richtigen finden sollte Winking smile. Wegen der Entfernung habe ich Oerlinghausen schnell als Alternative ausgeschlossen.

Der Segelflugverein in Bielefeld ist rein von der Entfernung her für mich am nähesten gelegen. Deshalb habe ich mir den Verein dort zuerst angeschaut, und war an zwei Wochenenden da. Allerdings müsste ich trotz der geringen Entfernung zum Platz quer durch Bielefeld fahren, um zum Flugplatz zu kommen. Nervige Staus wären da vorprogrammiert, und darauf habe ich keine Lust.

Somit blieb für mich noch eine Alternative übrig: der Flugplatz in Melle. Er ist etwas weiter entfernt als der Flugplatz in Bielefeld, aber trotzdem deutlich besser für mich erreichbar. In Melle gibt es gleich zwei Vereine: den SFC Melle Grönegau e.V. und den Aero Club Bünde e.V.. Bei meinem ersten Besuch auf dem Flugplatz in Melle habe ich mich direkt “willkommen” gefühlt und beschlossen, dort zu fliegen. Da ich beide Vereine nicht kannte bin ich der “Standardaufteilung” der Mitglieder gefolgt: Flieger aus Niedersachsen in den Meller Verein, aus NRW in den AC Bünde. Somit wurde ich also Mitglied bei den “Bündern”.

Als zusätzlichen “Pluspunkt” schult der AC Bünde mit einer ASK13. Die bin ich auch bei meiner ersten Segelflugausbildung geflogen, somit muss ich mich nicht an ein neues Flugzeug umgewöhnen.

ASK13

Unsere ASK13 hat schon über 23.000 Starts überstanden und ist älter als ich, aber trotzdem immer noch ein sehr schönes Flugzeug dass ich gerne fliege.

Kick it on dotnet-kicks.de

.NET User Group in Bielefeld am 21.06.2010

Friday, June 18, 2010 11:07:25 AM (W. Europe Daylight Time, UTC+02:00)

Bereits vor einigen Jahren gab es schon einmal die .NET User Group Bielefeld. Nachdem Henner – der die Gruppe damals organisierte – aber seinen Arbeitgeber gewechselt hatte, fand sich kein Nachfolger für die Organisation der User Group.

Inzwischen hat Henner eine eigene Firma und ich bin von Paderborn nach Werther (bei Bielefeld) gezogen. Deshalb werden wir zusammen erneut die Treffen der .NET User Group in Bielefeld organisieren.

Da unsere alte Domain dotnet-bielefeld.de noch immer bei Henners ehemaligem Arbeitgeber liegt und die Übergabe sich nun schon seit Wochen verzögert gibt es die Infos zur User Group vorläufig hier im Blog.

Das 1. Treffen ist am 21.06.2010 um 18 Uhr. Wir treffen uns bei Xavannah in der Karl-Oldewurtel Straße 40 in Bielefeld. Der Treffpunkt ist sowohl über die A2/A33 als auch mit der Bahn gut erreichbar.

Unter anderem werde ich ein wenig über Visual Studio 2010 Extensibility erzählen.

Kick it on dotnet-kicks.de

Warum ich nicht mehr bei Aral/BP tanke

Friday, June 11, 2010 11:34:58 AM (W. Europe Daylight Time, UTC+02:00)

Nachdem ein Kommentar von mir auf Twitter ("Nochmal zur Erinnerung für's nächste Tanken: Aral = BP") zu einigen Antworten von @writeline und @_zif_ geführt hat, und 140 Zeichen einfach zu kurz sind, ausführlich darauf zu antworten, kommentire ich das mal etwas ausführlicher hier im Blog.

In den Kommentaren ging es in etwa darum, ob es Sinn macht, BP/Aral zu boykottieren, und ob wir nicht als Autofahrer sowieso eine Mitschuld tragen.

Ist man als Autofahrer oder allgemein Verbraucher von Öl mitschuld an der Umweltkatastrophe im Golf? Ich würde sagen nein, so direkt kann man das nicht sagen, denn gerade beim Benzin und anderen Ölprodukten fehlt mir als Verbraucher die Möglichkeit zu erkennen, wie diese Produkte entstanden sind.

Ich bin gerne bereit, für "vernünftig" hergestellte Produkte auch mehr zu bezahlen. Wenn ich z.B. Eier kaufe und nicht mit bestimmten Haltungsbedingungen einverstanden bin, kann ich Bio-Eier kaufen. Bei vielen anderen Lebensmitteln habe ich ähnliche Wahlmöglichkeiten. Und die nutze ich auch.

Aber beim Öl? Bestenfalls über die Wahl meines Stromanbieters habe ich da einen einigermaßen großen Einfluss (hier nutze ich 100% Ökostrom von NaturWatt).

Wenn ich tanke sehe ich nicht, ob das Benzin aus Öl hergestellt wurde, das "vernünftig", also unter Einhaltung aller Sicherheitsstandards, vernünftiger Arbeitsbedingungen für die Mitarbeiter usw. gefördert wurde. Ich sehe nicht, wo es herkommt – ob von irgendeiner Bohrinsel, oder einem Bohrturm in der Wüste. Ob dabei viel Natur zerstört wurde oder nicht. Es gibt kein Gegenstück zu “Bio” bei Lebensmitteln, das irgendetwas darüber aussagt, wo das Öl herkommt.

Echte Alternativen gibt es auch nicht. Elektroautos z.B. sind für mich einfach nicht bezahlbar. Der ÖPNV scheidet aus wegen mangelnder Verbindungen und überhöhter Preise. Bestenfalls kann ich also ein kleines, sparsames Auto fahren (und das mache ich auch - ich bin allgemein sowieso kein Fan großer Autos).

Die einzige Einflussmöglichkeit, die ich sonst noch sehe, ist die Wahl der Tankstelle. Deshalb tanke ich nicht mehr bei BP/Aral. Möglicherweise (wahrscheinlich sogar) sind die meisten anderen Anbieter auch nicht besser. Aber ich habe keine Möglichkeit, das irgendwie zu überprüfen. Bei BP weiß ich zumindest, dass sie (mit großer Wahrscheinlichkeit) Schuld sind an dem, was da am Golf passiert. Ich weiß nicht, ob BP alle Sicherheitsstandards eingehalten hat oder nicht, aber zumindest weiß ich, dass sich BP vorher anscheinend überhaupt keine Gedanken gemacht hat, wie man im Katastrophenfall handeln soll. Das trifft dann zwar (leider) auch den unschuldigen Tankstellenpächter, aber einen anderen Weg sehe ich nicht.

Kick it on dotnet-kicks.de
«Older Posts