2009-10-14 117 views
0

我希望我的应用程序捕获异常并在生产环境中运行时发送电子邮件,但是当我运行我的MSTest单元测试时,我希望它能抛出异常,因此我可以进行调试。我现在的代码总是通过电子邮件发送例外。有什么方法可以知道当前进程是否由单元测试调用?有什么方法可以确定它是否是测试(.NET)?

我能想到的一种方法是测试Enviroment.CurrentDirectory和类似的变量。有更好的吗?

回答

2

这是依赖注入或服务定位器的经典用例。不要硬连线你的应用程序来发送电子邮件,让你的应用程序从服务定位器获得一个通知服务,并让它调用它。在生产中,配置服务定位器以返回电子邮件发送服务;在测试环境中,将其配置为返回不执行任何操作的服务,或者将通知添加到列表或其他任何内容。

你不需要在这里完整的重击依赖注入:一个非常简单的服务定位器就足够了。同样,可以通过测试夹具代码来完成测试通知服务的注入。这里是一个非常简单的例子:

public static class ServiceLocator 
{ 
    private static INotificationService _notificationService = new EmailNotificationService(); 

    public static INotificationService NotificationService 
    { 
    get { return _notificationService; } 
    } 

    // For test use only 
    public static void SetNotificationService(INotificationService notificationService) 
    { 
    _notificationService = notificationService; 
    } 
} 

(一个真正的服务定位器能够提供一个更加灵活的接口,沿着IServiceContainer/IServiceProvider的的线条,让你可以模拟出多种不同的服务,这仅仅是为了说明这个想法。)

然后在您的测试代码:

[SetUp] 
public void Setup() 
{ 
    ServiceLocator.NotificationService = new DiscardingService(); 
} 

(这里使用对于被每个测试之前运行的方法NUnit的术语 - 不知道的MSTest相当于是什么)

这样做的一个好处是您现在可以测试是否正在发送正确的通知:让您的测试通知服务捕获列表中的通知,并且您可以对该列表进行断言以验证通知是否正在发送并且正确。

再一次请注意,这不是DI或服务定位器的完整解释,我的示例代码绝不是最好的方法。

+0

服务定位器确实是一个很好的解决方案。 – 2009-10-14 20:35:20

+3

krystan:我不喜欢它们,因为它们隐藏了依赖关系,并通过“获取此服务,设置此服务”调用了应用程序代码。基于构造器的DI意味着如果依赖关系不满足,应用程序甚至不会构建。使用DI,可以轻松查看任何单独的代码,并了解它的依赖关系。 – 2009-10-14 20:50:44

+0

马克:公平点。我没有详细讨论DI的原因是它比简单的服务定位器方法需要更多的解释,并且基于构造器的DI可能需要比服务定位器更多地重构原始发布者的应用程序。但肯定有一个情况是,随着情景变得更加复杂,正确的DI可能会更容易维护。 – itowlson 2009-10-14 21:16:36

1

您可以使用编译指令(如#IF DEBUG)在测试时执行一些代码片段,并在发布模式下编译时执行另一个代码集;

另一种方法可以写成不同的TraceListeners,所以你可以用纯文本记录你的异常,或者通过在.config文件中设置发送你的电子邮件。

+0

马萨任何Emailsender类中, valeu pela dica – 2009-10-14 19:58:45

+0

@Jader:écurintia – 2009-10-14 20:00:01

+0

该方法不必要地混淆了代码。最好的做法是嘲笑电子邮件发件人。 您可以在http://en.wikipedia.org/wiki/Mock_object – Vitaliy 2009-10-15 17:26:58

3

您也可以在您的App.Config上设置一个标志,以便电子邮件例程验证它是否应发送这些电子邮件,并在您的测试套件中将该标志设置为false。

+0

阅读更多内容是的,但我的测试套装app.config是一个到我的应用程序app.config的软链接。我将不得不复制它=)不是一个问题。 – 2009-10-14 20:04:31

+0

SódáBrasil nessa thread – 2009-10-14 20:05:16

0

那么,基本上你想要做的是在测试过程中模拟邮件发送。 我假设你有一个类,负责发送电子邮件,你的代码看起来是这样的:

.. 
.. 
catch(Exception e) 
{ 
    EmailSender.Send(e); 
} 

一个好的单元测试应该隔离,这意味着执行代码不应该跨阶级界限,更不用说发送消息给其他进程!

我建议你阅读网上有关Mocking和Mocking框架的材料。 我个人使用RhinoMock框架。

1

你可以做一些真正的EVIL就像使用log4net和我们的IsDebug标志。这当然是疯狂的。

一个更好的方法是注入任何发送电子邮件到你的类,当你运行单元测试通过模拟对象这(moq)这样的电子邮件将不会发送代码时运行测试条件。

有这样做的

起订量 犀牛嘲笑

这几个不错的框架是我的两个最爱。

当我谈到注入我的意思是这样

public class Foo 
{ 
    private Emailer _emailer; 

    public Foo(Emailer mailer) 
    { 
     _emailer = mailer; 
    } 

    public void SomeMethod() 
    { 
     ... 
     try 
     { 
      ... 
     } 
     catch(SomeException ex) 
     { 
      _emailer.SendEmail(ex); 
     } 
     finally 
     {} 
    } 
} 

的框架基本上是让你在一定条件下的对象基本上是假的传递,然后可以将这些对象speicify行为和使用断言等在nunit。

但是,要直接回答你的问题,你可以在你的配置文件中使用一个标志,但是这不是一个整洁或有用的标志。

0

如果您更详细地描述您的特定场景,这将有所帮助。

这听起来不像你正在运行单元测试,如果他们发送电子邮件。代码单元不应该被控制,或者确实具有任何具体错误报告类型的知识,除非它们可以在应用程序级别轻松配置。

这听起来像你试图做的是破解一些设置不正确的东西。如果问题不严重并且继续是绝对安全的,那么您应该使用记录器,然后适当地对错误做出响应。如果你仍然需要这个功能(如果你没有配置log4net,它不会做任何事情,因此你的测试不会发送任何邮件),那么可能会有一个log4net appender发送邮件。

这不会影响可测试性,因为您可以输入错误条件,然后检查是否已采取适当的响应(例如,“如果我无法读取配置文件,则返回默认值”)。

如果您检测到致命错误,那么您应该查看应用程序顶级(即刚启动后)创建异常处理程序。您可以通过为AppDomain.UnhandledException事件配置处理程序来实现此目的。这样,如果某个异常未处理,您仍然可以正确地捕获异常,通过异常处理程序发送电子邮件,然后以优雅的方式终止应用程序。

您只会在正常的应用程序运行开始时安装异常处理程序。除了最初的靴子搭配/布线阶段,您的应用程序不知道它。你的代码单元不知道,你可以像往常一样测试。

0

快速的方法可能是...

public static class Tester 
{ 
    public static bool InUnitTest 
    { 
     get 
     { 
      return Environment.StackTrace.IndexOf("Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestRunner") > 0; 
     } 
    } 
} 

然后在你的应用程序,你可以只

catch (Exception e) 
{ 
    if (!Tester.InUnitTest) 
    { 
     ... // send mail 
    } 
} 

您也实现您可能使用所以你不需要担心改变每一个catch块

+0

我不会乱丢我的代码,像这样的东西。使用配置文件来定义是否发送电子邮件。 – Fadeproof 2009-10-29 13:27:36

相关问题