单元测试的重构

时间:2011-04-04 11:32:02

标签: c# unit-testing

我在为业务逻辑层编写单元测试时遇到了一些麻烦,请指出正确的方向。任何意见,将不胜感激。

业务逻辑

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

实体

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 个答案:

答案 0 :(得分:4)

您在TitleLogic课程中创建DAL组件时,无法轻松测试业务逻辑。

我要做的第一件事是让TitleDAL实现ITitleDAL接口,并使TitleLogic类获取ITitleDAL接口的实例。

然后,在测试InsertTitle方法时,您可以进行以下测试:

  • 检查无效TitleEntity时是否会抛出EntityException
  • 当实体有效时,ITitleDAL.InsertTitle被调用。
  • 正确插入标题后,可以使用FindTitle方法
  • 找到它

在测试中,您需要创建一个模拟ITitleDAL实现(或使用mocking library为您创建一个),以便您的测试返回已知的预期数据不依赖于实际的DAL。

您可能还想考虑测试:

  • 如果InsertTitle上的ITitleDAL方法失败会怎样?

答案 1 :(得分:2)

您需要做的第一件事是考虑依赖注入。最简单的方法是按照

的方式为DAL实现一个接口
interface ITitleDAL
{
    TitleEntity InsertTitle(TitleEntity titleEntity);
    ICollection<TitleEntity> GetAllTitles();
}

然后让你的DAL层实现接口。

接下来,更改DAL的构造函数以接受实现该接口的对象...

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

然后创建一个也实现接口的DAL模拟版本,但返回静态数据。

之后,你需要做两件事。

1)使您的生产代码创建DAL实例并将其传递到业务层的构造函数中 2)使您的单元测试在模拟类的实例中传递给构造函数,并根据您编码的已知数据进行测试。

答案 2 :(得分:0)

您希望单独测试每种类型。也就是说,当您为业务对象编写测试时,您不希望被迫使用它所使用的DAL和帮助程序对象的代码(如XTime900Entities等)。

现在,所有这些类型都是彼此紧密耦合的,这是不必要的,从可测试性的角度来看是一个问题。也就是说,您的业务对象类的单元测试被强制耦合到您的数据访问层和实现。从长远来看,这不会起作用。它不会扩展到大型代码库,并且单元测试维护将在变化中飙升。

此外,您需要在此处考虑异常。例如,xTime900Entities.Dispose();如果在该方法的中间存在异常,则不会被调用。这意味着如果在InsertTitle期间发生意外情况,您的代码将泄漏资源。这是一个普遍的概念,但在这种情况下,这样的事情会更好:

XTime900Entities xTime900Entities = new XTime900Entities() {    //方法的其余部分 } // dispose自动调用此处,无论块中是否抛出异常

良好实践是注入依赖关系,依赖于抽象,隔离关注点,允许您单独进行测试。

相关问题