@Sum001100

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

mockについて

解決したいこと

vbでのユニットテストにおいて、mockを使用する際に下記の条件の場合実現可能なのでしょうか

1,テスト対象メソッドの呼んでいるメソッドをmockにしたい
2,mock対象のメソッドはオーバーライド不可である
3,テスト対象のメソッドは書き換え不可である

自分で試したこと

調べて行くと、mock対象のクラスをラッパークラスにする方法を見つけましたが、元ソースの書き換えが必要であったため、断念しました。
そもそもVBでのmock使用参考資料がなかなか見つからず困っています
有識者の方、ご教授いただけると大変助かります

追記
Windowsフォームアプリケーションを作成しております。使用環境、バージョンは下記の通りです。
.NETFramework 4.6.1
VisualStudioProfessional 2022

0 likes

3Answer

そもそもVBでのmock使用参考資料がなかなか見つからず困っています

C# なら質問者さんの目的に沿った資料がネットに公開されている記事にあるのでしょうか? そういう記事があれば、その記事の url を提示して、VB.NET で書き換える際に困っている点を説明することはできますか?

今の質問では具体性がなくて、エスパーでもない限りピンポイントに質問者さんが困っていることに答えられないと思いますので。


【追記】

元ソースを変更せずにモックを作成する方法があるのかと模索しています。
https://hitochan777.vercel.app/posts/1b015052/

その「元ソース」というのは、参考にしている記事の HelloWorld クラスのソースのことなのでしょうか?

そうだとすると、質問者さんのケースで可能かどうかわかりませんけど、DI を使ってのリポジトリパターンのように(下図参照。出典: ASP.NET MVC 実践プログラミング)、

image.jpg

NumberGenerator クラスが interface を継承していて、HelloWorld クラスのコンストラクタはその interface を引数に取るような形にできれば、HelloWorld クラスのソースは変更しないですむと思います。

その例を以下に書いておきます。

interface, NumberGenerator と継承クラス, HelloWorld

namespace WindowsFormsUnitTest
{
    public interface INumberGenerator
    {
        int GetNumber();
    }

    public class NumberGenerator : INumberGenerator
    {
        private int Number { get; set; }

        public NumberGenerator(int number)
        {
            Number = number;
        }

        public int GetNumber()
        {
            return Number;
        }
    }

    public class NumberGenerator_Test : NumberGenerator
    {
        private int Number { get; set; }

        public NumberGenerator_Test(int number) : base(number)
        {
            Number = number;
        }

        public virtual new int GetNumber()
        {
            return Number;
        }
    }

    public class HelloWorld
    {
        private readonly INumberGenerator _klass;

        public HelloWorld(INumberGenerator klass)
        {
            _klass = klass;
        }

        public int Run()
        {
            return _klass.GetNumber();
        }
    }
}

単体テストクラス

using Microsoft.VisualStudio.TestTools.UnitTesting;
using WindowsFormsUnitTest;
using Moq;

namespace WinForms_Test
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            INumberGenerator gen = new NumberGenerator(2);
            var hellowWorld = new HelloWorld(gen);
            Assert.AreEqual(2, hellowWorld.Run());
        }

        [TestMethod]
        public void TestMethod2()
        {
            INumberGenerator gen = new NumberGenerator_Test(3);
            var hellowWorld = new HelloWorld(gen);
            Assert.AreEqual(3, hellowWorld.Run());
        }

        [TestMethod]
        public void TestMethod3()
        {
            var mock = new Mock<NumberGenerator_Test>(MockBehavior.Strict, 4);
            mock.Setup(klass => klass.GetNumber());
            var hellowWorld = new HelloWorld(mock.Object);
            Assert.AreEqual(4, hellowWorld.Run());
        }
    }
}

結果は:

TestResult.jpg

0Like

Comments

  1. @Sum001100

    Questioner

    C#使用でも、記事は見つかりませんでした。
    下記サイトでは、ラッパークラスを作成し元ソースを書き換える方法が載っていましたが、元ソースを変更せずにモックを作成する方法があるのかと模索しています。
    https://hitochan777.vercel.app/posts/1b015052/

  2. 下記サイトでは、ラッパークラスを作成し元ソースを書き換える方法が載っていましたが、元ソースを変更せずにモックを作成する方法があるのかと模索しています。
    https://hitochan777.vercel.app/posts/1b015052/

    「元ソースを書き換える」って何のことを言ってますか? 参考にされている記事では元ソースは書き換えないで(書き換えることができないので)ラッパーを作って対応したと書いてありますが?

  3. リンク先の記事見ると、NumberGenerator -> NumberGeneratorWrapper への書き換えは発生しますね。

    3,テスト対象のメソッドは書き換え不可である

    ということなので、この条件だと型も変えられないんじゃないかと。

  4. それは自分的には「書き換え」とは言いませんけど? 

  5. 元ソースを変更せずにモックを作成する方法があるのかと模索しています。

    その「元ソース」というのは、参考にしている記事の HelloWorld クラスのソースのことなのでしょうか?

    そうだとすると、質問者さんのケースで可能かどうかわかりませんけど、DI でよくある例のように、NumberGenerator クラスが interface を継承していて、HelloWorld クラスのコンストラクタはその interface を引数に取るような形にできれば、HelloWorld クラスのソースは変更しないですむと思います。

    後で上の回答欄に例を追記しておきます。

  6. @Sum001100

    Questioner

    拝見させていただきました。
    ご丁寧に図解まで、とてもわかりやすかったです。書き起こして理解したいと思います。ありがとうございました。

タグは"VB"だとVB.NET以前(VB6とか)的な風潮があるので、"VB.NET"にした方がよいかもしれません。

1,テスト対象メソッドの呼んでいるメソッドをmockにしたい
2,mock対象のメソッドはオーバーライド不可である
3,テスト対象のメソッドは書き換え不可である

この条件だと、直接的な手段はないのでせいぜいmock対象のメソッドが欲しい値を返すよう条件を整えるくらいしか出来ない気がします。
(DBから値を取ってきているなら、テスト用のDB繋がるようにしておくとか)
対象メソッドが特定のクラスメンバの値を返しているだけなら、Reflectionでクラスメンバの値を書き換えて対応可能かもしれませんが、メソッドの実装が変わった時にテストが動かなくなる可能性はあります。


[追記]
コメントに書いた Harmony を実際に試してみました。

Imports HarmonyLib

Public Class Test

    Private Shared _Rand As New Random

    Private Function GetRandomInt() As Integer
        Return _Rand.Next()
    End Function

    Public Function GetInt() As Integer
        Return GetRandomInt()
    End Function

End Class

Public Class MyPatcher

    Public Shared Sub DoPatching()
        Dim harmony = New Harmony("com.example.patch")
        harmony.PatchAll()
    End Sub

End Class

<HarmonyPatch(GetType(Test))>
<HarmonyPatch("GetRandomInt")>
Public Class Patch01

    Shared Sub Postfix(ByRef __result As Integer)
        __result = -1
    End Sub

End Class

Module Program

    Sub Main()

        Dim t1 As New Test()
        Console.WriteLine("[Before DoPatching]")
        Console.WriteLine(t1.GetInt())
        Console.WriteLine(t1.GetInt())
        Console.WriteLine(t1.GetInt())

        MyPatcher.DoPatching()

        Console.WriteLine("[After DoPatching]")
        Console.WriteLine(t1.GetInt())
        Console.WriteLine(t1.GetInt())
        Console.WriteLine(t1.GetInt())

        Console.ReadLine()
    End Sub

End Module

Test.GetRandomIntメソッドに対して、常に-1を返すようにパッチを当てています。

出力結果(Debugビルド)
[Before DoPatching]
1703078859
2070975414
935203501
[After DoPatching]
-1
-1
-1
出力結果(Releaseビルド)
[Before DoPatching]
410479300
157001909
1394199423
[After DoPatching]
1649118590
939353430
592607656

Releaseビルドだと最適化でインライン展開されてしまっているのか、パッチは動作しませんでした。

0Like

Comments

  1. @Sum001100

    Questioner

    やはりこの条件では難しいのですね、。
    添付サイトも拝見させていただきました。
    参考にさせていただきます。ご丁寧にありがとうございました。

  2. こんなのもありましたが、若干制約はあるようです。

    Limits of runtime patching
    note Harmony can't do everything. Make sure you understand the following:
    ・With Harmony, you only manipulate methods. This includes constructors and getters/setters.
    ・You can only work with methods that have an actual IL code body, which means that they appear in a dissassembler like dnSpy.
    ・Methods that are too small might get inlined and your patches will not run.
    ・You cannot add fields to classes and you cannot extend enums (they get compiled into ints).
    ・Patching generic methods or methods in generic classes is tricky and might not work as expected.

  3. @Sum001100

    Questioner

    このような方法があるのですね、。書き換えて理解して行きたいと思います。ご丁寧にありがとうございます。

  4. クローズして新しい質問を立てているようですが、結局こちらの問題は解決されたのでしょうか?解決されたのであれば、解決方法を書いてフィードバックをお願いします。質問サイトは単なる教えるだけの場所ではなく、解決方法の情報を共有する場でもあります。
    解決しなかったにしても、
    ・回答された方法を試したが、〇〇という結果になり解決できなかった
    ・回答された方法を試すのが難しすぎた、内容が理解出来なかった
    ・〇〇の理由で回答の方法は使えなかった
    くらいは書いてほしいです。
    聞くだけ聞いて、回答した事全然試さなかったのかなと思ってしまいます。

Comments

  1. @Sum001100

    Questioner

    ありがとうございます。
    こちらのサイト拝見していたのですが、自分の使用しているVisualStudioProfessionalでは使用できないようなので、断念しておりました。説明不足でしたすみません。

Your answer might help someone💌