VS2022でGitHub Copilotに単体テストを生成させていると、プロジェクトの“流儀”とズレたコード(例:AutoMapperのIMapperを毎回モックする)が出て困ることがあります。毎回プロンプトに追記せず、リポジトリ側の「ルールファイル」で生成結果を寄せる具体策を、Visual Studio前提で整理します。
結論:Copilotに「自分流の単体テスト」を寄せる現実解は3つ
「IMapperはモックしない」「mstest + moqで書く」などの方針を、毎回プロンプトに書かずに反映させたいなら、現時点で現実的に効く手段は次の3つです。
- リポジトリ用カスタムインストラクション:.github配下のファイルで、プロジェクトのルールを“常時”渡す
- パス別インストラクション:テストコードだけ強く効かせる(テスト以外に副作用を出しにくい)
- プロンプトファイル:よく使う「テスト生成の指示」を“ワンアクションで呼び出す”
ただし重要なのは、Copilotはリンターやテンプレートエンジンではなく生成AIなので、「必ず毎回100%その通り」にはなりません。代わりに、上の3つを組み合わせると、「外れにくくなる」「直す量が減る」「チームで統一できる」状態に持っていけます。
なぜCopilotはIMapperをモックしたがるのか
Copilotが提案する単体テストは、一般的な“依存を全てモックする”パターンに引っ張られやすいです。AutoMapperのIMapperも「外部依存=モック対象」と判断されやすく、典型的には次のような提案になります。
private Mock<IMapper> _mockMapper;
[TestInitialize]
public void Setup()
{
_mockMapper = new Mock();
_mockMapper.Setup(m => m.Map(It.IsAny()))
.Returns(new Destination());
}
一方で、プロジェクト側の方針として「マッピングプロファイルも含めて検証したい」「MapFactory.Create()で実マッパーを作る」と決めている場合、望む形はこうです。
private IMapper _mapper;
[TestInitialize]
public void Setup()
{
_mapper = MapFactory.Create();
}
このズレを埋めるには、Copilotが毎回参照できる形で「このリポジトリではIMapperは例外扱い」と伝えるのが近道です。
「ルールファイル」は置ける:カスタムインストラクションの種類を整理
まず、Copilotに“方針”を覚えさせる仕組みには種類があります。混同しやすいので、単体テスト生成の観点で整理します。
| 仕組み | 置き場所 / 設定 | 効く範囲 | 単体テスト生成での使いどころ | 注意点 |
|---|---|---|---|---|
| 個人用カスタムインストラクション | GitHub.comのCopilot Chatで個人設定 | 自分だけ | 「自分の好み」を固定したい | GitHub.com上のCopilot Chat中心。IDE側と完全一致を期待しない |
| リポジトリ全体のインストラクション | .github/copilot-instructions.md | そのリポジトリ全体 | テストフレームワークや例外ルール(IMapper等)をチームで統一 | 複数ルールが衝突すると結果が不安定になり得る |
| パス別インストラクション | .github/instructions/*.instructions.md(applyTo付き) | 対象パスのみ | 「テストプロジェクトだけ」に強く効かせる | applyToパターン設計が肝 |
| プロンプトファイル | .github/prompts/*.prompt.md | 呼び出した時だけ | “テスト生成の依頼文”を定型化(毎回打たない) | 常時適用ではない。ルールはインストラクション側に寄せると管理しやすい |
Visual Studio 2022で「ルールファイル」を効かせる手順
Visual Studioでルールファイル(カスタムインストラクション)を使うには、前提と設定がポイントです。特に、Visual Studio側で“読み込みを有効化する”手順を飛ばすと、ファイルを置いても効きません。
前提条件
- Visual Studio 2022(Copilot Chat対応のバージョンが必要)
- GitHubアカウントでVisual Studioへサインインし、Copilotが利用できる状態
手順
- リポジトリのルートに
.github/copilot-instructions.mdを作成(.githubがなければ作成) - 必要なら
.github/instructionsを作り、*.instructions.mdを追加(後述) - Visual Studioのメニューから Tools > Options を開く
- GitHub > Copilot > Copilot Chat(または同等のセクション)で、.github/copilot-instructions.mdを読み込む設定を有効化
- Copilot Chatで応答を出し、応答のReferences(参照)にインストラクションファイルが載っているか確認
カスタムインストラクションは、チャット画面に“本文として”表示されません。代わりに、Copilotが参照として読み込んだ場合、応答のReferencesにファイルが出ます。ここが動作確認の最短ルートです。
まずはこれだけ:.github/copilot-instructions.md の実用テンプレ
インストラクションは長文ポエムより、短いルールの集合の方が安定します。特に「例外」を効かせたい場合(IMapperはモックしない等)は、肯定形+禁止形をセットにするとズレにくくなります。
# Copilot instructions (C# unit tests)
- This repository uses MSTest for unit tests.
- Use Moq for mocking dependencies, but DO NOT create Mock<IMapper> for AutoMapper.
- For AutoMapper, always create a real IMapper instance via MapFactory.Create() and use real mapping profiles.
- Prefer [TestInitialize] to set up shared fixtures (e.g., _mapper = MapFactory.Create()).
- Use Arrange / Act / Assert comments.
- Test class name: {TargetClassName}Tests
- Test method name: {MethodName}_Should{Expected}_When{Condition}
- Keep tests deterministic: avoid DateTime.Now and random values unless explicitly controlled.
このファイルを置いた上で、いつも通りに「mstest と moq を使ってこのクラスの単体テストを書いて」と頼むだけでも、IMapperをモックせずにMapFactory.Create()を使う提案に寄りやすくなります。
“自分流”を伝えるときの書き方のコツ
| 狙い | 悪い例(ズレやすい) | 良い例(伝わりやすい) |
|---|---|---|
| IMapperを実体で使わせたい | 「AutoMapperは良い感じに」 | 「Mock<IMapper>は作らない」「_mapper = MapFactory.Create()」 |
| フレームワークを固定したい | 「ユニットテストを書いて」 | 「MSTestを使う」「[TestClass][TestMethod]を使う」 |
| 例外ルールを定着させたい | 「なるべくこのプロジェクトの流儀で」 | 「例外:IMapperは常に実体。その他は原則Moq」 |
精度を上げる:テストコードだけに効かせる“パス別インストラクション”
リポジトリ全体に「テストの細かい書き方」を適用すると、通常コードの生成にも影響が出ることがあります。そこでおすすめなのが、テストプロジェクト配下の.csだけに適用するパス別インストラクションです。
例:.github/instructions/csharp-tests.instructions.md
---
description: "C# unit test conventions (MSTest/Moq + MapFactory)"
applyTo: "**/*.Tests/**/*.cs,**/*Tests*/**/*.cs,**/*Test*/**/*.cs"
---
* Use MSTest ([TestClass], [TestInitialize], [TestMethod]).
* Use Moq for dependencies, but never mock AutoMapper IMapper.
* Always create IMapper using: _mapper = MapFactory.Create();
* Prefer strict mocks for collaborators (MockBehavior.Strict) when appropriate.
* Use Arrange / Act / Assert comments.
* Verify Moq setups where it adds value (Verify / VerifyAll), but avoid over-verification.
この形にしておくと、通常の業務コード(例:Services配下)で「MSTestの属性を付ける」などの副作用が出にくく、単体テスト生成のときだけ狙い撃ちできます。
衝突を避けるのが最重要
リポジトリ全体のルールとパス別ルールが同時に適用されることがあります。その際、内容が矛盾しているとCopilotの判断がブレやすくなります。例えば「IMapperはモックする」と「IMapperはモックしない」が混ざると、どちらが出るかは安定しません。一貫性を最優先に設計してください。
プロンプトも“ファイル化”する:.github/prompts のプロンプトファイル
「毎回プロンプトに追記したくない」という要望は、ルール(インストラクション)だけでなく、依頼文自体も対象です。Visual StudioのCopilot Chatは、リポジトリ内に置いたプロンプトファイルを呼び出して使えます。
例:.github/prompts/generate-mstest-tests.prompt.md
---
name: mstest-tests
description: "MSTest + Moqで単体テストを生成(IMapperはMapFactory.Create)"
argument-hint: "対象クラスやメソッドを選択、または #file: で対象ファイルを指定"
---
以下の条件でC#の単体テストを生成してください。
* テストフレームワークは MSTest を使用する([TestClass][TestMethod])。
* モックは Moq を使う。
* AutoMapper の IMapper はモックしない。必ず _mapper = MapFactory.Create() で実体を生成する。
* 生成するテストは Arrange / Act / Assert で構造化する。
* 成功系だけでなく、代表的な失敗系(null、境界値、例外、空コレクション等)を含める。
* 依存のセットアップは必要最小限にし、テストの意図が読める名前を付ける。
対象:
${selection}
使い方はシンプルで、Copilot Chatの入力欄で #prompt: を使って呼び出します。プロンプトの本文を毎回打たずに済むので、チーム内で「このプロンプトでテスト生成」という運用も作れます。
また、Visual Studioではスラッシュコマンド(例:/tests)や「Generate Tests」アクションからテスト生成に入れます。プロンプトファイルは、そうした導線に“追加の意図”を足す用途でも便利です(例:IMapper例外、命名規則、AAA固定)。
「IMapperはモックしない」を外しにくくする設計パターン
インストラクションで“寄せる”のに加えて、プロジェクト構造として「そう書かざるを得ない形」を作ると、さらに安定します。特にIMapperの扱いは、テスト側に“定番の作り方”があるだけでCopilotの出力が揃いやすいです。
パターン1:テスト用ベースクラスに寄せる
テストプロジェクトに、Mapper初期化を吸収するベースクラスを用意します。Copilotには「テストはこのベースクラスを継承する」と教えるだけで済みます。
using AutoMapper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
public abstract class TestBase
{
protected IMapper Mapper { get; private set; } = default!;
[TestInitialize]
public void BaseInitialize()
{
Mapper = MapFactory.Create();
}
}
そして各テストはこうなります。
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class OrderServiceTests : TestBase
{
[TestMethod]
public void Create_ShouldReturnDto_WhenValidRequest()
{
// Arrange
var sut = new OrderService(Mapper /*, other deps */);
// Act
var result = sut.Create(/* ... */);
// Assert
Assert.IsNotNull(result);
}
}
この形にすると、CopilotがIMapperをモックに置き換える余地が減ります。インストラクションには「テストはTestBaseを継承する」と1行足すだけで十分です。
パターン2:MapFactory側で“検証込み”にしてブレを減らす
MapFactory.Create()の中でプロファイル登録と設定検証(例:設定が壊れていたら即fail)を行うと、テストの信頼性も上がります。Copilotが生成したテストがマッパー周りで偶然通る、という事故も減ります。
using AutoMapper;
public static class MapFactory
{
public static IMapper Create()
{
var config = new MapperConfiguration(cfg =>
{
cfg.AddProfile();
// cfg.AddProfile();
});
config.AssertConfigurationIsValid();
return config.CreateMapper();
}
}
パターン3:テストプロジェクト内に“お手本”を増やす
Copilotはリポジトリ内の既存パターンにも影響を受けます。ベースクラス+MapFactoryの形で書かれたテストが複数存在すると、生成もそこに寄りやすくなります。特に、よく触るサービス・ユースケースのテストを先に数本だけでも整備しておくと効果が出やすいです。
「完全に毎回その通り」にできない理由と、割り切りポイント
カスタムインストラクションやプロンプトファイルは、Copilotに追加コンテキストを与える仕組みで、テンプレートの強制ではありません。さらに、適用されるコンテキスト(選択範囲、開いているファイル、会話の流れ)によって、出力は揺れます。
とはいえ、実務では「ゼロ修正」よりも次の状態を目指す方が費用対効果が高いです。
- 生成されたテストの骨格(Arrange/Act/Assert、命名規則、フレームワーク)が揃う
- 例外ルール(IMapperは実体)が大半のケースで守られる
- 外した場合も、修正が数行で終わる(Mock<IMapper>を消してMapFactoryに差し替える程度)
外したときの“最短リカバリ”手順
それでもMock<IMapper>が出てきた場合、最短で直すには「同じ会話内で、参照を明示して言い直す」のが効きます。ポイントは、単に怒るのではなく、修正指示を機械に解釈しやすい形に落とすことです。
- 「IMapperはモックしない。_mapper = MapFactory.Create() に置き換えて再生成して」
- 「このリポジトリのルール(.github/copilot-instructions.md)に従って、テストを生成し直して」
- 「TestBaseを継承する形に揃えて」
さらに、Visual Studioではコード選択→/testsの流れで「対象を絞った生成」ができるため、ファイル丸ごと生成よりもブレが減る傾向があります。テスト対象のメソッドやクラスを選択してから依頼する運用もおすすめです。
おすすめの落としどころ:ルールはインストラクション、作業はプロンプト
運用の分離をすると、チームで回しやすくなります。
- インストラクション(常時):フレームワーク、例外ルール、命名、AAAなど“守ってほしい基準”
- プロンプト(必要時):どの粒度で、何を増やすか(正常系+代表ケース、境界値、例外系、データパターンなど)
この分離にしておくと、たとえば「境界値を厚めにするプロンプト」「例外系を重視するプロンプト」などを追加しても、基本ルール(IMapperは実体、MSTest固定)が崩れません。
まとめ:VS2022でも“自分流の単体テスト”はかなり寄せられる
「毎回プロンプトに書かないと守れない」を脱するには、リポジトリにルールを置く発想が有効です。特に、IMapperのような“例外ルール”は、リポジトリ用インストラクション+テスト専用のパス別インストラクションで安定します。さらに、プロンプトファイルで依頼文まで定型化すれば、生成の手間とブレをまとめて減らせます。
最後に、運用上いちばん効くのは「ルールをファイル化して、誰でも同じ手順で使える状態にする」ことです。Copilotを“個人技”から“チームの道具”に寄せるほど、単体テスト生成の投資対効果は上がります。

コメント