Skip to content

Commit 4cea870

Browse files
authored
Merge pull request #19 from dotnet-campus/t/lindexi/CustomTestManager
Add CustomTestManager
2 parents 145f816 + 05e90d6 commit 4cea870

File tree

9 files changed

+363
-11
lines changed

9 files changed

+363
-11
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018 dotnet职业技术学院
3+
Copyright (c) 2021 dotnet职业技术学院
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

src/MSTest.Extensions/Contracts/ContractTestCaseAttribute.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.Contracts;
55
using System.Linq;
66
using System.Reflection;
7+
using System.Threading.Tasks;
78
using Microsoft.VisualStudio.TestTools.UnitTesting;
89
using MSTest.Extensions.Core;
910
using MSTest.Extensions.Utils;
@@ -46,6 +47,14 @@ public override TestResult[] Execute([NotNull] ITestMethod testMethod)
4647
return new[] { result };
4748
}
4849

50+
[NotNull]
51+
internal Task<TestResult> ExecuteAsync([NotNull] ITestMethod testMethod)
52+
{
53+
_testMethodProxy ??= new TestMethodProxy(testMethod);
54+
55+
return _testMethodProxy.InvokerAsync();
56+
}
57+
4958
#endregion
5059

5160
#region Instance derived from ITestDataSource
@@ -197,6 +206,6 @@ 1. Please check whether you have created two test cases which have the same cont
197206
/// overwrite the invoke method
198207
/// only one instance in one ContractTestCaseAttribute
199208
/// </summary>
200-
private ITestMethod _testMethodProxy;
209+
private TestMethodProxy _testMethodProxy;
201210
}
202211
}

src/MSTest.Extensions/Core/ContractTestCase.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,26 @@ internal ContractTestCase([NotNull] string contract, [NotNull] Func<Task> testCa
5050
public string DisplayName => _contract;
5151

5252
/// <inheritdoc />
53-
public TestResult Result => _result ?? (_result = Execute());
53+
public TestResult Result => _result ??= Execute();
5454

5555
/// <summary>
5656
/// Invoke the test case action to get the test result.
5757
/// </summary>
5858
/// <returns>The test result of this test case.</returns>
5959
[NotNull]
6060
private TestResult Execute()
61+
{
62+
return ExecuteAsync().Result;
63+
}
64+
65+
/// <summary>
66+
/// Invoke the test case action to get the test result.
67+
/// </summary>
68+
[ItemNotNull]
69+
internal async Task<TestResult> ExecuteAsync()
6170
{
6271
// Execute the unit test.
63-
var (duration, output, error, exception) = ExecuteCore();
72+
var (duration, output, error, exception) = await ExecuteCoreAsync().ConfigureAwait(false);
6473

6574
// Return the test result.
6675
var result = new TestResult
@@ -87,12 +96,12 @@ private TestResult Execute()
8796
/// Execute the unit test and get the execution result.
8897
/// </summary>
8998
/// <returns>
90-
/// duration: The ellapsed time of executing this test case.
99+
/// duration: The elapsed time of executing this test case.
91100
/// output: When `Console.Write` is called, the output will be collected into this property.
92101
/// output: When `Console.Write` is called, it will be collected into this property.
93102
/// exception: If any exception occurred, it will be collected into this property; otherwise, it will be null.
94103
/// </returns>
95-
private (TimeSpan duration, string output, string error, Exception exception) ExecuteCore()
104+
private async Task<(TimeSpan duration, string output, string error, Exception exception)> ExecuteCoreAsync()
96105
{
97106
TimeSpan duration;
98107
string output;
@@ -115,7 +124,7 @@ private TestResult Execute()
115124
// Execute the test case.
116125
try
117126
{
118-
_testCase().Wait();
127+
await _testCase().ConfigureAwait(false);
119128
exception = null;
120129
}
121130
catch (AggregateException ex)

src/MSTest.Extensions/Core/TestMethodProxy.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Reflection;
4+
using System.Threading.Tasks;
45
using Microsoft.VisualStudio.TestTools.UnitTesting;
56
using MSTest.Extensions.Contracts;
67
using MSTest.Extensions.Utils;
78

89
namespace MSTest.Extensions.Core
910
{
1011
/// <summary>
11-
/// the testmethod proxy, replace the implement of ItestMethod invoke
12+
/// the TestMethod proxy, replace the implement of ITestMethod invoke
1213
/// </summary>
1314
public class TestMethodProxy : ITestMethod
1415
{
@@ -44,6 +45,26 @@ public TestResult Invoke(object[] arguments)
4445
return result;
4546
}
4647

48+
[NotNull]
49+
[ItemNotNull]
50+
internal async Task<TestResult> InvokerAsync()
51+
{
52+
TestMethodInitialize();
53+
var testCases = ContractTest.Method[_realSubject.MethodInfo];
54+
var testCase = testCases[_testCaseIndex++];
55+
TestResult result;
56+
if (testCase is ContractTestCase contractTestCase)
57+
{
58+
result = await contractTestCase.ExecuteAsync().ConfigureAwait(false);
59+
}
60+
else
61+
{
62+
result = testCases[_testCaseIndex++].Result;
63+
}
64+
TestMethodCleanup();
65+
return result;
66+
}
67+
4768
/// <summary>
4869
/// return real instance implement of GetAllAttributes
4970
/// </summary>
@@ -57,12 +78,12 @@ public Attribute[] GetAllAttributes(bool inherit)
5778
/// <summary>
5879
/// return real instance implement of GetAttributes
5980
/// </summary>
60-
/// <typeparam name="AttributeType"></typeparam>
81+
/// <typeparam name="TAttributeType"></typeparam>
6182
/// <param name="inherit"></param>
6283
/// <returns></returns>
63-
public AttributeType[] GetAttributes<AttributeType>(bool inherit) where AttributeType : Attribute
84+
public TAttributeType[] GetAttributes<TAttributeType>(bool inherit) where TAttributeType : Attribute
6485
{
65-
return _realSubject.GetAttributes<AttributeType>(inherit);
86+
return _realSubject.GetAttributes<TAttributeType>(inherit);
6687
}
6788

6889
/// <summary>
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Threading.Tasks;
7+
using Microsoft.VisualStudio.TestTools.UnitTesting;
8+
using MSTest.Extensions.Contracts;
9+
10+
namespace MSTest.Extensions.CustomTestManagers
11+
{
12+
/// <summary>
13+
/// The custom test manager which can run without MSTest
14+
/// </summary>
15+
public class CustomTestManager
16+
{
17+
/// <summary>
18+
/// Create the custom test manager
19+
/// </summary>
20+
public CustomTestManager()
21+
{
22+
Context = new CustomTestManagerRunContext();
23+
}
24+
25+
/// <summary>
26+
/// Create the custom test manager
27+
/// </summary>
28+
/// <param name="context"></param>
29+
public CustomTestManager(CustomTestManagerRunContext context)
30+
{
31+
Context = context;
32+
}
33+
34+
/// <summary>
35+
/// Run all the test from MethodInfo
36+
/// </summary>
37+
/// <param name="methodInfo"></param>
38+
/// <returns></returns>
39+
[NotNull]
40+
[ItemNotNull]
41+
public async Task<TestManagerRunResult> RunAsync([NotNull] MethodInfo methodInfo)
42+
{
43+
if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo));
44+
var exceptionList = new List<TestExceptionResult>();
45+
int count = 0;
46+
TimeSpan duration = TimeSpan.Zero;
47+
48+
var contractTestCaseAttribute = methodInfo.GetCustomAttribute<ContractTestCaseAttribute>();
49+
if (contractTestCaseAttribute != null)
50+
{
51+
// 获取执行次数
52+
foreach (var data in contractTestCaseAttribute.GetData(methodInfo))
53+
{
54+
count++;
55+
var displayName = contractTestCaseAttribute.GetDisplayName(methodInfo, data);
56+
57+
try
58+
{
59+
var result = await contractTestCaseAttribute.ExecuteAsync(new FakeTestMethod(methodInfo))
60+
// 在 UI 中进行测试,期望每次都是返回到相同的线程
61+
.ConfigureAwait(true);
62+
duration += result.Duration;
63+
}
64+
#pragma warning disable CA1031 // 不捕获常规异常类型
65+
catch (Exception e)
66+
#pragma warning restore CA1031 // 不捕获常规异常类型
67+
{
68+
exceptionList.Add(new TestExceptionResult(displayName, e));
69+
}
70+
}
71+
}
72+
73+
return new TestManagerRunResult(count, duration, exceptionList);
74+
}
75+
76+
/// <summary>
77+
/// Run all the test from TestClass
78+
/// </summary>
79+
/// <returns></returns>
80+
[NotNull]
81+
[ItemNotNull]
82+
public async Task<TestManagerRunResult> RunAsync([NotNull] Type testClass)
83+
{
84+
if (testClass == null) throw new ArgumentNullException(nameof(testClass));
85+
var exceptionList = new List<TestExceptionResult>();
86+
int count = 0;
87+
88+
TimeSpan duration = TimeSpan.Zero;
89+
90+
foreach (var methodInfo in testClass.GetMethods())
91+
{
92+
var testManagerRunResult = await RunAsync(methodInfo)
93+
// 在 UI 中进行测试,期望每次都是返回到相同的线程
94+
.ConfigureAwait(true);
95+
96+
count += testManagerRunResult.AllTestCount;
97+
duration += testManagerRunResult.Duration;
98+
exceptionList.AddRange(testManagerRunResult.TestExceptionResultList);
99+
}
100+
101+
return new TestManagerRunResult(count, duration, exceptionList);
102+
}
103+
104+
/// <summary>
105+
/// Run all the test in assembly
106+
/// </summary>
107+
/// <param name="assembly"></param>
108+
/// <returns></returns>
109+
[NotNull]
110+
[ItemNotNull]
111+
public async Task<TestManagerRunResult> RunAsync([NotNull]Assembly assembly)
112+
{
113+
if (assembly is null)
114+
{
115+
throw new ArgumentNullException(nameof(assembly));
116+
}
117+
118+
var exceptionList = new List<TestExceptionResult>();
119+
int count = 0;
120+
121+
TimeSpan duration = TimeSpan.Zero;
122+
123+
foreach (var type in assembly.GetTypes())
124+
{
125+
if (type.GetCustomAttribute<TestClassAttribute>() != null)
126+
{
127+
var testManagerRunResult = await RunAsync(type)
128+
// 在 UI 中进行测试,期望每次都是返回到相同的线程
129+
.ConfigureAwait(true);
130+
131+
count += testManagerRunResult.AllTestCount;
132+
duration += testManagerRunResult.Duration;
133+
exceptionList.AddRange(testManagerRunResult.TestExceptionResultList);
134+
}
135+
}
136+
137+
return new TestManagerRunResult(count, duration, exceptionList);
138+
}
139+
140+
private CustomTestManagerRunContext Context { get; }
141+
142+
class FakeTestMethod : ITestMethod
143+
{
144+
public FakeTestMethod([NotNull] MethodInfo methodInfo, [CanBeNull] object obj = null)
145+
{
146+
MethodInfo = methodInfo;
147+
Obj = obj ?? Activator.CreateInstance(methodInfo.DeclaringType!);
148+
}
149+
150+
[NotNull]
151+
public TestResult Invoke(object[] arguments)
152+
{
153+
MethodInfo.Invoke(Obj, arguments);
154+
return new TestResult();
155+
}
156+
157+
[NotNull]
158+
public Attribute[] GetAllAttributes(bool inherit)
159+
{
160+
return MethodInfo.GetCustomAttributes().ToArray();
161+
}
162+
163+
[NotNull]
164+
public TAttributeType[] GetAttributes<TAttributeType>(bool inherit) where TAttributeType : Attribute
165+
{
166+
return MethodInfo.GetCustomAttributes().OfType<TAttributeType>().ToArray();
167+
}
168+
169+
private object Obj { get; }
170+
[NotNull]
171+
public string TestMethodName => MethodInfo.Name;
172+
[NotNull]
173+
public string TestClassName => MethodInfo.DeclaringType?.Name ?? string.Empty;
174+
[CanBeNull]
175+
public Type ReturnType => MethodInfo.ReturnType;
176+
public object[] Arguments { set; get; }
177+
[CanBeNull]
178+
public ParameterInfo[] ParameterTypes => MethodInfo.GetParameters();
179+
public MethodInfo MethodInfo { set; get; }
180+
181+
public FakeTestInfo Parent { get; } = new FakeTestInfo();
182+
}
183+
184+
class FakeTestInfo
185+
{
186+
// 命名不能更改,框架内使用反射获取
187+
188+
private MethodInfo testCleanupMethod;
189+
private MethodInfo testInitializeMethod;
190+
191+
public Queue<MethodInfo> BaseTestInitializeMethodsQueue { set; get; }
192+
193+
public Queue<MethodInfo> BaseTestCleanupMethodsQueue { set; get; }
194+
}
195+
}
196+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace MSTest.Extensions.CustomTestManagers
2+
{
3+
/// <summary>
4+
/// The context for custom test manager
5+
/// </summary>
6+
public class CustomTestManagerRunContext
7+
{
8+
}
9+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
3+
namespace MSTest.Extensions.CustomTestManagers
4+
{
5+
/// <summary>
6+
/// The test exception result
7+
/// </summary>
8+
public class TestExceptionResult
9+
{
10+
internal TestExceptionResult(string displayName, Exception exception)
11+
{
12+
DisplayName = displayName;
13+
Exception = exception;
14+
}
15+
16+
/// <summary>
17+
/// The test display name
18+
/// </summary>
19+
public string DisplayName { get; }
20+
21+
/// <summary>
22+
/// The test exception
23+
/// </summary>
24+
public Exception Exception { get; }
25+
}
26+
}

0 commit comments

Comments
 (0)