代码之家  ›  专栏  ›  技术社区  ›  alexn

那些单元测试可以吗?

  •  4
  • alexn  · 技术社区  · 15 年前

    我试图掌握测试驱动开发,我想知道那些单元测试是否可以。我有一个这样的界面:

    public interface IEntryRepository
    {
        IEnumerable<Entry> FetchAll();
        Entry Fetch(int id);
        void Add(Entry entry);
        void Delete(Entry entry);
    }
    

    然后这个实现接口的类:

    public class EntryRepository : IEntryRepository
    {
        public List<Entry> Entries {get; set; }
    
        public EntryRepository()
        {
            Entries = new List<Entry>();
        }
    
        public IEnumerable<Entry> FetchAll()
        {
            throw new NotImplementedException();
        }
    
        public Entry Fetch(int id)
        {
            return Entries.SingleOrDefault(e => e.ID == id);
        }
    
        public void Add(Entry entry)
        {
            Entries.Add(entry);
        }
    
        public void Delete(Entry entry)
        {
            Entries.Remove(entry);
        }
    }
    

    这些是我迄今为止编写的单元测试,它们是否正常,或者我应该做些不同的事情?我是不是应该嘲弄入口报告?

    [TestClass]
    public class EntryRepositoryTests
    {
        private EntryRepository rep;
    
        public EntryRepositoryTests()
        {
            rep = new EntryRepository();
        }
    
        [TestMethod]
        public void TestAddEntry()
        {
            Entry e = new Entry { ID = 1, Date = DateTime.Now, Task = "Testing" };
            rep.Add(e);
    
            Assert.AreEqual(1, rep.Entries.Count, "Add entry failed");
        }
    
        [TestMethod]
        public void TestRemoveEntry()
        {
            Entry e = new Entry { ID = 1, Date = DateTime.Now, Task = "Testing" };
            rep.Add(e);
    
            rep.Delete(e);
            Assert.AreEqual(null, rep.Entries.SingleOrDefault(i => i.ID == 1), "Delete entry failed");
        }
    
        [TestMethod]
        public void TestFetchEntry()
        {
            Entry e = new Entry { ID = 2, Date = DateTime.Now, Task = "Testing" };
            rep.Add(e);
    
            Assert.AreEqual(2, rep.Fetch(2).ID, "Fetch entry failed");
        }
    }
    

    谢谢!

    8 回复  |  直到 15 年前
        1
  •  7
  •   Steven Evers    15 年前

    就在我头顶上…

    虽然您对add的测试实际上只测试框架:

    • 你加了一个,很好
    • 加很多东西怎么样 (我的意思是,数量太多了——容器添加的n个条目的值是多少?)
    • 不添加项目怎么样?(空项)
    • 如果您向列表中添加项目,它们是按特定顺序排列的吗? 他们应该是吗?

    你的回信也是这样:

    • 如果x>rep.count,那么fetch(x)中会发生什么?
    • 如果x<0会发生什么?
    • 如果代表是空的,会发生什么?
    • X是否符合性能要求(它是什么算法) 复杂性?只有一个入口,什么时候在范围内? 有大量的条目?

    这本书有一个很好的清单 Pragmatic Unit Testing (好书,强烈推荐)

    • 结果正确吗?
    • 所有的边界条件都正确吗
      • 符合预期格式
      • 订购正确
      • 在合理范围内
      • 它是否引用任何外部依赖项
      • 基数是否正确?(正确的值数目)
      • 是否在正确的时间内完成(真实或相对)
    • 你能查查反向关系吗
    • 你能用另一种行之有效的方法交叉检查结果吗
    • 你能强制错误条件吗
    • 性能特征是否在范围内?
        2
  •  4
  •   Gavin Miller    15 年前

    以下是一些想法:

    积极的

    • 你是单元测试!
    • 你按照惯例安排、行动、断言

    否定的

    • 没有入口时移除入口的测试在哪里?
    • 当没有入口时,获取入口的测试在哪里?
    • 当您添加两个条目并删除一个条目时应该发生什么?应该留下哪一个?
    • 应该 Entries 公开。您的一个断言调用 rep.Entries.SingleOrDefault 在我看来,你没有正确地构造类。
    • 您的测试命名有点模糊;通常要遵循的一个好模式是: {MethodName}_{Context}_{Expected Behavior} 消除冗余“测试”冗余。

    作为TDD的初学者,我发现了这本书 Test-Driven Development By Example 成为一个巨大的帮助。其次,罗伊·奥舍洛夫有一些好的 Test Review 视频教程,看看。

        3
  •  1
  •   James McMahon    15 年前

    在我回答之前,让我声明我对单元测试还比较陌生,决不是专家,所以用一粒盐来处理我所说的一切。

    但是我觉得你的单元测试基本上是多余的。许多方法都是简单的传递,就像加数方法只是对基础列表方法add的调用一样。不测试代码,测试Java库。

    我只推荐包含您编写的逻辑的单元测试方法。避免测试像getter和setter这样明显的方法,因为它们在最基本的级别上运行。这是我的理念,但我知道有些人确实相信测试显而易见的方法,我只是碰巧认为这是毫无意义的。

        4
  •  0
  •   Mendelt    15 年前

    像这样很好。我个人喜欢给我的测试取一些更具描述性的名字,但这更多的是个人偏好。

    您可以使用模拟来测试所测试的类的依赖项,EntryReportory是正在测试的类,因此无需模拟,否则您将最终测试模拟实现而不是类。

    只是举个简单的例子。如果EntryReportory将使用后端数据库来存储条目而不是列表,则可以为数据访问注入模拟实现,而不是调用真正的数据库。

        5
  •  0
  •   AndreiM    15 年前

    这看起来是一个很好的开始,但是您应该尽可能地测试“边界”案例。考虑一下什么可能导致方法失败-传递一个空条目来添加或删除是有效的吗?尝试编写测试来练习所有可能的代码路径。如果您对代码进行任何更改,以这种方式编写测试将使您的测试套件在将来更加有用。

    另外,对于每个测试方法来说,将测试对象保持在调用它时的状态也是很有用的。我注意到TestFetchEntry方法向EntryReportory添加了一个元素,但从未移除它。使每个方法不影响测试对象状态,可以更容易地运行一系列测试。

        6
  •  0
  •   tvanfosson    15 年前

    您不应该模拟IEntryRepository,因为实现类是正在测试的类。你 可以 想嘲笑一下 List<Entry> 并注入它,然后测试通过公共接口调用的方法是否被适当地调用。这只是实现它的方法的一个替代方法,并不一定更好——除非您希望类注入它的支持类,在这种情况下,以这种方式编写测试将强制该行为。

    您可能还需要一些测试来确保插入条目时插入了正确的条目。同样,使用delete——插入几个条目,然后删除一个,并确保删除了正确的条目。一旦你想出了让代码做你想做的事情的测试,继续思考你可能会把编写代码搞砸的方法,并编写测试来确保这些不会发生。当然,这个类非常简单,并且您可能能够说服自己,您拥有的驱动行为测试已经足够了。不过,测试边缘案例和意外行为并不需要太多的复杂性。

        7
  •  0
  •   Igor Brejc    15 年前

    对于一个TDD初学者和这门特殊的课程,您的测试是可以的。+1.努力。

    一旦你进入了更复杂的场景,包括依赖注入和模拟,就发布另一个问题。这是事情变得非常有趣的地方;)。

        8
  •  -1
  •   Andriy Volkov    15 年前

    整体看起来不错。您应该使用事务(或在TestInitialize中创建存储库的新实例)来确保测试是真正隔离的。

    也要使用更具描述性的测试方法,比如当一个新的实体被添加到一个入口的报表中时,对象的总计数应该增加。