2008-11-04 17 views
25

He escrito algunas tareas personalizadas de MSBuild que funcionan bien y se usan en nuestro proceso de compilación CruiseControl.NET.Prueba unitaria Tarea personalizada MSBuild sin "Tarea intentó iniciar sesión antes de que se inicializara" error

Modifico uno y deseo probarlo unitariamente llamando al método Execute() de la tarea.

Sin embargo, si se encuentra con una línea que contiene

Log.LogMessage("some message here"); 

lanza una InvalidOperationException:

de tareas ha intentado iniciar sesión antes de que se ha inicializado. El mensaje fue ...

¿Alguna sugerencia? (En el pasado, tenía métodos estáticos internos mayormente probados en una unidad en mis tareas personalizadas para evitar tales problemas.)

+1

Acabo de encontrar esto en algunas tareas personalizadas para SO - ¡la respuesta de Branstar fue correcta! Simplemente configure BuildEngine en la tarea llamada. –

Respuesta

24

Debe establecer la propiedad .BuildEngine de la tarea personalizada que está llamando.

Puede configurarlo en el mismo BuildEngine que está utilizando su tarea actual para incluir la salida sin problemas.

Task myCustomTask = new CustomTask(); 
myCustomTask.BuildEngine = this.BuildEngine; 
myCustomTask.Execute(); 
+0

Esto era exactamente lo que necesitaba, ¡gracias! –

+12

Dado que la pregunta se realizó en el contexto de una prueba unitaria, también agregaría que alternativamente puede establecer la propiedad BuildEngine en un objeto simulacro que implemente la interfaz IBuildEngine. –

7

Si ha implementado la interfaz ITask, deberá inicializar la clase Log usted mismo.

De lo contrario sólo debe heredar de Tarea en Microsoft.Build.Utilities.dll que implementa ITask y hace una gran parte del trabajo de la pierna para usted.

Aquí está la página de referencia para crear una tarea personalizada, explica bastante.

Building a custom MSBuild task reference

También merece la pena ver

How to debug a custom MSBuild task

Aparte de eso, ¿podría enviar el XML MSBuild que está utilizando para llamar a su tarea personalizada. El código en sí sería obviamente la mejor ayuda :-)

+0

+1 ... buenos enlaces – alexandrul

14

He encontrado que la instancia de registro no funciona a menos que la tarea se ejecute dentro de msbuild, por lo que generalmente cierro mis llamadas a Log, luego verifico el valor de BuildEngine para determinar si estoy ejecutando dentro de msbuild. Como a continuación.

Tim

private void LogFormat(string message, params object[] args) 
{ 
    if (this.BuildEngine != null) 
    { 
     this.Log.LogMessage(message, args); 
    } 
    else 
    { 
     Console.WriteLine(message, args); 
    } 
} 
7

@Kiff comenta en mock/stub IBuildEngine es una buena idea. Aquí está mi FakeBuildEngine. Se proporcionan ejemplos de C# y VB.NET.

VB.NET

Imports System 
Imports System.Collections.Generic 
Imports Microsoft.Build.Framework 

Public Class FakeBuildEngine 
    Implements IBuildEngine 

    // It's just a test helper so public fields is fine. 
    Public LogErrorEvents As New List(Of BuildErrorEventArgs) 
    Public LogMessageEvents As New List(Of BuildMessageEventArgs) 
    Public LogCustomEvents As New List(Of CustomBuildEventArgs) 
    Public LogWarningEvents As New List(Of BuildWarningEventArgs) 

    Public Function BuildProjectFile(
     projectFileName As String, 
     targetNames() As String, 
     globalProperties As System.Collections.IDictionary, 
     targetOutputs As System.Collections.IDictionary) As Boolean 
     Implements IBuildEngine.BuildProjectFile 

     Throw New NotImplementedException 

    End Function 

    Public ReadOnly Property ColumnNumberOfTaskNode As Integer 
     Implements IBuildEngine.ColumnNumberOfTaskNode 
     Get 
      Return 0 
     End Get 
    End Property 

    Public ReadOnly Property ContinueOnError As Boolean 
     Implements IBuildEngine.ContinueOnError 
     Get 
      Throw New NotImplementedException 
     End Get 
    End Property 

    Public ReadOnly Property LineNumberOfTaskNode As Integer 
     Implements IBuildEngine.LineNumberOfTaskNode 
     Get 
      Return 0 
     End Get 
    End Property 

    Public Sub LogCustomEvent(e As CustomBuildEventArgs) 
     Implements IBuildEngine.LogCustomEvent 
     LogCustomEvents.Add(e) 
    End Sub 

    Public Sub LogErrorEvent(e As BuildErrorEventArgs) 
     Implements IBuildEngine.LogErrorEvent 
     LogErrorEvents.Add(e) 
    End Sub 

    Public Sub LogMessageEvent(e As BuildMessageEventArgs) 
     Implements IBuildEngine.LogMessageEvent 
     LogMessageEvents.Add(e) 
    End Sub 

    Public Sub LogWarningEvent(e As BuildWarningEventArgs) 
     Implements IBuildEngine.LogWarningEvent 
     LogWarningEvents.Add(e) 
    End Sub 

    Public ReadOnly Property ProjectFileOfTaskNode As String 
     Implements IBuildEngine.ProjectFileOfTaskNode 
     Get 
      Return "fake ProjectFileOfTaskNode" 
     End Get 
    End Property 

End Class 

C#

using System; 
using System.Collections.Generic; 
using Microsoft.Build.Framework; 

public class FakeBuildEngine : IBuildEngine 
{ 

    // It's just a test helper so public fields is fine. 
    public List<BuildErrorEventArgs> LogErrorEvents = new List<BuildErrorEventArgs>(); 

    public List<BuildMessageEventArgs> LogMessageEvents = 
     new List<BuildMessageEventArgs>(); 

    public List<CustomBuildEventArgs> LogCustomEvents = 
     new List<CustomBuildEventArgs>(); 

    public List<BuildWarningEventArgs> LogWarningEvents = 
     new List<BuildWarningEventArgs>(); 

    public bool BuildProjectFile(
     string projectFileName, string[] targetNames, 
     System.Collections.IDictionary globalProperties, 
     System.Collections.IDictionary targetOutputs) 
    { 
     throw new NotImplementedException(); 
    } 

    public int ColumnNumberOfTaskNode 
    { 
     get { return 0; } 
    } 

    public bool ContinueOnError 
    { 
     get 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    public int LineNumberOfTaskNode 
    { 
     get { return 0; } 
    } 

    public void LogCustomEvent(CustomBuildEventArgs e) 
    { 
     LogCustomEvents.Add(e); 
    } 

    public void LogErrorEvent(BuildErrorEventArgs e) 
    { 
     LogErrorEvents.Add(e); 
    } 

    public void LogMessageEvent(BuildMessageEventArgs e) 
    { 
     LogMessageEvents.Add(e); 
    } 

    public void LogWarningEvent(BuildWarningEventArgs e) 
    { 
     LogWarningEvents.Add(e); 
    } 

    public string ProjectFileOfTaskNode 
    { 
     get { return "fake ProjectFileOfTaskNode"; } 
    } 

} 
+3

Debería usar un marco de burla. En NSubstitute sería simplemente: var engine = Substitute.For (); –

+2

Correcto, si solo busca evitar la 'InvalidOperationException', no hay necesidad de una implementación concreta. Siempre que la instancia no sea nula, no verá la 'InvalidOperationException'. Para Moq, sería 'myCustomTask.BuildEngine = new Mock (). Object;'. Menos código para menos errores :) – mikegradek

+0

@perropicante True Moq podría haber sido utilizado. En ese momento tenía muchas pruebas que necesitaban IBuildEngine y una implementación concreta era más fácil de usar y hacía que las pruebas fueran más fáciles de leer. –

1

que tenían el mismo problema.Lo solucioné apagando el motor de construcción. Al igual que (AppSettings es el nombre de la tarea MSBUILD):

using Microsoft.Build.Framework; 
using NUnit.Framework; 
using Rhino.Mocks; 

namespace NameSpace 
{ 
    [TestFixture] 
    public class Tests 
    { 
     [Test] 
     public void Test() 
     { 
      MockRepository mock = new MockRepository(); 
      IBuildEngine engine = mock.Stub<IBuildEngine>(); 

      var appSettings = new AppSettings(); 
      appSettings.BuildEngine = engine; 
      appSettings.Execute(); 
     } 
    } 
} 
0

En el montaje System.Web en namespaceSystem.Web.Compilation es una clase que implementa MockEngineIBuildEngineinterface de una manera que se describe Tim Murphy.

+0

... en la casa que Jack construyó. –

Cuestiones relacionadas