这些单元测试是否正常?

时间:2009-04-07 18:56:45

标签: c# .net unit-testing tdd mocking

我正在努力掌握测试驱动的开发,我想知道这些单元测试是否合适。我有一个看起来像这样的界面:

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

Theese是我到目前为止所写的单元测试,它们是好还是我应该做些不同的事情?我应该嘲笑EntryRepository吗?

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

答案 0 :(得分:7)

就在我头顶......

虽然你对add的测试真的只测试框架:

  • 您已添加1项,这很好
  • 如何添加很多物品 (我的意思是,荒谬的金额 - 容器添加的n个条目的值是多少?)
  • 如果没有添加任何物品? (空条目)
  • 如果您将项目添加到列表中,它们是否按特定顺序排列? 它们应该是吗?

与你的获取相同:

  • 如果x&gt;,您的提取(x)会发生什么rep.Count?
  • 如果x&lt; 0?
  • 如果代表是空的,会发生什么?
  • x匹配性能要求(它是什么算法 复杂?当只有一个条目时,它是否在范围内 那里有大量的条目?

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

  • 结果是否正确?
  • 所有边界条件都是正确的
    • 符合预期格式
    • 正确订购
    • 在合理范围内
    • 是否引用任何外部依赖项
    • 基数是否正确? (正确的数量)
    • 以正确的时间(真实或相对)完成
  • 您可以查看反向关系
  • 您可以使用另一种经过验证的方法交叉检查结果吗
  • 可以强制错误条件
  • 性能特征是否在界限内

答案 1 :(得分:4)

以下是一些想法:

<强>正

  • 你是单位测试!
  • 您遵循惯例安排,行动,断言

<强>否定

  • 在没有条目时删除条目的测试在哪里?
  • 在没有条目的情况下获取条目的测试在哪里?
  • 添加两个条目并删除一个条目时会发生什么?应该留下哪一个?
  • Entries应该公开。你的一个断言调用rep.Entries.SingleOrDefault这个事实告诉我你没有正确地构建这个类。
  • 您的测试命名有点模糊;通常一个好的模式是:{MethodName}_{Context}_{Expected Behavior}删除冗余“测试”冗余。

作为TDD的初学者,我发现这本书Test-Driven Development By Example是一个巨大的帮助。其次,Roy Osherove有一些很好的Test Review视频教程,请查看它们。

答案 2 :(得分:1)

在我回答之前,请允许我声明我对单元测试相当新,而且绝不是专家,因此请将我所陈述的所有内容都放在一边。

但我觉得你的单元测试基本上是多余的。你的许多方法都是简单的传递,就像你的AddEntry方法只是调用底层的List方法一样。您不测试代码,测试Java库。

我建议只使用包含您编写的逻辑的单元测试方法。避免测试明显的方法,如getter和setter,因为它们在最基本的级别上运行。这是我的理念,但我知道有些人确实相信测试明显的方法,我恰好认为它毫无意义。

答案 3 :(得分:0)

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

你可以使用mocking来测试你正在测试的类的依赖关系,EntryRepository是被测试的类,所以不需要模拟它,否则你最终会测试模拟实现而不是类。

举一个简单的例子。如果您的EntryRepository将使用后端数据库来存储Entries而不是List,那么您可以为数据访问内容注入模拟实现,而不是调用真实数据库。

答案 4 :(得分:0)

这似乎是一个很好的开始,但你应该尝试尽可能多地测试'borderline'案例。想想可能导致您的方法失败的原因 - 将空条目传递给添加或删除是否有效?尝试编写运行每个可能的代码路径的测试。如果您对代码进行任何更改,以这种方式编写测试将使您的测试套件在将来更有用。

此外,每个测试方法都可以使测试对象保持与调用时相同的状态。我注意到你的TestFetchEntry方法向EntryRepository添加了一个元素,但从未删除它。让每种方法都不影响测试对象状态,可以更容易地进行一系列测试。

答案 5 :(得分:0)

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

您可能需要更多测试以确保在插入条目时插入正确的条目。同样删除 - 插入几个条目,然后删除一个条目,并确保已删除正确的条目。一旦你拿出测试来让代码完成你想要的代码,就要继续思考如何编写代码并编写测试以确保代码不会发生。当然,这个类非常简单,您可以说服自己,您拥有该驱动行为的测试就足够了。但是,它并不需要太多的复杂性,因为它值得测试边缘情况和意外行为。

答案 6 :(得分:0)

对于TDD初学者和此特定班级,您的测试很好。 +1努力。

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

答案 7 :(得分:-1)

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

还使用更多描述性测试方法,例如When_a_new_entity_is_added_to_a_EntryRepository_the_total_count_of_objects_should_get_incremented