MSTest
テストツール
MSTest
概要
MSTestはMicrosoftが開発する.NETアプリケーション用の公式テストフレームワークです。Visual StudioとVisual Studio Code Test Explorer、.NET CLI、多数のCIパイプラインとの統合を提供し、.NET Framework、.NET Core、.NET、UWP、WinUIなど、すべてのサポートされる.NETターゲットで動作します。オープンソースでクロスプラットフォーム対応の成熟したテストフレームワークとして、企業開発において広く採用されています。
詳細
MSTestの最大の特徴は、Microsoft開発エコシステムとの深い統合にあります。Visual Studioでのテスト実行とデバッグ体験が非常に優れており、IntelliSenseサポートやテストエクスプローラーとの連携により、効率的なテスト開発が可能です。2024年のMSTest 3.8では、Microsoft.Testing.Platform(MTP)への統合、フィルタリング機能の向上、協調的キャンセレーション機能などが追加されています。
属性ベースのテスト定義では、[TestClass]、[TestMethod]、[DataRow]、[TestInitialize]、[TestCleanup]など豊富な属性を提供し、テストライフサイクルを細かく制御できます。並列実行機能により、クラスレベルやメソッドレベルでの並行テスト実行が可能で、大規模なテストスイートでも高速な実行を実現できます。
メリット・デメリット
メリット
- Visual Studio統合: 最高クラスのIDE統合とデバッグ体験
- Microsoft公式: .NETエコシステムでの安定性と長期サポート
- 豊富な属性: テストライフサイクルを制御する包括的な属性セット
- データ駆動テスト: DataRowやDynamicDataによる柔軟なパラメータ化
- 並列実行: クラス・メソッドレベルでの並行実行サポート
- Enterprise対応: 大規模プロジェクトでの実績と信頼性
デメリット
- Microsoft依存: .NET以外の環境では使用不可
- 設定の複雑さ: 高度な機能を使用する際の設定が複雑
- 柔軟性の制限: 他フレームワークと比べてカスタマイズ性が低い
- 学習コスト: Microsoft特有の概念や命名規則の習得が必要
参考ページ
書き方の例
Hello World
// MSTest.Sdk プロジェクトファイル
<Project Sdk="MSTest.Sdk/3.8.3">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
// HelloWorldTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class HelloWorldTest
{
[TestMethod]
public void TestGreeting()
{
string result = Greet("MSTest");
Assert.AreEqual("Hello, MSTest!", result);
}
private string Greet(string name)
{
return $"Hello, {name}!";
}
}
基本テスト
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CalculatorTest
{
private Calculator _calculator;
[TestInitialize]
public void Setup()
{
_calculator = new Calculator();
}
[TestMethod]
public void Add_PositiveNumbers_ReturnsCorrectSum()
{
// Arrange
int a = 5;
int b = 3;
// Act
int result = _calculator.Add(a, b);
// Assert
Assert.AreEqual(8, result);
}
[TestMethod]
public void Add_NegativeNumbers_ReturnsCorrectSum()
{
// Arrange & Act
int result = _calculator.Add(-2, -3);
// Assert
Assert.AreEqual(-5, result);
}
[TestMethod]
[ExpectedException(typeof(DivideByZeroException))]
public void Divide_ByZero_ThrowsException()
{
// Act
_calculator.Divide(10, 0);
}
[TestMethod]
[Timeout(2000)]
public void ComplexCalculation_CompletesWithinTimeout()
{
// Act
var result = _calculator.ComplexCalculation();
// Assert
Assert.IsNotNull(result);
}
[TestCleanup]
public void Cleanup()
{
_calculator?.Dispose();
}
}
モック
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class UserServiceTest
{
private Mock<IUserRepository> _mockRepository;
private UserService _userService;
[TestInitialize]
public void Setup()
{
_mockRepository = new Mock<IUserRepository>();
_userService = new UserService(_mockRepository.Object);
}
[TestMethod]
public void GetUser_ValidId_ReturnsUser()
{
// Arrange
var expectedUser = new User { Id = 1, Name = "John Doe" };
_mockRepository.Setup(r => r.GetById(1))
.Returns(expectedUser);
// Act
var result = _userService.GetUser(1);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("John Doe", result.Name);
_mockRepository.Verify(r => r.GetById(1), Times.Once);
}
[TestMethod]
public void CreateUser_ValidUser_CallsRepository()
{
// Arrange
var newUser = new User { Name = "Jane Smith" };
var savedUser = new User { Id = 2, Name = "Jane Smith" };
_mockRepository.Setup(r => r.Save(It.IsAny<User>()))
.Returns(savedUser);
// Act
var result = _userService.CreateUser(newUser);
// Assert
Assert.AreEqual(2, result.Id);
Assert.AreEqual("Jane Smith", result.Name);
_mockRepository.Verify(r => r.Save(It.IsAny<User>()), Times.Once);
}
}
非同期テスト
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading.Tasks;
[TestClass]
public class AsyncOperationTest
{
[TestMethod]
public async Task ProcessDataAsync_ValidInput_ReturnsProcessedData()
{
// Arrange
var processor = new DataProcessor();
var inputData = "test data";
// Act
var result = await processor.ProcessDataAsync(inputData);
// Assert
Assert.IsNotNull(result);
Assert.IsTrue(result.Contains("processed"));
}
[TestMethod]
public async Task DownloadFileAsync_LargeFile_CompletesSuccessfully()
{
// Arrange
var downloader = new FileDownloader();
var url = "https://example.com/largefile.zip";
// Act
var success = await downloader.DownloadAsync(url);
// Assert
Assert.IsTrue(success);
}
[TestMethod]
[Timeout(5000)]
public async Task SlowOperationAsync_WithTimeout_CompletesWithinTimeLimit()
{
// Arrange
var service = new SlowService();
// Act
var result = await service.ProcessSlowlyAsync();
// Assert
Assert.IsNotNull(result);
}
}
セットアップ・ティアダウン
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class DatabaseIntegrationTest
{
private static DatabaseContext _context;
private TestDataBuilder _dataBuilder;
[ClassInitialize]
public static void ClassSetup(TestContext context)
{
// テストクラス全体の初期化
_context = new DatabaseContext("test-connection-string");
_context.Database.EnsureCreated();
}
[ClassCleanup]
public static void ClassTeardown()
{
// テストクラス全体のクリーンアップ
_context?.Database.EnsureDeleted();
_context?.Dispose();
}
[TestInitialize]
public void Setup()
{
// 各テストメソッドの前処理
_dataBuilder = new TestDataBuilder(_context);
_dataBuilder.SeedTestData();
}
[TestCleanup]
public void Cleanup()
{
// 各テストメソッドの後処理
_dataBuilder?.CleanTestData();
}
[TestMethod]
public void SaveUser_ValidUser_SavesSuccessfully()
{
// Arrange
var user = new User { Name = "Test User", Email = "[email protected]" };
// Act
_context.Users.Add(user);
_context.SaveChanges();
// Assert
var savedUser = _context.Users.FirstOrDefault(u => u.Email == "[email protected]");
Assert.IsNotNull(savedUser);
Assert.AreEqual("Test User", savedUser.Name);
}
[TestMethod]
public void GetUsers_WithData_ReturnsAllUsers()
{
// Act
var users = _context.Users.ToList();
// Assert
Assert.IsTrue(users.Count > 0);
}
}
高度な機能
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class AdvancedMSTestFeatures
{
// データ駆動テスト
[TestMethod]
[DataRow("apple", 5)]
[DataRow("banana", 6)]
[DataRow("cherry", 6)]
public void TestStringLength(string input, int expectedLength)
{
Assert.AreEqual(expectedLength, input.Length);
}
// 動的データプロバイダー
[TestMethod]
[DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)]
public void TestWithDynamicData(int value, int expected)
{
var result = Math.Abs(value);
Assert.AreEqual(expected, result);
}
public static IEnumerable<object[]> GetTestData()
{
yield return new object[] { -5, 5 };
yield return new object[] { 10, 10 };
yield return new object[] { -15, 15 };
}
// 条件付きテスト
[TestMethod]
public void TestOnlyOnWindows()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Assert.Inconclusive("このテストはWindowsでのみ実行されます");
}
// Windows固有のテストロジック
Assert.IsTrue(true);
}
// カテゴリ別テスト
[TestMethod]
[TestCategory("Integration")]
[TestCategory("Database")]
public void DatabaseIntegrationTest()
{
// 統合テストのロジック
Assert.IsTrue(true);
}
[TestMethod]
[TestCategory("Unit")]
public void UnitTest()
{
// 単体テストのロジック
Assert.IsTrue(true);
}
// カスタム属性
[TestMethod]
[Owner("開発チーム")]
[Priority(1)]
[Description("重要な機能のテスト")]
public void CriticalFeatureTest()
{
// 重要な機能のテスト
Assert.IsTrue(true);
}
// 並列実行テスト
[TestMethod]
public void ParallelTest1()
{
Thread.Sleep(1000);
Assert.IsTrue(true);
}
[TestMethod]
public void ParallelTest2()
{
Thread.Sleep(1000);
Assert.IsTrue(true);
}
// アサーションの種類
[TestMethod]
public void TestVariousAssertions()
{
// 基本アサーション
Assert.IsTrue(true);
Assert.IsFalse(false);
Assert.IsNull(null);
Assert.IsNotNull("value");
// 文字列アサーション
StringAssert.Contains("Hello World", "World");
StringAssert.StartsWith("Hello World", "Hello");
StringAssert.EndsWith("Hello World", "World");
// コレクションアサーション
var list = new List<int> { 1, 2, 3 };
CollectionAssert.Contains(list, 2);
CollectionAssert.DoesNotContain(list, 5);
// 例外アサーション
Assert.ThrowsException<ArgumentException>(() => {
throw new ArgumentException("test");
});
}
}