简体   繁体   中英

Using Asserts outside of Unit-Test

We're in a process of writing Test framework (for Integration Testings) and I have a question regarding asserts. The test code looks like that:

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

The framework code looks like that:

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

If FuncA() fails I don't want to run FuncB() , FuncC() or FuncD() . What I usually would do, is to check if FuncA() executed successfully using if-else sentence, and if not - I would have return false. My problem is that the person who write the Test-Code have to manually check if CreateEntity() return true or false. Because it's a test code, if CreateEntity() fails, there is no real meaning to continue the Test-run. I don't want the user to if-else every method on his Test-Case and assert on failure. Can I do the assert myself in the Framework method? for example -

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

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

Is that considered a good idea? or the user should anyway do the inquiring himself? I want the creation of Test-Cases to be easy as possible to the user.

Thanks!

EDIT: Important thing to remember is this is an Integration Test Framework. Unlike Unit-Tests, we are performing various actions and try to see how they all work together. FuncA() go to the server and try to perform an action. This action should not normally fail. If it has some major basic functionality bug, it will be caught on our Unit-Test run. When it doesn't work in our Integration run - It might point to a bug that happens after specific set of previously made actions. So we want to know about it, and stop the test as the next actions will probably not work as they are integrated one into the other. So I'm not sure we want to mock "CreateEntity", as it gives us information about illness in the system. I do agree with you that Assert is not the right way to handle it, I'm just not completely sure exception is the right way to go, as I don't want to handle the problem, I want to report it and stop the test. I'm not too fond of the other option that requires the user to if/else every framework call and check for the results (which will create a messy code in my own opinion).

Surround the functions in try catch. Then either throw the exception or have some logic in the catch to update the object with some info about the failure. I'd suggest throwing and make the object unaware. It shouldn't be in charge is handling it's own issues

This really depends on your requirements for the CRUD class. As a general rule, I would echo what rhughes suggested in his comment--you should explicitly throw exception. The simplest sensible rewrite of your CreateEntity() method in this instance would be as follows:

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

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

Alternatively, FuncA() itself could throw the exception if it fails, and you don't think it should be able to fail under normal execution. In either case, these will cause your test case to fail and the code in CreateEntity() to cease executing at the point that it is thrown.

I would argue against using test-framework asserts in application code--if you only add them to be consumed by tests then your application itself shouldn't know anything about them. Frameworks like MSTest use exceptions in methods like Assert.IsTrue anyway, so you'd end up with the same behaviour as manually throwing exceptions in your application code, but it would end up less readable and less easy to understand to the next person working on it.

EDIT: Have you considered abstracting the ugliness behind a method along the lines of:

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

    action();

    DeleteEntity();
}

You could then write your tests:

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

It would hide some of the gnarliness. That way your application code can still return false; should FuncA(); fail, and you don't have to write tons of boilerplate each time you want to write a new test. You could even just add a [Setup] attribute to your integration test framework to run before each test / suite that looks something like:

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

...and a similar [Cleanup] attribute to handle run code after each test / suite (depending on which you need). Would either of those suggestions help?

EDIT 2: Another thing that could help here is the decorator pattern. If your CRUD class implements an interface, you could do something of the sort...

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.
}

This fulfils more of your requirements:

  • Your business logic (in the main application) can still return bool s
  • You can use your decorated object in any place you could use the original
  • You only have to write the exception-throwing code once
  • Your tests can fail more quickly

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM