为了完整起见,我经常想将测试作为控制台应用程序运行,只是因为我发现出于某种原因调试它要容易得多……多年来,我创建了一些小的测试助手来帮助我;我想你可以很容易地将它们与你的 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();
// Add exception handling here for your CI solution.
var del = (Action)meth.CreateDelegate(typeof(Action), o);
del();
// NOTE: Don't use meth.Invoke(o, new object[0]); ! It'll eat your exception!
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();
// Collect all assemblies that are related
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"))))
{
// Add exception handling here for your CI solution.
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 && // Ignore GAC
reference.FullName != null &&
!reference.FullName.StartsWith("ms") && // mscorlib and other microsoft stuff
!reference.FullName.StartsWith("vshost") && // visual studio host process
!reference.FullName.StartsWith("System")) // System libraries
{
if (visited.Add(reference.GetName())) // We don't want to test assemblies twice
{
todo.Push(reference); // Queue assembly for processing
}
}
}
catch
{
// Perhaps log something here... I currently don't because I don't care...
}
}
}
如何使用此代码:
- 您可以简单地调用
TestHelpers.TestAll() 来测试所有程序集、引用的程序集、间接引用的程序集等。这可能是您在CI 中想要做的。
- 您可以调用
TestHelpers.TestAll(assembly) 来测试包含所有引用程序集的单个程序集。这在您跨多个程序集拆分测试和/或调试时非常有用。
- 您可以调用
new MyObject().TestAll() 来调用单个对象中的所有测试。这在调试时特别有用。
如果您像我一样使用 appdomains,您应该为从文件夹动态加载的 DLL 创建单个 appdomain,并在其上使用 TestAll。此外,如果您使用临时文件夹,您可能希望在测试之间清空该文件夹。这样,多个测试框架版本和多个测试将不会相互交互。特别是如果您的测试使用状态(例如静态变量),这可能是一个好习惯。 CreateInstanceAndUnwrap 网上有大量的例子可以帮助你解决这个问题。
需要注意的一点是,我使用委托而不是 method.Invoke。这基本上意味着您的异常对象不会被反射吃掉,这意味着您的调试器不会被破坏。另请注意,我按名称检查属性,这意味着这将适用于不同的框架 - 只要属性名称匹配。
HTH