【问题标题】:C# Unit Testing parserC# 单元测试解析器
【发布时间】:2013-05-12 06:24:36
【问题描述】:

我需要写一个解析器。我想测试许多输入格式,输出非常复杂。我应该如何使用类似的测试来测试不同的输入和输出?

例如

public class Person
{
    public string Name;
    public int Age;
    public string Comment;
}

public interface IParser
{
    Person Parse(string input);
}

public class Parser : IParser
{
    public Person Parse( string input )
    {
        var fields = input.Split(',');

        var p = new Person
        {
            Name = fields[0],
            Age = int.Parse(fields[1])
        };

        p.Comment = fields.Length == 3 ? fields[2] : ""; 

        return p;
    }
}

我写了这些测试...

public class ParserTests
{
    protected string _input;
    protected Person _expected;

    private IParser _parser = new Parser();
    private Person _actual { get { return _parser.Parse(_input); } }

    [TestMethod]
    public void Parse_Name()
    {
        Assert.AreEqual(_expected.Name, _actual.Name);
    }

    [TestMethod]
    public void Parse_Age()
    {
        Assert.AreEqual(_expected.Age, _actual.Age);
    }

    [TestMethod]
    public void Parse_Comment()
    {
        Assert.AreEqual(_expected.Comment, _actual.Comment);
    }
}

[TestClass]
public class ParserTestsWithoutComment : ParserTests
{
    public ParserTestsWithoutComment()
    {
        _input = "John,29";
        _expected = new Person { Name = "John", Age = 29, Comment = "" };
    }
}

[TestClass]
public class ParserTestsWithComment : ParserTests
{
    public ParserTestsWithComment()
    {
        _input = "Brian,99,test";
        _expected = new Person { Name = "Brian", Age = 99, Comment = "test" };
    }
}

我是单元测试的新手,我不知道如何从更复杂的东西开始。我真正的输入文件更复杂,就像

PokerStars Hand #98451585362:  Hold'em No Limit ($5/$10 USD) - 2013/05/12 9:25:04 CET [2013/05/12 3:25:04 ET]
Table 'Soyuz-Apollo II' 6-max Seat #4 is the button
Seat 1: Codrus426 ($1812.52 in chips) 
Seat 2: JMBigJoe ($2299.10 in chips) 
Seat 3: xinxin1 ($903.94 in chips) 
Seat 4: moshmachine ($1107 in chips) 
Seat 5: TopKat5757 ($1147 in chips) 
Seat 6: LukaschenkoA ($1274.96 in chips) 
TopKat5757: posts small blind $5
LukaschenkoA: posts big blind $10
*** HOLE CARDS ***
Codrus426: calls $10
JMBigJoe: raises $25 to $35
xinxin1: folds 
moshmachine: folds 
TopKat5757: folds 
LukaschenkoA: folds 
Codrus426: calls $25
*** FLOP *** [2h 3s 6h]
Codrus426: checks 
JMBigJoe: bets $41
Codrus426: calls $41
*** TURN *** [2h 3s 6h] [2d]
Codrus426: bets $40
JMBigJoe: calls $40
*** RIVER *** [2h 3s 6h 2d] [Qh]
Codrus426: checks 
JMBigJoe: checks 
*** SHOW DOWN ***
Codrus426: shows [9d Ah] (a pair of Deuces)
JMBigJoe: mucks hand 
Codrus426 collected $244 from pot
*** SUMMARY ***
Total pot $247 | Rake $3 
Board [2h 3s 6h 2d Qh]
Seat 1: Codrus426 showed [9d Ah] and won ($244) with a pair of Deuces
Seat 2: JMBigJoe mucked
Seat 3: xinxin1 folded before Flop (didn't bet)
Seat 4: moshmachine (button) folded before Flop (didn't bet)
Seat 5: TopKat5757 (small blind) folded before Flop
Seat 6: LukaschenkoA (big blind) folded before Flop

我想将其解析为我正在处理的Hand 类...

public class Hand
{   
    public long ID; 
    public string Stakes; 
    public DateTime Date; 

    public IDictionary<Street, decimal> Pots;
    public decimal FinalPot; 
    public decimal Rake; 

    public Player Hero; 
    public IDictionary<Player, PlayerInfo> Players; 

    public IList<Card> Board; 

    public IList<Decision> Actions; 

    public Hand()
    { 
        this.Players = new Dictionary<Player, PlayerInfo>();
        this.Board = new List<Card>();
        this.Actions = new List<Decision>();
        this.Pots = new Dictionary<Street, decimal>();
    }
}

public class PlayerInfo
{
    public Player Player;
    public decimal Stack;
    public decimal Summary;
    public Position Position;
    public Card Holecards;
}

【问题讨论】:

  • 如果你使用 nUnit,你可以为此使用测试用例,这很干净 imo
  • 我同意 - 很干净。不过,我不同意@JohanLarsson 的观点,即 NUnit 会实质性地改进任何东西——尽管它是我现在主要用于单元测试的东西。 MSTest 也适用于您正在做的事情。

标签: c# unit-testing parsing


【解决方案1】:

由于您将全局变量与继承相结合,您的解决方案有效,但难以理解。

如果您使用的是 NUnit 2.5 或更高版本,则可以使用 Parameterized Tests with TestCaseAttribute

[TestCase("John,29","John",29,"")]
[TestCase(",13","",13,"")]
public void ParserTest(Sting stringToParse, String expextedName, int expectedAge, String expectedComment)
{
    IParser _parser = new Parser();
    Person _actual = _parser.Parse(stringToParse);

    Assert.AreEqual(expextedName, _actual.Name, stringToParse + " failed on Name");
    Assert.AreEqual(expextedAge, _actual.Age, stringToParse + " failed on Age");
    Assert.AreEqual(expextedComment, _actual.Comment, stringToParse + " failed on Comment");
}

我认为这更容易理解。

如果您需要继续使用 mstest,您必须按照how-to-rowtest-with-mstest 中的描述对其进行模拟

【讨论】:

  • @Pankracy:k3b 在参数化单元测试方面提出了一个很好的观点,以便稍微清理一下。我脑海中闪过这个想法,暗示当前的结构可以折叠成ParserTests。我已经完成了晚上的工作,但会尽快使用代码来更好地解释我的意思。
  • 抱歉,我将用我想要解析的真实数据更新我的第一篇文章。
【解决方案2】:

我认为你的结构非常好,记住 TMTOWTDI。

但是,请务必明确测试 Parse 的 null、空(即零长度)和空白字符串。更复杂/预期路径的情况当然是测试的关键,但像这样的简单情况也很重要。

此外,您正在使用的结构很可能被折叠成 ParserTests,使用通用的 %MethodUnderTest%_With%Condition(s)%_Expect%ExpectedResult% 测试用例命名约定和可选的 Test Data Builder 模式,以使另一个类像您现在一样负责构建测试数据在你的ParserTests 子类中做

【讨论】:

    猜你喜欢
    • 2013-02-05
    • 2010-09-19
    • 2015-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 1970-01-01
    相关资源
    最近更新 更多