9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C#Advent Calendar 2023

Day 14

UnsafeAccessorでprivateメソッドをテストしよう

Last updated at Posted at 2023-12-14

はじめに

この記事は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にすることができ、より安全なクラス設計ができます。
ただしご想像の通り悪用するといろんなことができてしまいますので乱用はほどほどに。。。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?