はじめに
この記事はC# Advent Calendar 2023の14日目の記事です。
13日目の記事は @sh1ch によるC# TimeProvider の利用について (.NET8)でした。
注意
この記事中にはprivateメソッドをUnitTestすることの是非は取り扱いません。
・・・でもUnitTestしたくなりますよね?
UnsafeAccessorとは
UnsafeAccessorとは.NET8.0で登場した属性で、今までリフレクションを使用しないと呼び出せなかったprivateメソッドなどにアクセスできるようになるものです。
今まで
今までpublicにしたくないがUnitTestをしたいというメソッドをどうしていたかというとInternalsVisibleToAttributeを使う方法が主流だったのではないかと思います。
具体的に言えばcsprojに以下のような定義を追加していました。
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>UnitTestProjectName</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
更に昔にはAssemblyInfo.cs
というファイルを用意し、以下の定義を使っていました。
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("UnitTestProjectName")]
そしてテストしたいメソッドは以下のように定義していました。
public class TestTarget
{
// UnitTestから呼ぶためにinternalで定義
internal int SomeMethod() => 1;
}
そしてUnitTestのコードは以下のようになるかと思います。
public class TestTargetUnitTest
{
[Fact]
public void SomeMethod()
{
var target = new TestTarget();
Assert.Equal(1, target.SomeMethod()); // internalへはアクセス可能
}
}
これの何が問題かというと、公開したくないメソッドなのにinternalになっているせいで同じアセンブリ内からアクセスされてしまうという点です。
またInternalsVisibleTo
属性の都合上、UnitTestプロジェクトの名前を参照される方のプロジェクトに追加する必要があるという問題もあります。
これから
.NET8.0でUnsafeAccessor
を使うと以下のように記述できます。
public class TestTarget
{
private int SomeMethod() => 1;
}
そう。普通にprivateで定義するだけです。
そしてUnitTest側では以下のように記載します。
public class TestTargetUnitTest
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "SomeMethod")]
static extern int SomeMethod(TestTarget testTarget);
[Fact]
public void SomeMethodTest()
{
var target = new TestTarget();
Assert.Equal(1, SomeMethod(target));
}
}
このように前述した問題点がすべて解消しています。
UnsafeAccessor
自体は.NET8.0の機能ですが、ご覧いただいた通り呼び出し側(UnitTest側)でしか使用されないためテスト対象のプロジェクトのTargetFramework
を変更する必要はないという点もうれしいですね。
Unity用にnetstandard2.1を使用しているようなクラスライブラリにも使用できるのがありがたいのではないでしょうか。
PrivateProxy
この機能を使ったライブラリとして @neuecc さんのPrivateProxyというものがあります。
こちらについてはご自身の解説記事もこちらにありますので合わせてご参照ください。
まとめ
これによりテストしたいがためにinternal
にしていたメソッドをprivate
にすることができ、より安全なクラス設計ができます。
ただしご想像の通り悪用するといろんなことができてしまいますので乱用はほどほどに。。。