Рефакторинг для юнит-тестирования - PullRequest
2 голосов
/ 04 апреля 2011

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

Бизнес-логика

public class TitleLogic
{
    private readonly TitleDAL titleDAL = new TitleDAL();
    private readonly List<TitleEntity> titleEntities;

    public TitleLogic()
    {
        titleEntities = titleDAL.GetAllTitles().ToList();
    }

    public TitleEntity InsertTitle(TitleEntity titleEntity)
    {
        if (!titleEntity.IsValid)
        {
            throw new EntityException<TitleEntity>("Invalid Title.", titleEntity);
        }

        titleEntity.TitleName.TrimSize(TitleEntity.TitleName_Length);

        var createdTitle = titleDAL.InsertTitle(titleEntity);

        titleEntities.Add(createdTitle);

        return createdTitle;
    }

    public TitleEntity FindTitle(string titleName)
    {
        return titleEntities.Find(p => p.TitleName == titleName);
    }
}

Уровень данных

public class TitleDAL
{
    private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    public TitleEntity InsertTitle(TitleEntity titleEntity)
    {
        XTime900Entities xTime900Entities = new XTime900Entities();

        //Find the next CodeId to use
        var titleCodeId = xTime900Entities.TITLEs.Max(p => p.TITLE_CODEID) + 1;

        TITLE title = new TITLE
        {
            TITLE_CODEID = (short) titleCodeId,
            TITLE_ACTIVE = Convert.ToInt16(titleEntity.TitleActive),
            TITLE_NAME = titleEntity.TitleName
        };

        xTime900Entities.TITLEs.InsertOnSubmit(title);
        xTime900Entities.SubmitChanges();
        logger.Debug("Inserted New Title CodeId: {0}", titleCodeId);
        xTime900Entities.Dispose();

        return titleEntity.Clone((short)titleCodeId);
    }

    public ICollection<TitleEntity> GetAllTitles()
    {
        logger.Debug("Retrieving List all Titles from XTime900 database.");
        List<TitleEntity> titleEntities = new List<TitleEntity>();

        using (XTime900Entities XTEntities = new XTime900Entities())
        {
            var titlesInDB = from p in XTEntities.TITLEs
                                  select p;

            foreach (var titlesInDb in titlesInDB)
            {
                TitleEntity genderEntity = new TitleEntity(titlesInDb.TITLE_CODEID)
                {
                    TitleActive = Convert.ToBoolean(titlesInDb.TITLE_ACTIVE),
                    TitleName = titlesInDb.TITLE_NAME
                };

                titleEntities.Add(genderEntity);
            }
        }

        logger.Debug("Found {0} Titles.", titleEntities.Count);
        return titleEntities;
    }
}

Entity

public class TitleEntity
{
    public const int TitleName_Length = 30;

    public short TitleCodeId { get; private set; }
    public bool TitleActive { get; set; }
    public string TitleName { get; set; }
    public bool IsValid
    {
        get
        {
            return !String.IsNullOrEmpty(TitleName);
        }
    }

    public TitleEntity()
    {
        this.TitleCodeId = -1;
    }
    public TitleEntity(short titleCodeId)
    {
        this.TitleCodeId = titleCodeId;
    }

    public TitleEntity Clone(short titleCodeId)
    {
        TitleEntity genderEntity = new TitleEntity(titleCodeId)
        {
            TitleActive = this.TitleActive,
            TitleName = this.TitleName
        };

        return genderEntity;
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLine(String.Format("TitleCodeId : {0}", TitleCodeId));
        sb.AppendLine(String.Format("TitleActive : {0}", TitleActive));
        sb.AppendLine(String.Format("TitleName : {0}", TitleName));
        return sb.ToString();
    }

    public static bool operator ==(TitleEntity x, TitleEntity y)
    {
        return (x.Equals(y));
    }

    public static bool operator !=(TitleEntity x, TitleEntity y)
    {
        return !(x.Equals(y));
    }

    public bool Equals(TitleEntity other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return other.TitleCodeId == TitleCodeId && other.TitleActive.Equals(TitleActive) && Equals(other.TitleName, TitleName);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        return obj.GetType() == typeof(TitleEntity) && Equals((TitleEntity)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var result = TitleCodeId.GetHashCode();
            result = (result * 397) ^ TitleActive.GetHashCode();
            result = (result * 397) ^ (TitleName != null ? TitleName.GetHashCode() : 0);
            return result;
        }
    }
}

Ответы [ 3 ]

4 голосов
/ 04 апреля 2011

Вы не можете легко протестировать свою бизнес-логику, поскольку создали компонент DAL внутри класса TitleLogic.

Первое, что я хотел бы сделать, это заставить TitleDAL реализовать интерфейс ITitleDAL и заставить класс TitleLogic взять экземпляр интерфейса ITitleDAL.

Затем, когда вы тестируете метод InsertTitle, у вас могут быть тесты, которые:

  • проверьте, что при выдаче недействительного TitleEntity выдается EntityException
  • Когда сущность действительна, вызывается ITitleDAL.InsertTitle.
  • Если заголовок вставлен правильно, его можно найти, используя метод FindTitle

В ваших тестах вам нужно будет создать фиктивную реализацию ITitleDAL (или использовать насмешливую библиотеку для ее создания), чтобы ваши тесты возвращали известные ожидаемые данные, не зависящие от фактического DAL. .

Вы также можете рассмотреть возможность тестирования:

  • Что произойдет, если метод InsertTitle в ITitleDAL завершится неудачей?
2 голосов
/ 04 апреля 2011

Первое, что вам нужно сделать, это подумать о внедрении зависимости. Самый простой способ для этого - реализовать интерфейс для DAL по принципу

interface ITitleDAL
{
    TitleEntity InsertTitle(TitleEntity titleEntity);
    ICollection<TitleEntity> GetAllTitles();
}

затем заставьте ваш уровень DAL реализовать интерфейс.

Затем измените конструктор для вашего DAL, чтобы он принимал объект, который реализует этот интерфейс ...

public TitleLogic(ITitleDAL myDAL)
{
    titleDAL = myDAL;
    titleEntities = titleDAL.GetAllTitles().ToList();
}

Затем создайте фиктивную версию вашего DAL, которая также реализует интерфейс, но возвращает статические данные.

После этого вам нужно сделать 2 вещи.

1) Заставьте свой производственный код создать экземпляр DAL и передать его в конструктор вашего бизнес-уровня.
2) Сделайте так, чтобы ваш модульный тест прошел в экземпляре вашего смоделированного класса для конструктора, и проверьте на соответствие известным данным, которые вы кодировали.

0 голосов
/ 04 апреля 2011

Вы хотите проверить каждый тип изолированно. То есть, когда вы пишете тесты для своих бизнес-объектов, вы не хотите, чтобы вас заставляли применять код для DAL и вспомогательных объектов, которые он использует (например, XTime900Entities и т. Д.).

В настоящее время все эти типы тесно связаны друг с другом, что не является необходимым и является проблемой с точки зрения тестируемости. То есть юнит-тест для вашего класса бизнес-объектов должен быть связан с вашим уровнем доступа к данным и реализацией. Это не будет работать в долгосрочной перспективе. Он не будет масштабироваться по большой базе кода, и обслуживание модульных тестов будет стремительно расти.

Кроме того, вам нужно принять во внимание исключения здесь. Например, xTime900Entities.Dispose (); не будет вызван, если в середине этого метода есть исключение. Это означает, что ваш код будет пропускать ресурсы, если во время InsertTitle произойдет что-то непредвиденное. Это общая концепция, но что-то вроде этого было бы лучше в этом случае:

XTime900Entities xTime900Entities = new XTime900Entities () { // остальная часть метода } // утилизировать вызываемый здесь автоматически, независимо от того, выброшено исключение в блоке или нет

Хорошей практикой является внедрение зависимостей, зависимостей от абстракций для изоляции проблем, что позволяет проводить тестирование изолированно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...