【问题标题】:How to write my first Unit-test for Email-function如何为电子邮件功能编写我的第一个单元测试
【发布时间】:2017-08-15 18:29:03
【问题描述】:

这只是电子邮件发送应用程序的一小部分,但我正在尝试为该应用程序实施单元测试。到目前为止,我已经阅读了一些关于单元测试的内容,我知道你应该只测试单个函数而不是整个类。如何测试这段代码?

private void Send(IEnumerable<Customer> customers, string body)
        {
            string titleOfEmail = "Welcome as a new customer at Company!";
            string ourEmailAddress = "info@company.com";
            int NumberOfRetriesOnError = 2;
            int DelayOnError = 10;

            foreach (var customer in customers)
            {


              for (int i = 0; i <= NumberOfRetriesOnError; ++i)
                {
                    try
                    {
                        Sender.Send(ourEmailAddress, customer.Email, titleOfEmail);
                        return;
                    }
                    catch (SmtpException e)
                    {
                        if (i < NumberOfRetriesOnError)
                            Thread.Sleep((i + 1) * DelayOnError);
                        else
                            Errors.Add(e.Message + customer); // Include customeremail
                    }
                }
}

编辑:其余信息可能不需要其余信息

public interface IMailSender
{
    void Send(string from, string to, string body);
}
sealed class NullMailSender : IMailSender
{
    void IMailSender.Send(string from, string to, string body)
    {

    }
}

sealed class SmtpMailSender : IMailSender
{
    void IMailSender.Send(string from, string to, string body)
    {
        System.Net.Mail.MailMessage mail = new System.Net.Mail.MailMessage();
        mail.From = new System.Net.Mail.MailAddress(from);
        System.Net.Mail.SmtpClient smtp = new System.Net.Mail.SmtpClient("yoursmtphost");
        mail.To.Add(to);
        mail.Body = body;
        smtp.Send(mail);

    }
}     

而这部分就是顶层函数的其余部分,整个类

public class SendingMail
{
    public List<string> Errors { get; } = new List<string>();

    public IEnumerable<Customer> Customers { get; set; }

    public IEnumerable<Order> Orders { get; set; }

    public IMailSender Sender { get; set; }

    public void SendWelcomeEmails()
    {
        var template = Resources.WelcomeEmailTemplate;
        Send(GetNewCustomers(), Resources.WelcomeEmailTemplate);
    }

    public void SendComeBackEmail()
    {
        var template = Resources.WelcomeEmailTemplate;
        var emailContent = String.Format(template);
        Send(GetCustomersWithoutRecentOrders(), Resources.ComeBackEmailTemplate);
    }

    private IEnumerable<Customer> GetNewCustomers()
    {
        var yesterday = DateTime.Now.Date.AddDays(-1);
        return Customers.Where(x => x.CreatedDateTime >= yesterday);
    }
    private IEnumerable<Customer> GetCustomersWithoutRecentOrders()
    {
        var oneMonthAgo = DateTime.Now.Date.AddMonths(-1);

        return Customers.Where(c => {
            var latestOrder = Orders
                .Where(o => o.CustomerEmail == c.Email)
                .OrderByDescending(o => o.OrderDatetime)
                .FirstOrDefault();

            return latestOrder != null
                && latestOrder.OrderDatetime < oneMonthAgo;
        });
    }

【问题讨论】:

    标签: c# .net unit-testing email


    【解决方案1】:

    好的,我认为可能会让您感到困惑的是您没有根据 SRP(单一责任原则)打破您的职能。

    这样说吧:你的职能目前负责什么?

    1. 设置电子邮件的标题和发件人地址
    2. 遍历每个客户
    3. 呼叫发件人
    4. 处理错误

    ...所以马上,您的单元测试将不得不尝试在函数调用中处理所有这些单独的事情。单元测试讨厌

    但是如果你以不同的方式编写你的函数呢?

    // needs System.Net.Mail for its MailMessage class - but you could write your own
    private void Send(IEnumerable<Customer> customers, string body)
    {
        foreach(Customer customer in customers)
            Send(customer, body);
    }
    private void Send(Customer customer, string body)
    {
        MailMessage message = GenerateMessage(customer, body);
        // your code for sending/retrying; omitted to keep the code short
    }
    private MailMessage GenerateMessage(Customer customer, string body)
    {
        string subject = "...";
        string from = "...";
        string to = customer.Email;
        MailMessage retVal = new MailMessage(from, to, subject, body);
        return retVal;
    }
    

    好的,现在看一下单元测试图片。突然,您可以在不发送电子邮件的情况下测试电子邮件消息的生成。只需创建一个虚拟客户,将其传递给 GenerateMessage 函数,然后验证它传回的结果(甚至不会发送电子邮件。)

    至于电子邮件本身?这有点难。你有两个选择。选项#1 是使用您的个人/组的电子邮件地址生成一个虚拟客户,并让它实际继续并向您发送电子邮件。不理想,但它会确保电子邮件代码有效。但另一种选择是修改 Sender 类(或包装它,如果你不控制它) - 类似于:

    interface ISender
    {
        void Send(/* the args you've got for your send function */);
    }
    class Sender : ISender
    {
        void Send(/* args */) { /* code */ }
    }
    class DummySender : ISender
    {
        void Send(/* args */)
        {
            // code to validate the unit tests of Send() are passing in correctly (and you won't have it actually send anything.)
        }
    }
    

    ... 然后,您无需直接调用“Sender.Send”,而是传入您计划用于执行发送的 ISender。常规代码将传入一个 Sender 实例;您的单元测试将通过 DummySender 的实例。

    编辑(帮助提供新信息)

    IMailSender 的那部分?这实际上是完美的。

    首先,您不只是直接连接到“Sender”,而是将一个 IMailSender 对象作为附加参数传递给您的 Send() 函数。然后你可以这样写:

    public class UnitTestDummySender : IMailSender
    {
        public string fromAddressIShouldGet;
        public string toAddressIShouldGet;
        public string bodyIShouldGet;
        public void Send(string from, string to, string body)
        {
            // check to make sure the arguments passed in match those properties
        }
    }
    

    看看这是如何工作的?除了调用 Send(),您还可以执行以下操作:

    // your regular code:
    Send(custList, body, myNormalSenderClass);
    
    // your unit test code:
    UnitTestDummySender faker = new UnitTestDummySender();
    // lines to set the faker properties to be what the email should work out to be
    Send(myFakeCustList, body, faker);
    

    希望对您有所帮助! :-)

    【讨论】:

    • 我有点新手,但正在努力学习,我完全理解了第一部分。但是当我们进入电子邮件部分时,我感到困惑,因为这与我的其他类文件非常相似。我不确定您是否需要它来理解我,但我为界面和您实现的其他类编辑了我的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-27
    • 1970-01-01
    • 1970-01-01
    • 2014-07-05
    • 2021-12-08
    • 1970-01-01
    相关资源
    最近更新 更多