在Unit-Test之外使用Asserts

时间:2014-06-22 16:16:22

标签: c# automated-tests integration-testing assert xunit

我们正在编写测试框架(用于集成测试),我对断言有疑问。测试代码如下:

public class MyTestClass 
{
    [Fact]
    public void MyTest() 
    {
        CreateEntity();
        DeleteEntity();
    }
}

框架代码如下:

public class CRUD 
{
    public bool CreateEntity() 
    {
        FuncA();
        FuncB();
        FuncC();
        FuncD();
    }
}

如果FuncA()失败,我不想运行FuncB()FuncC()FuncD()。 我通常会做的是检查FuncA()是否使用if-else语句成功执行,如果没有 - 我会返回false。 我的问题是编写测试代码的人必须手动检查CreateEntity()是否返回true或false。因为它是测试代码,如果CreateEntity()失败,继续测试运行没有任何实际意义。我不希望用户if-else在他的Test-Case上使用每个方法并在失败时断言。我可以在Framework方法中自己断言吗?例如 -

public class CRUD 
{
    public bool CreateEntity() 
    {
        if (FuncA() == false) 
        {
            Assert.True(false, "FuncA() has failed");
        }

        FuncB();
        FuncC();
        FuncD();
    }
}

这被认为是个好主意吗?或者用户应该自己查询?我希望尽可能简化用户创建测试用例。

谢谢!

修改 需要记住的重要一点是这是一个集成测试框架。与单元测试不同,我们正在执行各种操作,并尝试了解它们如何协同工作。 FuncA()转到服务器并尝试执行操作。此操作通常不应失败。如果它有一些主要的基本功能错误,它将在我们的单元测试运行中被捕获。当它在我们的集成运行中不起作用时 - 它可能指向在特定的一组先前执行的操作之后发生的错误。所以我们想了解它,并停止测试,因为下一个动作可能无法正常工作,因为它们被集成到另一个中。所以我不确定我们是否想要模拟“CreateEntity”,因为它为我们提供了有关系统疾病的信息。我同意你的观点,Assert不是处理它的正确方法,我只是不完全确定异常是正确的方法,因为我不想处理问题,我想报告并停止测试。我不太喜欢需要用户if / else每个框架调用并检查结果的其他选项(这会在我看来会产生一个混乱的代码)。

2 个答案:

答案 0 :(得分:0)

围绕try catch中的函数。然后抛出异常或在catch中有一些逻辑来更新对象,并提供有关失败的一些信息。我建议投掷并使对象不知情。它不应该负责处理它自己的问题

答案 1 :(得分:0)

这实际上取决于您对CRUD课程的要求。作为一般规则,我会回应rhughes在评论中提出的建议 - 你应该明确抛出异常。在这种情况下,CreateEntity()方法最简单的合理重写如下:

public class CRUD 
{
    public bool CreateEntity() 
    {
        if (FuncA() == false) 
        {
            throw new Exception("FuncA() has failed");
        }

        FuncB();
        FuncC();
        FuncD();
    }
}

或者,FuncA()本身可以在异常失败时抛出异常,并且您认为它不能在正常执行下失败。在任何一种情况下,这些都会导致您的测试用例失败,并且CreateEntity()中的代码会在抛出它时停止执行。

我反对在应用程序代码中使用测试框架断言 - 如果你只是将它们添加到测试中,那么你的应用程序本身就不应该知道它们。像MSTest这样的框架无论如何都会在Assert.IsTrue这样的方法中使用异常,所以你最终会遇到与在应用程序代码中手动抛出异常相同的行为,但最终会降低对下一个人的可读性和不太容易理解正在努力。

编辑:您是否考虑过以下方式抽象方法背后的丑陋:

private void WithEntity(Action action)
{
    if (!CreateEntity())
    {
        throw new Exception("Failed to create entity");
    }

    action();

    DeleteEntity();
}

然后你可以写下你的测试:

[Fact]
public void TestMethod1()
{
    WithEntity(() =>
    {
        // test code goes here
    });
}

它会掩盖一些尴尬。这样,您的应用程序代码仍然可以return false; FuncA();失败,并且每次要编写新测试时都不必编写大量样板文件。您甚至可以在集成测试框架中添加[Setup]属性,以便在每个测试/套件之前运行,如下所示:

[Setup]
public void Setup()
{
    if (!CreateEntity())
    {
        throw new Exception("Failed to create entity");
    }
}

...和一个类似的[Cleanup]属性来处理每个测试/套件后的运行代码(取决于您需要的)。这些建议中的任何一个都有帮助吗?

编辑2:另一件可以帮助的事情是装饰模式。如果您的CRUD类实现了一个接口,那么您可以执行某种操作......

public class Crud : ICrud
{
    public bool CreateEntity()
    {
        // exciting business logic
    }

    // etc.
}

public class CrudTestDecorator : ICrud
{
    private readonly ICrud m_Crud;

    public CrudTestDecorator(ICrud crud)
    {
        m_Crud = crud;
    }

    public bool CreateEntity()
    {
        if (!m_Crud.CreateEntity())
        {
            throw new Exception("Could not create entity");
        }
    }

    // etc.
}

这满足了您的更多要求:

  • 您的业务逻辑(在主应用程序中)仍然可以返回bool s
  • 您可以在任何可以使用原始
  • 的地方使用装饰物品
  • 您只需编写一次抛出异常的代码
  • 您的测试可能会更快失败