如何找到必须用单元测试覆盖的代码位置

3

我知道在编写代码之后再撰写测试并不是一个好习惯。作为一个单元测试新手,我感到单元测试可能会带来很多好处,因此我着迷于尽可能多地进行覆盖。

例如,我们有以下代码:

public class ProjectsPresenter : IProjectsViewObserver
{
    private readonly IProjectsView _view;
    private readonly IProjectsRepository _repository;

    public ProjectsPresenter(IProjectsRepository repository, IProjectsView view)
    {
        _view = view;
        _repository = repository;
        Start();
    }

    public void Start()
    {
        _view.projects = _repository.FetchAll();
        _view.AttachPresenter(this);
    }

}

看一下上面的代码,你能告诉我在那段代码中通常应该写哪些测试吗?

我打算编写构造函数的测试,以确保调用了仓库的FetchAll方法,并且在视图网站上调用了AttachPresenter方法。


编辑后的POST

这是我的视图接口:

public interface IProjectsView
{
    List<Project> projects { set; }
    Project project { set; }

    void AttachPresenter(IProjectsViewObserver projectsPresenter);
}

以下是一个视图:

public partial class ProjectsForm : DockContent, IProjectsView
{
    private IProjectsViewObserver _presenter;
    public ProjectsForm()
    {
        InitializeComponent();
    }

    public Project project
    {
        set
        {
            listBoxProjects.SelectedItem = value;
        }
    }

    public List<Project> projects
    {
        set
        {
            listBoxProjects.Items.Clear();   
            if ((value != null) && (value.Count() > 0))
                listBoxProjects.Items.AddRange(value.ToArray());
        }
    }

    public void AttachPresenter(IProjectsViewObserver projectsPresenter)
    {
        if (projectsPresenter == null)
            throw new ArgumentNullException("projectsPresenter");

        _presenter = projectsPresenter;
    }

    private void listBoxProjects_SelectedValueChanged(object sender, EventArgs e)
    {
        if (_presenter != null)
            _presenter.SelectedProjectChanged((Project)listBoxProjects.SelectedItem);
    }
}

第二次编辑提交

这是我测试与存储库交互的方式。一切都好吗?

    [Test]
    public void ProjectsPresenter_RegularProjectsProcessing_ViewProjectsAreSetCorrectly()
    {
        // Arrange
        MockRepository mocks = new MockRepository();
        var view = mocks.StrictMock<IProjectsView>();
        var repository = mocks.StrictMock<IProjectsRepository>();
        List<Project> projList = new List<Project> {
            new Project { ID = 1, Name = "test1", CreateTimestamp = DateTime.Now },
            new Project { ID = 2, Name = "test2", CreateTimestamp = DateTime.Now }
        };
        Expect.Call(repository.FetchAll()).Return(projList);
        Expect.Call(view.projects = projList);
        Expect.Call(delegate { view.AttachPresenter(null); }).IgnoreArguments();
        mocks.ReplayAll();
        // Act
        ProjectsPresenter presenter = new ProjectsPresenter(repository, view);
        // Assert
        mocks.VerifyAll();            
    }

Rhino Mocks的示例似乎与此无关...也许它应该有一个单独的问题。无论如何,您可能想查看Rhino Mocks AAA语法,例如https://dev59.com/THI95IYBdhLWcg3w2R3z - Amittai Shapira
5个回答

2

我知道在你编写代码之后编写测试用例并不是最好的方式

但比完全不写测试要好。

您的方法与两个外部组件配合使用,应该验证它们之间的交互(除了验证参数的有效性)。检查FetchAll是否被调用没有任何价值(或者检查它是否返回了某些内容——这属于ProjectsRepository的测试本身),您想要检查视图的项目是否设置(这将间接检查FetchAll是否被调用)。您需要的测试用例是:

  • 验证视图项目是否设置为预期值
  • 验证是否附加了Presenter
  • 验证输入参数

编辑:以下是如何测试第一个情况(项目已设置)的示例

// "RegularProcessing" in test name feels a bit forced;
// in such cases, you can simply skip 'conditions' part of test name
public void ProjectsPresenter_SetsViewProjectsCorrectly()
{
    var view = MockRepository.GenerateMock<IProjectView>();
    var repository = MockRepository.GenerateMock<IProjectsRepository>();
    // Don't even need content;
    // reference comparison will be enough
    List<Project> projects = new List<Project>();
    // We use repository in stub mode;
    // it will simply provide data and that's all
    repository.Stub(r => r.FetchAll()).Return(projects);
    view.Expect(v => v.projects = projects);

    ProjectsPresenter presenter = new ProjectsPresenter(repository, view);

    view.VerifyAllExpecations();
}

在第二种情况下,您将期望视图的AttachPresenter被调用,并传入有效的对象。
public void ProjectsPresenter_AttachesPresenterToView()
{
    // Arrange
    var view = MockRepository.GenerateMock<IProjectView>();
    view.Expect(v => v.AttachPresenter(Arg<IProjectsViewObserver>.Is.Anything));
    var repository = MockRepository.GenerateMock<IProjectsRepository>();

    // Act
    var presenter = new ProjectsPresenter(repository, view);

    // Assert
    view.VerifyAllExpectations();
}

我应该在两个不同的测试中实现第一项和第二项,还是只在一个测试中实现? - kseen
@kseen:这应该是两个测试,因为它们除了与同一组件交互之外没有关联 - k.m
请查看我的问题更新。我附上了视图的详细信息。请告诉我如何测试Presenter是否正确附加? - kseen
看一下 IProjectsView 接口。IProjectsView.projects 只有一个属性设置器。因此,断言部分不起作用:Project.Forms.Projects.IProjectsView.projects 无法在此上下文中使用,因为它缺少 get 访问器 - kseen
@kseen:我觉得这是不可能的。在设置期望值时,你需要构建ProjectsPresenter对象。由于你的操作发生在构造函数中,所以那时并不可能完成。你可以将_presenter暴露为内部字段,并在结尾处检查该字段。 - k.m
显示剩余2条评论

0
编写生产代码之前编写测试的目的首先是让您(开发人员)进入“如何知道我的代码是否有效”的思维模式。当您的开发关注于工作代码的结果而不是代码本身时,您将专注于代码带来的实际业务价值,而不是其他无关紧要的问题(已经花费了数百万人小时来构建和维护用户从未要求、想要或需要的功能)。这就是所谓的“测试驱动开发”。
如果您正在进行纯TDD,则答案是100%的代码覆盖率。也就是说,您不编写任何一行生产代码,这些代码没有被单元测试代码覆盖。
在Visual Studio中,如果您转到“测试”->“分析代码覆盖率”,它将向您显示所有未覆盖的代码行。
实际上,强制执行100%的代码覆盖率并不总是可行的。此外,有些代码行比其他代码行更重要。确定哪些代码行更重要取决于每个代码行提供的业务价值以及该代码行失败的后果。某些代码行(例如日志记录)可能具有较小的后果。

0

我会在开始时添加简单的测试,例如:

  • null 引用检查

  • FetchAll() 返回任何值

不要一开始就添加大量的测试代码,而是在开发代码变化后逐步完善它们。


空引用检查似乎是个好主意!谢谢!我对你关于“FetchAll()返回任何值”的观点不是很清楚。你能否更详细地描述一下? - kseen
@kseen:我的意思是,在 _view.projects = _repository.FetchAll(); 中检查 FetchAll() 是否返回任何值。换句话说,如果程序能够找到任何可用项目(不知道这是否对您的应用程序至关重要,但只是意图是),请检查。 - Tigran

0

我会为异常(例如ArgumentException)、FetchAll()的边缘情况和通常情况添加测试。

附言:start必须是公共的吗?


更好的问题是在构造函数中是否调用了start()。 - Damian Leszczyński - Vash

0
Pex是一个值得一试的有趣工具。它可以生成高代码覆盖率的单元测试套件:http://research.microsoft.com/en-us/projects/pex/。它并不取代您对代码的了解,以及哪些测试场景对您比其他场景更重要,但它是一个不错的补充。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接