28

我编写了一些运行良好的 MSBuild 自定义任务,并在我们的 CruiseControl.NET 构建过程中使用。

我正在修改一个,并希望通过调用任务的 Execute() 方法对其进行单元测试。

但是,如果它遇到包含

Log.LogMessage("some message here");

它抛出一个 InvalidOperationException:

任务在初始化之前尝试记录。消息是...

有什么建议么?(过去,我在自定义任务中主要使用单元测试内部静态方法来避免此类问题。)

4

6 回答 6

28

您需要设置您正在调用的自定义任务的 .BuildEngine 属性。

您可以将其设置为当前任务使用的相同 BuildEngine 以无缝包含输出。

Task myCustomTask = new CustomTask();
myCustomTask.BuildEngine = this.BuildEngine;
myCustomTask.Execute();
于 2008-11-20T14:12:07.433 回答
15

我发现除非任务在 msbuild 中运行,否则日志实例不起作用,因此我通常将我对 Log 的调用包装起来,然后检查 BuildEngine 的值以确定我是否在 msbuild 中运行。如下。

蒂姆

private void LogFormat(string message, params object[] args)
{
    if (this.BuildEngine != null)
    {
        this.Log.LogMessage(message, args);
    }
    else
    {
        Console.WriteLine(message, args);
    }
}
于 2008-11-13T17:57:37.637 回答
10

@Kiff 对 mock/stub IBuildEngine 的评论是个好主意。这是我的 FakeBuildEngine。提供了 C# 和 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"; }
    }

}
于 2011-04-10T10:09:16.727 回答
7

如果你已经实现了 ITask 接口,你将不得不自己初始化 Log 类。

否则,您应该只从Microsoft.Build.Utilities.dll中的Task继承,它实现了ITask并为您做了大量的工作。

这是构建自定义任务的参考页面,它解释了很多。

构建自定义 MSBuild 任务参考

另外值得一看的是

如何调试自定义 MSBuild 任务

除此之外,您可以发布用于调用自定义任务的 MSBuild XML。代码本身显然是最有帮助的:-)

于 2008-11-08T12:12:28.637 回答
3

我有同样的问题。我通过存根构建引擎来解决它。像这样(AppSettings 是 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();
        }
    }
}
于 2012-03-04T22:14:31.953 回答
0

In assembly System.Webinnamespace System.Web.Compilation是一个以描述 Tim Murphy 的方式MockEngine实现的类。IBuildEngine interface

于 2016-09-01T07:47:27.817 回答