繁体   English   中英

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

[英]How to write my first Unit-test for Email-function

这仅仅是一个电子邮件发送应用程序的小部分,但我试图实现对应用程序的单元测试。 到目前为止,我已经阅读了一些有关单元测试的内容,并且我了解您应该只测试单个函数而不是整个类。 如何测试此代码?

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

好的,我认为可能令您失望的是,您尚未按照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的那一部分? 这实际上是完美的。

首先,您可以将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);

希望有帮助! :-)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM