Tóm lược NUnit

NUnit là test framework phổ biến trong cộng đồng .NET. Dù Microsoft đã tích hợp MSTest vào Visual Studio, nhiều người vẫn thích dùng NUnit. Bài này sẽ tóm tắt những tính năng thường dùng trong NUnit để tiện tra cứu.

Các chức năng trong bài này áp dụng cho NUnit 2.6. Một số chức năng đã bị thay đổi trong NUnit 3.

Lệnh Assert

NUnit cung cấp hai cách để viết lệnh Assert.

Cách “cổ điển”:

Assert.AreEqual(4, calculator.Add(2, 2));

Cách “trôi chảy” (fluent syntax):

Assert.That(calculator.Add(2, 2), Is.EqualTo(4));

Cách “trôi chảy” nghe tự nhiên hơn vì nó mô phỏng một câu tiếng Anh.

So sánh kiểu string

Assert.That(name, Is.EqualTo("John"));
Assert.That(name, Is.EqualTo("John").IgnoreCase);
Assert.That(name, Is.Not.EqualTo("John"));

So sánh kiểu số

Assert.That(result, Is.EqualTo(3));
Assert.That(result, Is.EqualTo(3.3).Within(0.01));
Assert.That(result, Is.EqualTo(101).Within(1).Percent);
Assert.That(result, Is.Positive(3));
Assert.That(result, Is.Negative(3));
Assert.That(result, Is.NaN(3));

So sánh kiểu DateTime

Assert.That(result, Is.EqualTo(new DateTime(2016, 8, 21)));
Assert.That(result, Is.EqualTo(new DateTime(2016, 8, 21))
                      .Within(TimeSpan.FromMillisecond(1)));

Ta truyền vào Within() một đối tượng kiểu TimeSpan. Kiểu này đóng vai trò là sai số (tolerance).

Assert.That(result, Is.EqualTo(new DateTime(2016, 8, 21))
                      .Within(1).Milliseconds);

So sánh một khoảng giá trị (range)

Assert.That(result, Is.GreaterThan(100));
Assert.That(result, Is.InRange(100, 200)); // inclusive
Assert.That(result, Is.GreaterThanOrEqualTo(100));
Assert.That(result, Is.LessThan(100));
Assert.That(result, Is.LessThanOrEqualTo(100));

So sánh giá trị Null và Boolean

Assert.That(name, Is.Not.Empty);
Assert.That(name, Is.Null);
Assert.That(name, Is.Not.Null);
Assert.That(name, Is.True);
Assert.That(name, Is.False);

So sánh collection

// No item in string collection is empty
Assert.That(stringCollection, Is.All.Not.Empty);

// Any item in collection with value "Name"?
Assert.That(stringCollection, Contains.Item("Name"));

// One or more items in collection contains the substring "sub"
Assert.That(stringCollection, Has.Some.ContainSubString("sub"));

// The collection contains exactly 2 items with value "Name"
Assert.That(stringCollection, Has.Exactly(2).EndsWith("Name"));

// The collection has no duplicate
Assert.That(stringCollection, Is.Unique);

Assert.That(stringCollection, Has.None.EqualTo("Name"));

// Check if 2 collections contain the same elements, won't check order
Assert.That(stringCollection, Is.EquivalentTo(anotherCollection));

// Is the collection alphabetically ordered?
Assert.That(stringCollection, Is.Ordered);

So sánh tham chiếu

// Are 2 variables point to the same object?
Assert.That(object1, Is.SameAs(object2));
Assert.That(object1, Is.Not.SameAs(object2));

Kiểm tra kiểu đối tượng và thuộc tính

Assert.That(object, Is.TypeOf<Type>());
Assert.That(object, Is.InstanceOf<Type>());
Assert.That(object, Has.Property("Name"));

Kiểm tra ngoại lệ (exception)

// Not a good idea, should be more specific
Assert.That(() => Calculator.Divide(2, 0), Throws.Exception);

Assert.That(() => Calculator.Divide(2, 0),
                  Throws.TypeOf<DivideByZeroException>());

Assert.That(() => Calculator.Divide(2, 0),
                  Throws.TypeOf<ArgumentOutOfRangeException>()
                        .With.Matches<ArgumentOutOfRangeException>(
                             x => x.ParaName == "value"));

Attribute thường dùng

Chạy code trước và sau mỗi test

private Calculator calculator;

[SetUp]
public void BeforeEachTest()
{
    calculator = new Calculator();
}

[TearDown]
public void AfterEachTest()
{
    // clear, release resources
}

Ta có thể truy xuất thông tin của test thông qua đối tượng TestContext.CurrentContext.Test. Giả sử tôi muốn lấy tên của test method, tôi dùng dòng lệnh sau:

Console.WriteLine(TestContext.CurrentContext.Test.Name);

Chạy code trước và sau một test fixture

[TestFixtureSetUp]
public void BeforeAnyTestStarted() {}

[TestFixtureTearDown]
public void AfterAllTestsFinished() {}

Chạy code trước và sau assembly hoặc namespace

namespace Calculator.Tests
{
    [SetUpFixture]
    public class SetUpFixtureForNamespace
    {
        [SetUp]
        public void RunBeforeAnyTestInNamespace() {}

        [TearDown]
        public void RunAfterAnyTestInNamespace() {}
    }
}

Nếu không có dòng khai báo namespace thì [SetUpFixture] sẽ áp dụng cho tất cả test trong assembly.

[SetUpFixture]
public class SetUpFixtureForEntireAssembly
{
    [SetUp]
    public void RunBeforeAnyTestInAssembly() {}

    [TearDown]
    public void RunAfterAnyTestInAssembly() {}
}

Chạy test với nhiều dữ liệu test case

[TestCase(2, 2, 4)]
public void ShouldAdd(int num1, int num2, int expected) {}

Có thể dùng nhiều test case:

[TestCase(1, 2, 3)]
[TestCase(2, 2, 4)]
[TestCase(4, 3, 7)]
public void ShouldAdd(int num1, int num2, int expected) {}

Tái sử dụng dữ liệu test case

Tạo một class để cung cấp dữ liệu cho test method.

public class TestCaseSource : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        yield return new[] { 1, 1, 2 };
        yield return new[] { 2, 2, 4 };
        yield return new[] { 3, 4, 7 };
    }
}
[TestCaseSource(typeof(TestCaseSource))]
public void ShouldAdd(int num1, int num2, int expected) {}

Tạo tổ hợp dữ liệu đầu vào

[Test]
public void ShouldAddAndDivide([Values(10, 20, 30)] int numberToAdd,
                               [Values(2, 1, 0)] int numberToDivide) {}

Bỏ qua test

[Test]
[Ignore] // Shown as a warning sign in Test Explorer
public void ShouldAdd() {}

Phân nhóm test bằng Category

[Test]
[Category("CategoryName")] // Can also be used for TestFixture
public void ShouldAdd() {}

Trong Test Explorer, ta chọn mục Traits để hiển thị Category.

Chỉ định thời gian chạy test tối đa

[Test]
[MaxTime(4000)] // milliseconds
public void ShouldAdd() {}

Test sẽ fail nếu thời gian chạy vượt quy định (4 giây).

Chạy một test nhiều lần

[Test]
[Repeat(10000)]
public void ShouldDoWork() {}

Trong 10.000 lần chạy, chỉ 1 lần fail là test sẽ fail.