为了完整起见,我经常希望将测试作为控制台应用程序运行,因为我发现这样更容易调试……多年来,我创建了一些小的测试助手来帮助自己;我想您也可以很容易地将它们与 CI 解决方案一起使用。
我知道这并不完全是你的问题;然而,既然你正在寻找 CI 解决方案并提到了 Visual Studio,那么这应该能很好地解决它。
只是让你知道,我的小框架比这个大一点,但是缺少的东西很容易添加。基本上我省略了所有日志记录和测试不同程序集在不同应用程序域中(因为可能存在 DLL 冲突和状态)的事情。下面会详细介绍。
需要注意的一件事是,在下面的过程中,我没有捕获任何异常。我的主要重点是使您在故障排除时轻松调试应用程序。我有一个单独的(但类似的)实现用于 CI,基本上在下面的注释点上添加 try/catch。
这种方法只有一个限制:Visual Studio 不会复制您引用的所有程序集;它只会复制您在代码中使用的程序集。解决此问题的简单方法是引入一个从未被调用的方法,它使用您正在测试的 DLL 中的一种类型。这样,您的程序集就会被复制,一切都会很好地运作。
下面是代码:
static class TestHelpers
{
public static void TestAll(this object o)
{
foreach (MethodInfo meth in o.GetType().GetMethods().
Where((a) => a.GetCustomAttributes(true).
Any((b) => b.GetType().Name.Contains("TestMethod"))))
{
Console.WriteLine();
Console.WriteLine("--- Testing {0} ---", meth.Name);
Console.WriteLine();
var del = (Action)meth.CreateDelegate(typeof(Action), o);
del();
Console.WriteLine();
}
}
public static void TestAll(this Assembly ass)
{
HashSet<AssemblyName> visited = new HashSet<AssemblyName>();
Stack<Assembly> todo = new Stack<Assembly>();
todo.Push(ass);
HandleStack(visited, todo);
}
private static void HandleStack(HashSet<AssemblyName> visited, Stack<Assembly> todo)
{
while (todo.Count > 0)
{
var assembly = todo.Pop();
foreach (var refass in assembly.GetReferencedAssemblies())
{
TryAdd(refass, visited, todo);
}
foreach (var type in assembly.GetTypes().
Where((a) => a.GetCustomAttributes(true).
Any((b) => b.GetType().Name.Contains("TestClass"))))
{
var obj = Activator.CreateInstance(type);
obj.TestAll();
}
}
}
public static void TestAll()
{
HashSet<AssemblyName> visited = new HashSet<AssemblyName>();
Stack<Assembly> todo = new Stack<Assembly>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
TryAdd(assembly.GetName(), visited, todo);
}
HandleStack(visited, todo);
}
private static void TryAdd(AssemblyName ass, HashSet<AssemblyName> visited, Stack<Assembly> todo)
{
try
{
var reference = Assembly.Load(ass);
if (reference != null &&
!reference.GlobalAssemblyCache &&
reference.FullName != null &&
!reference.FullName.StartsWith("ms") &&
!reference.FullName.StartsWith("vshost") &&
!reference.FullName.StartsWith("System"))
{
if (visited.Add(reference.GetName()))
{
todo.Push(reference);
}
}
}
catch
{
}
}
}
如何使用此代码:
- 您可以简单地调用
TestHelpers.TestAll()
来测试所有程序集、引用的程序集、间接引用的程序集等。这可能是您在CI中想要做的。
- 您可以调用
TestHelpers.TestAll(assembly)
来测试一个带有所有引用程序集的单个程序集。当您将测试分散到多个程序集中并/或者进行调试时,这可能非常有用。
- 您可以调用
new MyObject().TestAll()
来调用单个对象中的所有测试。这在调试时特别有帮助。
如果您像我一样使用应用程序域,则应为从文件夹动态加载的DLL创建单个应用程序域,并在其上使用TestAll。另外,如果您使用临时文件夹,您可能需要在测试之间清空它。这样,多个测试框架版本和多个测试就不会相互影响。特别是如果您的测试使用状态(例如静态变量),这可能是一个好习惯。有大量关于CreateInstanceAndUnwrap
的示例在线可以帮助您解决这个问题。
需要注意的一件事是,我使用委托而不是method.Invoke
。这基本上意味着您的异常对象不会被反射吞噬,这意味着您的调试器不会被打断。还要注意,我按名称检查属性,这意味着只要属性名称匹配,就可以与不同的框架一起使用。
希望对您有所帮助
/testcontainer
选项对您无效,请解释为什么它无效。当然,存在一些限制。最坏的情况是您需要将不同的输出 XML 合并成一个。 - jessehouwingvstest.console.exe
。请参见:https://msdn.microsoft.com/en-us/library/jj155796.aspx。在TFS XAML构建编辑器中,它被标识为“敏捷测试运行器”。如果需要,您可以直接从Jenkins调用vstest.console.exe。 - jessehouwing