Visual C# ExpressでNUnitのユニットテストをステップ実行する方法

1. テストを書く

[TestFixture]
public class Test1
{
    [TestCase]
    public void てすと()
    {
            
    }
}

2. dllではなくexeファイルを出力するようにする

3. nunit-console-runner.dllを参照する
4. Mainの中でNUnit.ConsoleRunner.Runner.Mainを呼び出す

public class Program
{
    public static void Main(string[] args)
    {
        NUnit.ConsoleRunner.Runner.Main(args);
    }
}

5. デバッグ時のコマンドライン引数にexeファイルのパスを設定する
(パスに空白が含まれる場合は""で囲むのを忘れずに)

6. あとはVisualC#で実行するだけ

LCGTypeBuilderでINotifyPropertyChangedを自動実装するコードを書いてみた

http://d.hatena.ne.jp/okazuki/20110116/1295166605 を見てたら「それLCGTypeBuilderでできるよ!」と言いたくなったので、INotifyPropertyChangedをTypeBuilderLCG(Lightweight Code Gen)で実装するコードを書いてみました。

最終的に次のコードでINotifyPropertyChangedが実装できるようになります。

// publicクラスじゃないとダメ
public class MyViewModel : ViewModelBase
{
	// newで作ったインスタンスではPropertyChanged呼び出されないのでnew禁止
	protected MyViewModel() { }

	// Notify属性を付けるとPropertyChangedイベントが呼び出される
	[Notify]
	public virtual string Memory { get; set; }
}

で、このクラスはnewではなく、下で説明するViewModelFactoryでインスタンス化します。

public class Program
{
	static void Main(string[] args)
	{
		// インスタンスはViewModelFactory.Create()で作る
		var m = ViewModelFactory.Create<MyViewModel>();

		m.PropertyChanged += (sender, e) =>
			Console.WriteLine("あの日の {0} は失われてしまいました!!", e.PropertyName);

		m.Memory = "ハヤシライス";
	}
}

newが使えなくなるのが難点ですが、コードはとてもシンプル。

以下はViewModelFactoryの実装です。外部ライブラリには依存していないので、そのままコピー&ペーストして利用可能。.NET Framework 4 と Silverlight 4 で動作確認をしています。

using System;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.ComponentModel;

namespace FrozenLib
{
	public static class ViewModelFactory
	{
		public static T Create<T>()
			where T : IRaiseNotifyPropertyChangedEvent
		{
			return ViewModelFactoryHelper<T>.Create();
		}
	}

	public interface IRaiseNotifyPropertyChangedEvent
	{
		void RaisePropertyChangedEvent(string name);
	}
	public class ViewModelBase : INotifyPropertyChanged, IRaiseNotifyPropertyChangedEvent
	{
		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChangedEvent(string name)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(name));
		}
		void IRaiseNotifyPropertyChangedEvent.RaisePropertyChangedEvent(string name)
		{
			RaisePropertyChangedEvent(name);
		}
	}

	static class ViewModelFactoryHelper<T>
		where T : IRaiseNotifyPropertyChangedEvent
	{
		public static T Create()
		{
			return (T)Activator.CreateInstance(type);
		}

		static readonly Type type;
		static ViewModelFactoryHelper()
		{
			type = new ViewModelFactoryHelper(typeof(T)).CreateType();
		}
	}
	class ViewModelFactoryHelper
	{
		public ViewModelFactoryHelper(Type type)
		{
			this.type = type;

			var typeName = typeof(ViewModelFactoryHelper) + ".DynamicType_" + type.FullName;
			this.builder = module.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Sealed, type);
		}
		readonly Type type;
		readonly TypeBuilder builder;

		public Type CreateType()
		{
			var flags = BindingFlags.Public | BindingFlags.Instance;
			foreach (var p in type.GetProperties(flags))
			{
				if (!p.IsDefined(typeof(NotifyAttribute), true))
					continue;
				var setMethod = p.GetSetMethod(true);
				var setMethodOverride = builder.DefineOverrideMethod(setMethod);
				var g = setMethodOverride.GetILGenerator();
				g.Emit(OpCodes.Ldarg_0);
				g.Emit(OpCodes.Ldarg_1);
				g.Emit(OpCodes.Call, setMethod);
				g.Emit(OpCodes.Ldarg_0);
				g.Emit(OpCodes.Ldstr, p.Name);
				g.Emit(OpCodes.Callvirt, raisePropertyChangedEventMethod);
				g.Emit(OpCodes.Ret);
				builder.DefineMethodOverride(setMethodOverride, setMethod);
			}
			return builder.CreateType();
		}

		static ViewModelFactoryHelper()
		{
			var assemblyName = new AssemblyName(typeof(ViewModelFactoryHelper).FullName + ".DynamicAssembly");
			assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
			module = assembly.DefineDynamicModule(typeof(ViewModelFactoryHelper).FullName + ".DynamicModule.dll");
			raisePropertyChangedEventMethod = typeof(IRaiseNotifyPropertyChangedEvent).GetMethod("RaisePropertyChangedEvent");
		}
		static readonly AssemblyBuilder assembly;
		static readonly ModuleBuilder module;
		static readonly MethodInfo raisePropertyChangedEventMethod;
	}

	[AttributeUsage(AttributeTargets.Property)]
	public class NotifyAttribute : Attribute
	{
		public NotifyAttribute() { }
	}
	static class TypeBuilderExt
	{
		public static MethodBuilder DefineOverrideMethod(this TypeBuilder type, MethodInfo methodInfoDeclaration)
		{
			if (!methodInfoDeclaration.IsVirtual)
				throw new ArgumentException(string.Format("'{0}' をオーバーライドできません。", methodInfoDeclaration));
			var a = (methodInfoDeclaration.Attributes & MethodAttributes.MemberAccessMask) | MethodAttributes.Virtual;
			var p = methodInfoDeclaration.GetParameters().Select(i => i.ParameterType).ToArray();
			return type.DefineMethod(methodInfoDeclaration.Name, a, methodInfoDeclaration.ReturnType, p);
		}
	}
}

ViewModelFactory.Create()は何をしているかというと...例えば、Tが上のMyViewModelなら

public class DynamicType_MyViewModel : MyViewModel
{
	public override string Memory
	{
		get { return base.Memory; }
		set
		{
			base.Memory = value;
			((IRaiseNotifyPropertyChangedEvent)this).RaisePropertyChangedEvent("Memory");
		}
	}
}

のような型を実行時に作成し、そのインスタンスを返しています。

ちなみに、ViewModelBaseを継承したくない場合は、上で定義したIRaiseNotifyPropertyChangedEventを実装すればOK。

public class ViewModelWithoutBase : IRaiseNotifyPropertyChangedEvent, INotifyPropertyChanged
{
	public event PropertyChangedEventHandler PropertyChanged;
	public void RaisePropertyChangedEvent(string name)
	{
		if (PropertyChanged != null)
			PropertyChanged(this, new PropertyChangedEventArgs(name));
	}

	[Notify]
	public virtual int MyName { get; set; }
}

IRaiseNotifyPropertyChangedEventに関するアイディアは http://d.hatena.ne.jp/zecl/20091211/p1 を参考にしました。

※2011/1/25 使ってるのがLCGじゃない気がしてきたので、文中のLCGをTypeBuilderに置き換え。

IISでメンテナンス中ページを表示する

"App_Offline.htm" をトップに置いておけば、全てのURLでこのファイルが表示される。
ただし、ファイルが空だとサーバ側でエラーを起こし、空でなくてもファイルが小さすぎるとブラウザ側で正しく表示してくれなくなる。
(512バイトが以下だとダメらしい)