我正在努力掌握测试驱动的开发,我想知道这些单元测试是否合适。我有一个看起来像这样的界面:
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");
}
}
谢谢!
答案 0 :(得分:7)
就在我头顶......
虽然你对add的测试真的只测试框架:
与你的获取相同:
书中有一个很好的清单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