当前位置:  开发笔记 > 编程语言 > 正文

单元测试MSBuild自定义任务没有"任务尝试在初始化之前记录"错误

如何解决《单元测试MSBuild自定义任务没有"任务尝试在初始化之前记录"错误》经验,为你挑选了4个好方法。

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

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

但是,如果遇到包含的行

Log.LogMessage("some message here");

它会抛出一个InvalidOperationException:

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

有什么建议?(过去我在自定义任务上主要经过单元测试的内部静态方法,以避免出现此类问题.)



1> Branstar..:

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

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

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


由于问题是在单元测试的上下文中,我还要补充一点,您也可以将BuildEngine属性设置为实现IBuildEngine接口的mock/stub对象.

2> Tim Bailey..:

我发现除非任务在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);
    }
}



3> Tim Murphy..:

@Kiff评论模拟/存根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 LogErrorEvents = new List();

    public List LogMessageEvents = 
        new List();

    public List LogCustomEvents = 
        new List();

    public List LogWarningEvents =
        new List();

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

}


应该只使用一个模拟框架.在NSubstitute中它只是:var engine = Substitute.For ();
是的,如果您只是想避免使用`InvalidOperationException`,则不需要具体的实现.只要实例不为null,就不会看到`InvalidOperationException`.对于Moq来说,它是`myCustomTask.BuildEngine = new Mock ().Object;`.更少的代码减少错误:)

4> evilhomer..:

如果您已实现接口ITask,则必须自己初始化Log类.

否则你应该继承Microsoft.Build.Utilities.dll中的Task,它实现了ITask并为你做了很多工作.

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

构建自定义MSBuild任务引用

另外值得一看的是

如何调试自定义MSBuild任务

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

推荐阅读
携手相约幸福
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有