NUnit无法找到程序集,但控制台应用程序可以找到

23
我有一个C#类,调用了从Matlab函数构建的.Net程序集。我可以从简单的C#控制台应用程序中调用此函数而没有任何问题。
然而,如果我尝试从NUnit运行单元测试,我会得到以下异常:
ClassLibrary1.Tests.UnitTests.TestPerformOptimization: System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation. ----> System.Exception : Error marshalling .NET object. 'Message: Unable to find assembly 'ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. Source: mscorlib HelpLink:
如果我尝试从独立的NUnit控制台、ReSharper的测试运行器或者尝试从Excel中调用该函数(使用Excel-DNA),我会得到相同的错误。
在调用我的编译Matlab组件时,我实际上将一个C#方法(在MWObjectArray对象中)打包起来并注入进去。我认为问题是发生在编译的Matlab组件尝试调用这个注入的方法时。
我找到的唯一解决方法是将包含被注入方法的类的副本放在NUnit测试运行器,ReSharper测试运行器或Excel的相同位置。然而,这并不是一个实际的解决方案,因为我需要将此应用程序安装到用户的机器上。另一个选项是将文件复制到我的%DEVPATH%,但由于同样的原因,我无法使用它。
有没有办法告诉Matlab组件在哪里找到我注入方法/类的程序集? 示例项目下载 可以在这里下载示例项目。只需按照zip文件中的README.txt文件中的说明操作即可。 更新1 通过修改我的类以在其构造函数中包含以下内容,我成功使我的单元测试识别我的程序集:
AppDomain.CurrentDomain.AssemblyResolve +=
                 (sender, args) => typeof(OptimizationFunction).Assembly;

然而,现在我遇到以下异常:
异常:System.Reflection.TargetInvocationException:已通过调用目标引发异常。 ---> System.Exception:错误编排 .NET 对象。'消息:无法加载文件或程序集“dotnetcli,Version=1.0.5488.33915,Culture=neutral,PublicKeyToken=da1231a838c93da4”或其其中之一的依赖项。需要具有强名称的程序集。(HRESULT 异常:0x80131044)源:mscorlib HelpLink:'在 dotnetcli.throwNetExceptionID(BaseMsgID* msgId) 位置 在 dotnetcli.DeployedDataConversion.GetMxArrayFromObject(Object data) 位置--- 内部异常堆栈跟踪的结尾--- 位置于 System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
因此,它现在无法解决dotnetclli.dll(请参见下面的Fusion日志/异常),据我所知,它应该只驻留在C:\Program Files (x86)\MATLAB\MATLAB Runtime\v85\bin\win32。
以下是 Fusion 日志的摘录:

=== 预绑定状态信息 ===

LOG: DisplayName = ClassLibrary1 (部分) WRN: 程序集名称:ClassLibrary1 | 域 ID:1 WRN: 当只提供程序集显示名称的一部分时,会发生部分绑定。 WRN: 这可能导致绑定器加载错误的程序集。 WRN: 建议为程序集提供完全指定的文本标识, WRN: 该标识由简单名称、版本、区域性和公钥令牌组成。 > WRN: 有关此问题的更多信息和常见解决方案,请参见白皮书http://go.microsoft.com/fwlink/?LinkId=109270。 LOG: Appbase = file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/ LOG: 初始 PrivatePath = NULL LOG: 动态 Base = NULL LOG: 缓存 Base = NULL LOG: AppName = nunit-agent-x86.exe 调用程序集:(未知)。

=== LOG: 此绑定在默认加载上下文中开始。 LOG: 使用应用程序配置文件: C:\Insight\TFS\Asg\ConsoleApplication4\packages\NUnit.Runners.2.6.3\tools\nunit-agent-x86.exe.Config LOG: 使用主机配置文件: LOG: 使用来自 C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config 的计算机配置文件。 LOG: 目前未将策略应用于此引用(私有、自定义、部分或基于位置的程序集绑定)。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication4/packages/NUnit.Runners.2.6.3/tools/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/ClassLibrary1/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/lib/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/lib/ClassLibrary1/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/addins/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/addins/ClassLibrary1/ClassLibrary1.DLL。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/ClassLibrary1.EXE。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/ClassLibrary1/ClassLibrary1.EXE。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/lib/ClassLibrary1.EXE。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/lib/ClassLibrary1/ClassLibrary1.EXE。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/addins/ClassLibrary1.EXE。 LOG: 尝试下载新 URL file:///C:/XXXXX/ConsoleApplication1/packages/NUnit.Runners.2.6.3/tools/addins/ClassLibrary1/ClassLibrary1.EXE。 LOG: 所有探测的 URL 均已尝试并失败。

警告:为程序集提供了部分绑定信息:我的控制台应用程序可以解析此程序集,但我的NUnit测试不能解析它?

更新2

我联系Matlab时收到了以下回复:

据我所知,这是默认的.NET反序列化行为。反序列化类的应用程序会在其自己的当前文件夹和GAC中查找程序集,如果找不到,则类似于以下情况(当您阅读此内容时,请将MATLAB替换为Nunit)。
您的示例并未涉及MATLAB的情况。MATLAB将类发送到APPDomains。您的示例没有这样做。
使用Fuslogvw查看绑定。您将看到无法解析依赖项的调用应用程序而不是MATLAB(在您的情况下是nunit-agent-x86.exe)。

3
在GUI NUnit中,我遇到了类似的行为:它无法找到对我的DLL工作必要的XML文件。这是因为NUnit默认使用“影子复制”。在这种情况下,NUnit会将我的DLL复制到某个临时目录并启动我的测试,但它不会复制我的XML文件。请查看“工具->设置...->测试加载程序->高级->影子复制->启用影子复制”选项。也许这也是你的情况。当我取消选中此选项时,一切正常工作,因为现在NUnit使用我的程序集而不是复制。 - Andrey Bushman
1
此选项值保存在 %AppData%\Local\NUnit\NUnitSettings.xml 文件中(查找名称为 "Options.TestLoader.ShadowCopyFiles"Setting 项)。 - Andrey Bushman
我已经尝试打开/关闭阴影副本,但没有任何运气。 - openshac
@MichalHosala 嗯,这可能有点棘手。在我工作的地方,我们在发送电子邮件、发布代码或在线编程方面受到限制。 - openshac
确保在从NUnit加载时,您的dll保持原始的PublicKeyToken。 我遇到了类似的加载问题,一些外部的dll需要具有特定版本的PublicKeyToken,在我的执行过程中,PublicKeyToken被设置为null... - silver
显示剩余3条评论
6个回答

4
请确保 ConsoleApplication4 的编译生成了所有所需的文件,并将它们放在可执行文件旁边(例如任何需要的程序集 DLL、运行时 DLL、.config 文件等)。
一旦你确定已经完成了以上步骤,请使用 --no-shadow-copy 命令行开关启动 NUnit,或者从 GUI 工具中设置它作为配置参数。正如 Bush 所说,NUnit 通常会将测试文件复制到单独的目录中,并可能留下重要文件。
如果 Matlab 创建的类需要某种特定的运行时,请确保它已正确安装在运行测试/使用 Excel/ReSharper 的计算机上。
在这个层面上,我不建议试图干预程序集解析过程:请总结你链接的文章内容,因为它是付费的,并尝试使用 NUnit 的 no-shadow-copy 设置。

你的命令行应用程序针对哪个.NET框架? - Alex Mazzariol
NUnit是否能正确识别4.5框架?您可以尝试使用/framework:net-4.5,并检查是否有任何不同。 - Alex Mazzariol
我已经没有任何想法了。但同时,我找到了这个链接,你可能已经知道了:http://www.mathworks.com/matlabcentral/answers/161601-calling-matlab-from-c-won-t-work-in-unit-tests 其中一些其他用户也遇到了同样的问题,但Mathworks还没有回答。如果你有支持计划,请尝试向他们询问这个问题 :/ - Alex Mazzariol
@alex.mazzariol 是的,我联系了Mathworks。我已经更新了我的问题,并加入了他们的建议。 - openshac
好的,阅读答案后,似乎是 NUnit / ReSharper / Excel 中的错误... 我会尝试一种不太正规的方法,手动添加 AssemblyResolver 事件处理程序和 dotnetcli.dll(来自 Matlab)作为引用,并将 "Local Copy" 属性设置为 True,这样 DLL 就始终跟随您的程序集。让我知道这是否改变了错误,虽然我在一个未知的(据我所知)道路上。另外一个问题是,从 Matlab 到纯 C# 移植您的优化方法真的不可行吗? - Alex Mazzariol
显示剩余4条评论

4

关于第一个问题:

  1. 运行 regedit
  2. 转到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Fusion
  3. 添加 DWORD ForceLog = 1
  4. 添加 DWORD LogFailures = 1
  5. 添加 DWORD LogResourceBinds = 1
  6. 添加 String LogPath = C:\log\ (最后的 \ 必须存在)
  7. 创建一个日志目录(例如 c:\logs)

它会记录你的应用程序查找程序集的位置以及它失败的原因。这可能意味着 NUnit 的工作目录与您的控制台应用程序使用的工作目录不同。

关于第二个错误:“需要强名称程序集”表示您尝试使用的库未签名。如果这是您自己的程序集,您只需在项目属性中使用 Visual Studio 的“签名程序集”功能即可。如果这不是您自己的程序集,您可以反编译它并使用强名称进行编译(使用此方法),但是第三方 dll 的提供者应该为您提供经过强名称签名的程序集。


我猜测这个问题来自于.NET CLR版本不兼容。 - g.pickardou
我已经添加了Fusion日志,但它无法找到ClasLibrary1.dll库,因为它正在寻找NUnit.Runner目录。显然,如果我将dll复制到此文件夹中,它就可以正常工作,但当我开始在构建服务器上运行这些测试时,这对我来说并不是一个真正的选择。 - openshac
@g.pickardou 这个版本不兼容的问题是从哪里来的?我该如何解决它?我已经尝试使用NUnit命令行选项显式设置框架。 - openshac
始终为您的类库签名 - 这只需要几秒钟,没有任何理由不这样做,但根据您发布的日志,强烈建议进行签名,否则您的 .net 将无法找到您的程序集。如果是这种情况,请考虑将显式 dll 路径添加到您的 nunit 项目中。 - Ryszard Fiński
@RyszardFiński,它正在寻找的文件位于C:\ Program Files(x86)\ MATLAB \ MATLAB Runtime \ v85 \ bin \ win32中。我的ConsoleApplication如何解决这个问题,但我的单元测试却不能? - openshac
显示剩余2条评论

4
我猜这个问题可能源于.NET CLR版本不兼容。一些客户端(我指的是调用你的程序集的客户端)加载了不同版本的CLE,则解析后的程序集可能与已加载的CLR不兼容。这可能会导致“未找到”错误,因为确实找不到兼容的程序集。
尝试检查/操作客户端版本(控制台应用程序)和服务器版本(你的程序集),并诊断发生了什么变化。
另外,请检查你的程序集引用在所有项目中是否勾选了“复制到输出目录”。

我尝试设置/framework:net-4.5,但仍然失败。我的ClassLibrary1、ConsoleApplication4和ClassLibrary1.Test项目都使用了.Net Framework 4.5版本。我正在使用项目引用(而非程序集引用),并将“复制本地”设置为“True”。 - openshac
你在单元测试项目中引用了ClassLibrary1.dll吗? - g.pickardou
是的,我有。没有它,我只会得到一个简单的“System.IO.FileNotFoundException”错误。 - openshac
不兼容通常会抛出BadImageFormatException异常。 - Ryszard Fiński

3
您需要学习在.NET加载时如何进行探测操作。以下是相关规则:https://msdn.microsoft.com/en-us/library/yx7xezcf(v=vs.110).aspx
除了将bin包含在您发布的程序集中,加载器决定是否可以加载支持DLL。如果按照上述规则无法找到它,则只能放弃。
我们曾经遇到过类似的问题,在这种情况下,某些开发人员告诉系统管理员,这里是让应用程序运行所需的Dlls。系统管理员按照说明操作后,应用程序在加载时失败。
原因:程序集中的子代引用了系统上不存在的dll,根据探测规则。
解决方案: 1)使(子代)dll包括其程序集的所有依赖项。 2)更改较新的主程序集以包括所有依赖项(如果无法更改子程序集)。 3)手动复制它们。 4)创建一个安装程序来完成所有工作。

好的,从融合日志中我可以看到一次正确的绑定到dotnet.cli.dll。一个日志文件说:“这个绑定在LoadFrom加载上下文中开始。”并且成功了,另一个日志则说:“这个绑定在默认加载上下文中开始。”并且失败了。我还尝试在配置文件中指定codeBase,但是这也没有成功。 - openshac
我不确定为什么,但人们很难理解加载程序失败。加载程序提示是基于 .NET 模块的 DLL 的清单发生的。这些可以使用 ILDASM 工具查看。清单中对“extern”的任何引用都意味着这是一个被认为是与程序集中的内容和已知内容“外部”的 DLL。如果该外部引用不在探测路径中,则加载程序将失败。首先确定哪个模块失败了。然后问自己:我如何解决加载程序的依赖项问题。 - JWP

2

这个程序集是否在全局程序集缓存 (GAC) 中或者任何可能会覆盖你认为正在被加载的程序集的地方?通常情况下,这是由于错误的程序集被加载所导致的问题,对我来说,这意味着我通常有一些在 GAC 中覆盖了我在 bin/Debug 中的版本。


融合日志报告说GAC查找失败了,所以我不确定是否是这种情况。 - openshac

0

我猜想这个问题来自于 .NET CLR 版本不兼容。有些客户端(我的意思是调用你的程序集的客户端)加载了不同版本的 CLE,然后解析的程序集可能与该加载的 CLR 不兼容。这可能会导致未找到错误,因为真正兼容的程序集没有找到。

尝试检查/操作客户端版本(控制台应用程序)和服务器版本(你的程序集),并诊断发生了什么变化。

还要检查你的程序集引用在所有项目中都选中了“复制到输出目录”。


如果使用的 .Net CLR 版本不兼容,那么如果我将 MatLab DLL 放置在 NUnit 测试运行器相同的位置,它为什么能够运行?当然,如果版本不兼容,它仍然会失败。您能够下载提供的示例项目吗? - openshac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接