How to extend/migrate TraitAttribute to XUnit v2 and why has become sealed

xUnit

In these days I was testing xUnit for the first time, and as a greedy developer, I started with the pre-release v2. Everything was great, except that I was trying to categories my tests in unit, integration, manual, and so on, to let me separate the CI tests from the ones, and I found misleading documentation out there, actually it was simply because there’s some breaking changes passing from v1 to v2, and one is that TraitAttribute is now sealed.

In xUnit v1 and v2 there’s the Trait attribute than can be used to add any kind of description above a test method and that can be read from visual studio test explorer and of course from gui/consoles as well. This description can be useful to let you run just a “category” of tests. See “Using Traits with different test frameworks in the Unit Test Explorer” for more information about Traits in Visual Studio 2013.

Trait and Visual Studio Test Explorer

The annoying part is that Trait takes two strings as name/value pairs, and needs to be copied all around your tests:

public class MyTests1
{
    [Trait("Category", "CI")]
    [Fact]
    public void Test1()
    {
    }

    [Trait("Category", "CI")]
    [Fact]
    public void Test2()
    {
    }

    [Trait("Category", "CI")]
    [Fact]
    public void Test3()
    {
    }
}

One convenient way is to put the Trait attribute on the class itself, in that way every single method will “inherits” that attribute, and with the latest version of visual studio + updates, you will get the expected behavior in the test explorer:

[Trait("Category", "CI")]
public class MyTests2
{
    [Fact]
    public void Test1()
    {
    }

    [Fact]
    public void Test2()
    {
    }

    [Fact]
    public void Test3()
    {
    }
}

But a more convenient way could be to define our CI attribute, and in v1 was easy as inherits:

public class CIAttribute : TraitAttribute
{
    public CIAttribute() 
        : base("Category", "CI")
    {
    }
}

and you can straight use it:

public class MyTests1
{
    [CI]
    [Fact]
    public void Test1()
    {
    }

    [CI]
    [Fact]
    public void Test2()
    {
    }

    [CI]
    [Fact]
    public void Test3()
    {
    }
}

[CI]
public class MyTests3
{
    [Fact]
    public void Test1()
    {
    }

    [Fact]
    public void Test2()
    {
    }

    [Fact]
    public void Test3()
    {
    }
}

…but how to do the same in xUnit v2, where the TraitAttribute become sealed? And why has become selead?

I ask this on a github issue and Brad Wilson (a team member of xUnit) replied me a really exhaustive explanation:

Test discovery in xUnit.net can happen without binaries (Resharper and CodeRush can discover tests as you type, by looking at the source code, even when that code doesn’t necessarily compile).

In order to support this (not being able to run code), it requires that we seal the attributes, so that discovery can be based on things that are known only from the source, and not from running compiled code.

Let’s take a common example: someone wants to make [Category("...")] which is the equivalent of[Trait("category", "...")]. Without a sealed attribute, you would write this:

public class CategoryAttribute : TraitAttribute
{
    public CategoryAttribute(string value) : base("category", value) { }
}

The problem is that Resharper or CodeRush cannot run this code, they can only look at the source. Simple examples like this may be possible, but it doesn’t take long to come up with a scenario where it becomes non-trivial and the only recourse would be to run code and see what happens.

So the tradeoff we make here is to require a bit of known-to-be-compiled code which can inspect the[Category("...")] code and return the correct trait value(s). By sealing the attribute, it says that someone who wants to write CategoryAttribute also needs to write the discoverer that can make sense of what that means without being able to run the code in question.

We have an example which does exactly this: https://github.com/xunit/samples/tree/master/TraitExtensibility

and here it is the migration of CIAttribute:

[TraitDiscoverer("ClassLibrary1.CIDiscoverer", "ClassLibrary1")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class CIAttribute : Attribute, ITraitAttribute
{
}

public class CIDiscoverer : ITraitDiscoverer
{
    public IEnumerable<KeyValuePair<string, string>> GetTraits(IAttributeInfo traitAttribute)
    {
        yield return new KeyValuePair<string, string>("Category", "CI");
    }
}

Pay attention on [TraitDiscoverer("ClassLibrary1.CIDiscoverer", "ClassLibrary1")], the first argument is “The fully qualified type name of the discoverer (f.e., ‘Xunit.Sdk.TraitDiscoverer’)” and the second argument is “The name of the assembly that the discoverer type is located in, without file extension (f.e., ‘xunit.execution’)“, it took me a while to figure it out because as usual I should RTFM first 🙂

Advertisements

How to open and close Flyouts in Universal apps using MVVM

Around and About .NET World

Some times ago we talked about how to open attached Flyouts and close them using custom behaviors and MVVM. This was necessary because the Flyout control doesn’t expose properties to control its visibility, so we need to use its ShowAt and Hide methods explicitly.

While this approach works correctly, we can obtain the same result in a more elegant way with two simple Attached Properties. The following is a generic version of the implementation shown in the article Using Windows 8.1 Flyout with MVVM and works with every control, not only buttons (remember, in fact, that we can attach a Flyout on any FrameworkElement).

Let’s start creating a FlyoutHelper class:

This code adds an IsOpen Attached Property to flyout objects. When it changes (lines 22-39), we check whether we want to open or close the Flyout: in the first case, we call the ShowAt method (lines 32), that actually…

View original post 234 more words

Git: Aliases

git-6-728

With aliases, you can avoid typing the same commands over and over again. Aliases were added in Git version 1.4.0. I found in githowto.com/aliases a great set of aliases that I really use everyday and I would like to share.

One of the most useful alias is “git hist“, it shows you all the last commit, showing the branch tree and the commit message, all in a short form (remember to hit ‘q’ to exit the hist command).

git-hist

In windows you just need to add the following section to the .gitconfig file under %HOMEDRIVE%%HOMEPATH% folder (usually is something like ‘C:\Users\michael\.gitconfig’).

[alias]
  co = checkout
  ci = commit
  st = status
  br = branch
  hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
  type = cat-file -t
  dump = cat-file -p

or you can run these commands in the command-line prompt:

git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.br branch
git config --global alias.hist 'log --pretty=format:"%h %ad | %s%d [%an]" --graph --date=short'
git config --global alias.type 'cat-file -t'
git config --global alias.dump 'cat-file -p'

If you don’t already have a .gitconfig file, this could be a default settings file you can use:

[merge]
	tool = kdiff3
[mergetool "kdiff3"]
	path = C:/Program Files/KDiff3/kdiff3.exe
[diff]
	guitool = kdiff3
[difftool "kdiff3"]
	path = C:/Program Files/KDiff3/kdiff3.exe
[user]
	name = Your Name
	email = youremail@me.com
[alias]
  co = checkout
  ci = commit
  st = status
  br = branch
  hist = log --pretty=format:\"%h %ad | %s%d [%an]\" --graph --date=short
  type = cat-file -t
  dump = cat-file -p