通过CC.NET运行时失败的NUnit测试

问题描述:

此错误的解决方案已经逃脱了几天,现在是时候来到这里寻求帮助。简短的版本是,我有一个单元测试,在构建服务器上失败,但没有其他环境。通过CC.NET运行时失败的NUnit测试

我正在测试的方法是log4net中ILog的扩展方法。这个扩展方法的目的是在调用当前方法的时候创建一个调试日志,并将其用于调试。执行此操作的代码非常简单。

public static void MethodHead(this ILog log, params object[] parameters) 
{ 
    /* Assert */ 
    log.AssertNonNull(); 

    /* Since this is an expensive operation, don't do it if Debug is not enabled */ 
    if (log.IsDebugEnabled) 
    { 
     StackTrace stackTrace = new StackTrace(); 

     /* Get calling method */ 
     MethodBase method = stackTrace.GetFrame(1).GetMethod(); 

     string logMessage = string.Format("{0}.{1}({2})", method.DeclaringType.Name, method.Name, parameters.ToDelimitedString(", ")); 
     log.Debug(logMessage); 
    } 
} 

在此方法中我检查调试模式下启用,因为我不想做的堆栈跟踪,如果没有应该得到记录(因为性能问题)。当我测试这种方法时,我将使用Rhino Mocks来模拟ILog接口并让IsDebugEnabled返回true。

请考虑以下NUnit测试方法。

[Test(Description = "Verify that MethodHead extension method will log with calling class.method(arguments)")] 
public void MethodHeadShouldLogCurrentMethodNameWithArguments() 
{ 
    /* Setup */ 
    MockRepository mocks = new MockRepository(); 
    ILog log = mocks.CreateMock<ILog>(); 
    string[] arguments = new string[] { "CAT", "IN", "A", "HAT" }; 

    string logMessage = string.Format("{0}.{1}({2})", 
     "MethodHeadTest", "CallingMethod", arguments.ToDelimitedString(", ")); 

    With.Mocks(mocks).Expecting(delegate 
    { 
     /* Record */ 
     Expect.Call(log.IsDebugEnabled).Return(true); 
     Expect.Call(delegate { log.Debug(logMessage); }); 
    }) 
    .Verify(delegate 
    { 
     /* Verify */ 
     CallingMethod(log, arguments); 
    }); 
} 

private void CallingMethod(ILog log, params object[] arguments) 
{ 
    log.MethodHead(arguments); 
} 

这在我的开发环境Visual Studio 2008中使用TestDriven.NET可以很好地执行。如果我通过nunit-console.exe或nunit-gui运行测试,它确实执行得很好。如果我使用我的NAnt脚本执行测试,它甚至会运行良好。

但是,我的构建服务器在通过从CruiseControl.NET执行的NAnt运行时未能通过此测试。当我在构建服务器上使用nunit-console.exe手动运行它时,它会成功。

错误和堆栈跟踪如下。

Rhino.Mocks.Exceptions.ExpectationViolationException : ILog.Debug("**<>c__DisplayClass8.<MethodHeadShouldLogCurrentMethodNameWithArguments>b__5**(CAT, IN, A, HAT)"); Expected #0, Actual #1. 
ILog.Debug("MethodHeadTest.CallingMethod(CAT, IN, A, HAT)"); Expected #1, Actual #0. 

at Rhino.Mocks.MethodRecorders.UnorderedMethodRecorder.DoGetRecordedExpectation(IInvocation invocation, Object proxy, MethodInfo method, Object[] args) 
at Rhino.Mocks.MethodRecorders.MethodRecorderBase.GetRecordedExpectation(IInvocation invocation, Object proxy, MethodInfo method, Object[] args) 
at Rhino.Mocks.Impl.ReplayMockState.DoMethodCall(IInvocation invocation, MethodInfo method, Object[] args) 
at Rhino.Mocks.Impl.ReplayMockState.MethodCall(IInvocation invocation, MethodInfo method, Object[] args) 
at Rhino.Mocks.MockRepository.MethodCall(IInvocation invocation, Object proxy, MethodInfo method, Object[] args) 
at Rhino.Mocks.Impl.RhinoInterceptor.Intercept(IInvocation invocation) 
at Castle.DynamicProxy.AbstractInvocation.Proceed() 
at ILogProxy86e676a4761d4509b43a354c1aba33ed.Debug(Object message) 
at Vanilla.Extensions.LogExtensions.MethodHead(ILog log, Object[] parameters) in d:\Build\Mint\WorkingDirectory\Source\Main\Vanilla\Extensions\LogExtensions.cs:line 42 
at Vanilla.UnitTests.Extensions.LogExtensions.MethodHeadTest.<>c__DisplayClass8.<MethodHeadShouldLogCurrentMethodNameWithArguments>b__5() in d:\Build\Mint\WorkingDirectory\Source\Test\Vanilla.UnitTests\Extensions\LogExtensions\MethodHeadTest.cs:line 99 
at Rhino.Mocks.With.FluentMocker.Verify(Proc methodCallsToBeVerified) 
at Vanilla.UnitTests.Extensions.LogExtensions.MethodHeadTest.MethodHeadShouldLogCurrentMethodNameWithArguments() in d:\Build\Mint\WorkingDirectory\Source\Test\Vanilla.UnitTests\Extensions\LogExtensions\MethodHeadTest.cs:line 90 

所以问题是,构建服务器认为这个方法有另一个(动态?)名称。或者更确切地说,是Rhino Mocks做出了这个假设?

由于我无法在我的开发机器上重新创建,因此我无法获得此错误的任何地方。我很高兴能够获得所有输入。

谢谢!

的Mikael Lundin的

貌似CallingMethod是在构建服务器优化掉。当你手动重复测试时,你真的使用完全相同的程序集吗?

CallingMethod可能会被优化掉,这是我没有想到的。让我对此进行一些更多的测试。

首先,我在构建服务器上手动调用nant。

"C:\Program Files\nant-0.86-beta1\bin\nant.exe" test -D:nunit.exe.path="C:\\Program Files\\NUnit 2.4.8\bin\\" -D:Artifact.Output.Path="D:\\Build\\Mint\\Artifacts\\" -D:msbuild.logger="C:\\Program Files\\CruiseControl.NET\\server\\ThoughtWorks.CruiseControl.MSBuild.dll" -D:fxcop.exe.path="C:\\Program Files\\Microsoft FxCop 1.36\\" 

这工作正常,测试不会失败!我转到生成的二进制文件并手动执行NUnit。

D:\Build\Mint\WorkingDirectory\Source\Test\Vanilla.UnitTests\bin\Debug>"C:\Program Files\NUnit 2.4.8\bin\nunit-console.exe" Vanilla.UnitTests.dll 
NUnit version 2.4.8 
Copyright (C) 2002-2007 Charlie Poole. 
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov. 
Copyright (C) 2000-2002 Philip Craig. 
All Rights Reserved. 

Runtime Environment - 
    OS Version: Microsoft Windows NT 5.2.3790 Service Pack 2 
    CLR Version: 2.0.50727.3082 (Net 2.0.50727.3082) 

.............................................................. 
Tests run: 62, Failures: 0, Not run: 0, Time: 7.891 seconds 

它的工作原理应该如此。但是当我通过CC.NET强制构建测试失败时,就像我上面显示的那样。然后,如果我选择构建服务器生成的二进制文件,并对那些成功运行的应用程序运行NUnit,

因此,二进制文件不会改变,但测试成功/失败,取决于NAnt是通过命令行还是CC.NET运行。

这是我用来执行我的NAnt构建脚本的cc.net任务。

<nant> 
    <executable>C:\Program Files\nant-0.86-beta1\bin\nant.exe</executable> 
    <buildArgs>-D:nunit.exe.path="C:\\Program Files\\NUnit 2.4.8\bin\\" -D:Artifact.Output.Path="D:\\Build\\Mint\\Artifacts\\" -D:msbuild.logger="C:\\Program Files\\CruiseControl.NET\\server\\ThoughtWorks.CruiseControl.MSBuild.dll" -D:fxcop.exe.path="C:\\Program Files\\Microsoft FxCop 1.36\\"</buildArgs> 
    <nologo>true</nologo> 
    <buildFile>Mint.build</buildFile> 
    <targetList> 
     <target>clean</target> 
     <target>build</target> 
     <target>test</target> 
     <target>staticAnalysis</target> 
    </targetList> 
    <buildTimeoutSeconds>1200</buildTimeoutSeconds> 
</nant> 

在我的构建脚本中执行NUnit的任务有点麻烦。

<!-- Run all tests --> 
<target name="test" description="Run NUnit tests" depends="build"> 
    <property name="Failed.Test.Count" value="0"/> 

    <!-- Test Vanilla --> 
    <property name="Test.Name" value="Vanilla.UnitTests" /> 
    <call target="runCurrentTest" /> 

    <fail if="${int::parse(Failed.Test.Count)>0}" message="Failures reported in unit tests" /> 
</target> 

<!-- Utility method to run tests --> 
<target name="runCurrentTest">  
    <exec program="${nunit.exe.path}nunit-console.exe" 
     failonerror="false" 
     resultproperty="Test.Result" 
     verbose="true"> 
     <arg value="${Test.Path + Test.Name + '\bin\Debug\' + Test.Name}.dll" /> 
     <arg value="/xml:${Artifact.Output.Path + Test.Name}-nunit-results.xml" /> 
     <arg value="/nologo" /> 
    </exec> 
    <property name="Failed.Test.Count" value="${int::parse(Test.Result) + int::parse(Failed.Test.Count)}"/> 
</target> 

我可以明确地把优化标志放在msbuild exec调用,以确定它不是这样的问题吗?我确实将编译指向了csproj文件。 msbuild不应该从那里选择优化配置吗?

谢谢你的所有输入! Mikael Lundin

我解决了它。

从我的代码中删除了CallingMethod,并让测试直接调用SUT。它使测试代码看起来有点丑陋,但它工作。

仍然不知道为什么CallingMethod在通过CC.NET运行时更改了名称。我想这将是为了别人弄清楚。