首页 > 解决方案 > 我如何能够对 void 方法进行单元测试?

问题描述

我想测试我的方法来检查车辆的运动方向。它是一种 void 方法,只有一个参数 - 整数。

据我所知,所有的 if 都应该分成不同的测试方法,但我不知道该怎么做。

/// <summary>
        /// checks the east neighbour of the crossing on a given cell
        /// </summary>
        /// <param name="cellNr">the cell Nr of the crossing whose neighbour ///its checked</param>

    public void CheckEast(int cellNr)
    {
        Crossing cr = Cells[cellNr].Crossing;

        //If there is east neighbour - laneIns of the current crossing with direction east are NOT endLane
        if (cells[cellNr + 1].Taken)
        {
            foreach (LaneIn laneIn in cr.LaneInList)
            {
                if (laneIn.Direction == "west")
                {
                    laneIn.EndLane = false;
                }
            }

            //car should NOT be spawned from east && LaneOut of current crossing with direction east is NOT endLane
            cr.IncomingStreams[3] = "";
            cr.LaneOutList[0].EndLane = false;

            //LaneIn 'east' of the east neighbour is NOT endLane
            foreach (LaneIn laneIn in cells[cellNr + 1].Crossing.LaneInList)
            {
                if (laneIn.Direction == "east")
                {
                    laneIn.EndLane = false;
                }
            }
            //no spawned car in the neighbour from the east laneIns and LaneOut 'west' is not endlane
            cells[cellNr + 1].Crossing.IncomingStreams[1] = "";
            cells[cellNr + 1].Crossing.LaneOutList[1].EndLane = false;
            cr.Neighbours[1] = cells[cellNr + 1].Crossing;
            cells[cellNr + 1].Crossing.Neighbours[3] = cr;
        }
        //if there is NO neighbour on east side, laneIns of the current crossing are endlane(spawning point)
        //laneout is endlane, cars are erased after leaving it
        else
        {
            cr.Neighbours[1] = null;
            cr.IncomingStreams[3] = "west";
            cr.LaneOutList[0].EndLane = true;
            foreach (LaneIn laneIn in cr.LaneInList)
            {
                if (laneIn.Direction == "west")
                {
                    laneIn.EndLane = true;
                }
            }
        }
    }

标签: c#unit-testing

解决方案


从字面上回答你的问题,如果一个方法没有返回值,那么它必须产生一些其他的副作用。(如果它不返回任何东西或有副作用,那么它什么也不做。)

如果方法改变了类本身的状态,您可以执行该方法并断言预期状态:

public class Counter
{
    public int Value { get; private set; }

    public void Increment() => Value++;
}
public void Counter_increments()
{
    var subject = new Counter();
    subject.Increment();
    Assert.AreEqual(1, subject.Value());
}

或者您想要测试的行为可能是您的类与某些依赖项的交互。有几种方法可以做到这一点。一种是检查依赖的状态:

public class ClassThatIncrementsCounter
{
    public readonly Counter _counter;

    public ClassThatIncrementsCounter(Counter counter)
    {
        _counter = counter;
    }

    public void DoSomething()
    {
        // does something and then increments the counter
        _counter.Increment();
    }
}

[TestMethod]
public void DoSomething_increments_counter()
{
    var counter = new Counter();
    var subject = new ClassThatIncrementsCounter(counter);
    subject.DoSomething();
    Assert.AreEqual(1, counter.Value);
}

您还可以使用Moq 之类的库来验证您的类是否与依赖项交互:

public class ClassThatIncrementsCounter
{
    public readonly ICounter _counter;

    public ClassThatIncrementsCounter(ICounter counter)
    {
        _counter = counter;
    }

    public void DoSomething()
    {
        // does something and then increments the counter
        _counter.Increment();
    }
}

[TestMethod]
public void DoSomething_increments_counter()
{
    var counter = new Mock<ICounter>();

    // indicate that we want to track whether this method was called.
    counter.Setup(x => x.Increment()).Verifiable();
    var subject = new ClassThatIncrementsCounter(counter.Object);
    subject.DoSomething();

    // verify that the method was called.
    counter.Verify(x => x.Increment());
}

正如您所指出的,为了使其正常工作,有必要将方法分解成更小的块,以便我们可以单独测试。如果一个方法做出很多决定,那么为了完全测试,我们需要测试每个适用的组合,或者代码可以执行的每个可能的分支。这就是为什么具有很多条件的方法更难测试的原因。

我花了一些时间查看您的代码,但还不清楚它实际上在做什么,因此很难建议如何重构它。

您可以采用更大的代码块,这些代码会像这样重复:

        foreach (LaneIn laneIn in cr.LaneInList)
        {
            if (laneIn.Direction == "west")
            {
                laneIn.EndLane = false;
            }
        }

        foreach (LaneIn laneIn in cr.LaneInList)
        {
            if (laneIn.Direction == "west")
            {
                laneIn.EndLane = true;
            }
        }

...并用这样的方法替换它们,但您会给它们起清晰、有意义的名称。我不能这样做,因为我不确定他们做了什么:

public void SetEndLaneInDirection(List<LaneIn> laneInList, string direction, bool isEnd)
{
    foreach (LaneIn laneIn in laneInList)
    {
        if (laneIn.Direction == direction)
        {
            laneIn.EndLane = isEnd;
        }
    }
}

现在一小段代码更容易测试。或者,如果您有一组方法都进行一些相关更新,如下所示:

        cells[cellNr + 1].Crossing.IncomingStreams[1] = "";
        cells[cellNr + 1].Crossing.LaneOutList[1].EndLane = false;
        cr.Neighbours[1] = cells[cellNr + 1].Crossing;
        cells[cellNr + 1].Crossing.Neighbours[3] = cr;

然后把它们放在一个方法中,再给它一个清晰的名字。

现在您可以设置类、调用方法并断言您期望的状态。

副作用是,当您用命名良好的方法调用替换代码块时,您的代码变得更容易阅读,因为方法调用用近乎英语的方式说明了每个步骤正在做什么。

事后更难做到。挑战的一部分是学习编写已经更容易测试的代码。好消息是,当我们这样做时,我们的代码自然会变得更简单、更容易理解。我们有测试。

这是一篇关于如何重构现有代码以使其更易于测试的精彩文章。


推荐阅读