Сбой теста NUnit при работе через CC.NET - PullRequest
1 голос
/ 22 февраля 2009

Решение этой ошибки ускользнуло от меня уже несколько дней, и пришло время обратиться за помощью. Короткая версия: у меня есть модульное тестирование, которое не проходит на сервере сборки, но нет другой среды.

Метод, который я тестирую, является методом расширения для ILog в log4net. Цель этого метода расширения - сделать журнал отладки текущего метода, когда он вызывается, и я использую его для отладки. Код для этого довольно прост.

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);
    }
}

В этом методе я проверяю, включен ли режим отладки, потому что я не хочу делать StackTrace, если ничего не должно регистрироваться (из-за проблем с производительностью). Когда я протестирую этот метод, я буду использовать 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 для выполнения теста.

Однако мой сервер сборки не проходит этот тест, когда он проходит через NAnt, который выполняется из CruiseControl.NET. Когда я запускаю его вручную с помощью 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?

С этой ошибкой я ничего не получаю, так как не могу воссоздать ее на своей машине для разработки. Я рад за все, что могу получить.

Спасибо!

Микаэль Лундин

Ответы [ 3 ]

2 голосов
/ 22 февраля 2009

Похоже, что CallingMethod был оптимизирован на сервере сборки. Когда вы повторили тест вручную, действительно ли вы использовали точно такую ​​же сборку?

0 голосов
/ 22 февраля 2009

Я решил это.

Удалил CallingMethod из моего кода и позволил тестам напрямую вызывать SUT. Это делает тестовый код немного уродливым, но работает.

До сих пор не знаю, почему CallingMethod изменил свое имя при работе через CC.NET. Я думаю, что это будет для кого-то еще, чтобы выяснить.

0 голосов
/ 22 февраля 2009

То, что 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.

Это задача cc.net, которую я использую для выполнения сценария сборки NAnt.

<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 не может выбрать оттуда конфигурацию оптимизации?

Спасибо за ваш вклад! Микаэль Лундин

...