Модульное тестирование доменных объектов - PullRequest
3 голосов
/ 06 января 2010

У нас есть требование добавить напоминание о событии, когда пользователь вводит свой адрес электронной почты на странице события. Событие - это еще один объект домена. Нашей первоначальной мыслью было создать объект домена Customer и связанный с ним CustomerService:

public class CustomerService {
    public void AddEventReminder(string emailAddress, int eventId) {
       var customer = new Customer(emailAddress);
       customer.AddEmailReminder(eventId);
    }
}

Как мы можем проверить в модульном тесте, что метод AddEmailReminder действительно был вызван для нового клиента?

Мои мысли:

  1. Используйте фабрику, чтобы создать клиента. Это пахнет, потому что я думал, что вы должны были использовать только фабрику, где была некоторая сложность в создании объекта.
  2. Неверный код. Может быть, есть лучший способ сделать это?
  3. Магическая магия.

В отдельном примечании (может быть, это связано), как мы решаем, какой здесь совокупный корень? Мы произвольно решили, что клиент, но это может быть событие. Я читал и понимаю статьи о совокупных корнях, но в этом сценарии неясно.

Ответы [ 2 ]

6 голосов
/ 06 января 2010

В подобных случаях я бы создал защищенный метод в службе, которая создает клиента, в тесте переопределил этот метод анонимным внутренним классом и заставил его вернуть фиктивный объект Customer. Затем вы можете проверить на фиктивном объекте Customer, что был вызван AddEmailReminder. Что-то вроде:

public class CustomerService {
    public void AddEventReminder(string emailAddress, int eventId) {
       var customer = createCustomer(emailAddress);
       customer.AddEmailReminder(eventId);
    }

    protected Customer createCustomer(string emailAddress) {
       return new Customer(emailAddress);
    }
}

и в тесте (предположим, что знания C # ограничены, но это должно проиллюстрировать это):

void testCustomerCreation() {
    /* final? */ Customer mockCustomer = new Customer("email");
    CustomerService customerService = new CustomerService() {
       protected Customer createCustomer(string emailAddress) {
           return mockCustomer;
       }            
    };

    customerService.AddEventReminder("email", 14);

    assertEquals(mockCustomer.EventReminder() /* ? */, 14);
}
2 голосов
/ 07 января 2010

Мысли об API CustomerService

Есть ли какие-либо конкретные причины, по которым вы решили инкапсулировать эту операцию в CustomerService? Это выглядит немного Анемия для меня. Может быть, вместо этого он может быть инкапсулирован непосредственно на Клиента?

Возможно, вы упустили что-то из примера кода CustomerService для упрощения ...

Однако, если необходимо, изменение подписи для экземпляра Customer решает проблему:

public void AddEventReminder(Customer customer, int eventId)

но опять же, Int32 вряд ли можно квалифицировать как Domain Object, поэтому подпись должна действительно быть

public void AddEventReminder(Customer customer, Event event)

Вопрос теперь в том, добавляет ли этот метод какое-либо значение вообще?

Что такое совокупный корень?

Ни один из них, я бы подумал. Совокупный корень означает, что вы управляете потомками только через корень, и в этом случае это не имеет смысла.

Рассмотрим варианты:

Если вы сделаете Event корневым, это будет означать, что у вас не может быть CustomerRepository, и единственный способ получить, отредактировать и сохранить Customer будет через Event. Это звучит очень неправильно для меня.

Если вы сделаете Клиента корнем, у вас может не быть EventRepository, и единственный способ получить, отредактировать и сохранить Событие будет через определенного Клиента. Это звучит так же неправильно для меня.

Единственная оставшаяся возможность - это то, что они являются отдельными корнями. Это также означает, что они слабо связаны друг с другом, и вам понадобится какая-то доменная служба для поиска событий для клиента или клиентов для события.

...