如何编写我的第一个单元测试电子邮件功能

时间:2017-08-15 18:29:03

标签: c# .net unit-testing email

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

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

1 个答案:

答案 0 :(得分:2)

好吧,我认为可能会让你失望的是你没有按照SRP(单一责任原则)来解决你的职能。

这样说:你目前负责的是什么?

  1. 设置标题&amp;发件人地址
  2. 循环每个客户
  3. 致电发件人
  4. 处理错误
  5. ...所以马上,你的单元测试必须尝试在调用函数时处理所有这些单独的东西。单元测试讨厌那个。

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

    // 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);
    

    希望有所帮助! : - )