diff --git a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
index db7d4e4da2..47b4e8c12a 100644
--- a/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
@@ -6,3 +6,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0058 | Usage | Info | AvoidAssertsInCatchBlocksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0058)
+MSTEST0059 | Usage | Warning | DuplicateTestMethodAttributeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0059)
diff --git a/src/Analyzers/MSTest.Analyzers/DuplicateTestMethodAttributeAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/DuplicateTestMethodAttributeAnalyzer.cs
new file mode 100644
index 0000000000..a25f7faf5f
--- /dev/null
+++ b/src/Analyzers/MSTest.Analyzers/DuplicateTestMethodAttributeAnalyzer.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Immutable;
+
+using Analyzer.Utilities.Extensions;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+using MSTest.Analyzers.Helpers;
+
+namespace MSTest.Analyzers;
+
+///
+/// MSTEST0059: .
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
+public sealed class DuplicateTestMethodAttributeAnalyzer : DiagnosticAnalyzer
+{
+ internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
+ DiagnosticIds.DuplicateTestMethodAttributeRuleId,
+ new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeTitle), Resources.ResourceManager, typeof(Resources)),
+ new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeMessageFormat), Resources.ResourceManager, typeof(Resources)),
+ new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeDescription), Resources.ResourceManager, typeof(Resources)),
+ Category.Usage,
+ DiagnosticSeverity.Warning,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(context =>
+ {
+ if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out INamedTypeSymbol? testMethodAttributeSymbol))
+ {
+ context.RegisterSymbolAction(
+ context => AnalyzeSymbol(context, testMethodAttributeSymbol),
+ SymbolKind.Method);
+ }
+ });
+ }
+
+ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testMethodAttributeSymbol)
+ {
+ var methodSymbol = (IMethodSymbol)context.Symbol;
+
+ int testMethodAttributeCount = 0;
+ foreach (AttributeData attribute in methodSymbol.GetAttributes())
+ {
+ if (attribute.AttributeClass is not null && attribute.AttributeClass.Inherits(testMethodAttributeSymbol))
+ {
+ testMethodAttributeCount++;
+ if (testMethodAttributeCount > 1)
+ {
+ // Report diagnostic on the method itself
+ context.ReportDiagnostic(methodSymbol.CreateDiagnostic(Rule, methodSymbol.Name));
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
index 15b809366e..4bd0292609 100644
--- a/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
+++ b/src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
@@ -63,4 +63,5 @@ internal static class DiagnosticIds
public const string TestMethodAttributeShouldSetDisplayNameCorrectlyRuleId = "MSTEST0056";
public const string TestMethodAttributeShouldPropagateSourceInformationRuleId = "MSTEST0057";
public const string AvoidAssertsInCatchBlocksRuleId = "MSTEST0058";
+ public const string DuplicateTestMethodAttributeRuleId = "MSTEST0059";
}
diff --git a/src/Analyzers/MSTest.Analyzers/Resources.resx b/src/Analyzers/MSTest.Analyzers/Resources.resx
index a8d5123404..6f6764692f 100644
--- a/src/Analyzers/MSTest.Analyzers/Resources.resx
+++ b/src/Analyzers/MSTest.Analyzers/Resources.resx
@@ -699,4 +699,13 @@ The type declaring these methods should also respect the following rules:
Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use 'Assert.Throws', 'Assert.ThrowsExactly', 'Assert.ThrowsAsync' or 'Assert.ThrowsExactlyAsync' to verify that an exception is thrown, and then make additional assertions on the caught exception without using the try-catch block.
+
+ Avoid duplicate test method attributes
+
+
+ Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute should be used.
+
+
+ A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. When multiple such attributes are present, only the first one returned by reflection will be used.
+
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
index 1a543104f9..23320dac4d 100644
--- a/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
@@ -1014,6 +1014,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
Používání kontrolních výrazů v blocích catch je problematické, protože test projde, i když se nevyvolá žádná výjimka a blok catch se nikdy nespustí. K ověření, že je vyvolána výjimka, použijte metody Assert.Throws, Assert.ThrowsExactly, Assert.ThrowsAsync nebo Assert.ThrowsExactlyAsync a poté proveďte další kontrolní výrazy nad zachycenou výjimkou bez použití bloku try-catch.
+
+ Avoid duplicate test method attributes
+ Avoid duplicate test method attributes
+
+
+
+ Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute should be used.
+ Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute should be used.
+
+
+
+ A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. When multiple such attributes are present, only the first one returned by reflection will be used.
+ A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. When multiple such attributes are present, only the first one returned by reflection will be used.
+
+