Модульные тесты C#. Подробная инструкция

Часть 1. Создание тестового проекта в два клика

Пусть есть класс с реализованными (хотя бы в состоянии заглушки) функциями, которые нужно протестировать

//Создали проект и сделали заглушку для первого задания
namespace Homework_N
{
    public class Program
    {
        static void Main(string[] args)
        {
        }

        /*Дана строка. Определить, является ли она палиндромом.
          Пустая строка и строка из одного символа являются палиндромами.*/
        public static bool IsPalindrome(string s)
        {
            return true;
        }
    }
}

Важно: тестируемый класс и тестируемые функции должны иметь модификатор доступа public.

Нажав на правую кнопку мыши, в меню необходимо выбрать Создание модульных тестов. В зависимости от того, откуда мы вызываем контекстное меню, будет разная реакция, т.е. будут созданы тесты для всех методов класса, или же для конкретного.  


Допишем в формат имени "0" или "1" и нажмем ОК. В VisualStudio будет создан проект с модульными тестами, связанный с тестируемым проектом

//Создали тестовый проект
using... 
using static Homework_N.Program;
namespace Homework_N.Tests
{
    [TestClass()]
    public class ProgramTests
    {
        [TestMethod()]
        public void IsPalindromeTest0()
        {
            Assert.Fail();
        }
    }
}

Для удобства (так как тестировать мы будем только класс Program) напишем using static Homework_N.Program;

[TestClass()] говорит компилятору, что класс содержит тестирующие функции. Для каждого тестируемого класса обычно создаётся свой тестирующий класс

[TestMethod()] указывает, что следующий метод является тестирующей процедурой. Компилятор будет ожидать от неё исключение AssertFailedException, которое генерируется классом Assert в том случае, если тест не пройден

В отличие от Debug.Assert, Assert в модульных тестах является классом с набором статических функций

Asser.Fail() всегда делает тест провалившимся. Генератор тестов добавил его по умолчанию исходя из последнего пункта настроек (на рисунке 2)

В обозревателе тестов нажмите "Запустить все". Также тесты можно запустить с помощью вкладки "Тестирование" на верхней панели, либо кликнув правой кнопкой мыши где-нибудь в коде.

Вам будет показан IsPalindromeTest0 среди провалившихся. Кликните по нему в обозревателе тестов. Внизу будет подробный отчёт о результатах теста.


При клике на ссылку "Трассировка стека: ProgramTests.IsPalindromeTest0()" VS поставит ваш курсор на то место в тесте, в котором произошла ошибка.


Оформление тестирующей процедуры

Assert.IsTrue|IsFalse

Если бы мы использовали Debug.Assert, то написали бы тест в стиле Debug.Assert(IsPalindrome("qwewq"), "IsPalindrome: Test 1");

Теперь нам не нужно указывать в строковом виде, к чему относится данный тест, это делает за нас VS

Для проверки логических выражений можно использовать Assert.IsTrue. Напишем вместо Asser.Fail() Assert.IsTrue(IsPalindrome("qwewq"));

[TestMethod()]
public void IsPalindromeTest0()
{
    Assert.IsTrue(IsPalindrome("qwewq"));
}

Запустим. Тест пройден. Напишем ещё пару тестов. Например, для не палиндрома и строки из одного символа. Вместо Assert.IsTrue(!IsPalindrome("wq")) лучше написать Assert.IsFalse(IsPalindrome("wq"));

[TestMethod()]
public void IsPalindromeTest0()
{
    Assert.IsTrue(IsPalindrome("qwewq"));
    Assert.IsFalse(IsPalindrome("wq"));
    Assert.IsTrue(IsPalindrome("q"));
}

Запустим тест снова. Теперь он окажется провалившимся, хотя сбой даст только один тест. Лучше всего для каждого Assert создавать отдельную функцию. Каждая тестирующая функция должна проверять один набор входных данных. Так как кроме Assert мы ничего не используем, можем написать в сокращённом виде

[TestClass()]
public class ProgramTests
{
    [TestMethod()]
    public void IsPalindromeTest0() => Assert.IsTrue(IsPalindrome("qwewq"));
    [TestMethod()]
    public void IsPalindromeTest1() => Assert.IsFalse(IsPalindrome("wq"));
    [TestMethod()]
    public void IsPalindromeTest2() => Assert.IsTrue(IsPalindrome("q"));
}

В идеале, название теста должно отображать не только то, какую функцию он тестирует, но и на каком смысловом наборе. Но для нас это избыточно, поэтому будем просто нумеровать тесты.

Запустим. Теперь чётко видно, что провальным является один тест. С помощью обозревателя тестов мы можем легко выяснить, какой именно

Прежде чем исправить решение задачи, допишем ещё несколько тестов

[TestMethod()]
public void IsPalindromeTest0() => Assert.IsTrue(IsPalindrome("qwewq"));
[TestMethod()]
public void IsPalindromeTest1() => Assert.IsFalse(IsPalindrome("wq"));
[TestMethod()]
public void IsPalindromeTest2() => Assert.IsTrue(IsPalindrome("q"));
[TestMethod()]
public void IsPalindromeTest3() => Assert.IsTrue(IsPalindrome("qq"));
[TestMethod()]
public void IsPalindromeTest4() => Assert.IsTrue(IsPalindrome(""));
[TestMethod()]
public void IsPalindromeTest5() => Assert.IsFalse(IsPalindrome("abcddcbs"));
Допишем функцию

public static bool IsPalindrome(string s)
{
    if (s.Length < 2)
        return true;
    return (s.First() == s.Last());
}

Запустим тесты. Тесты пройдены! Хотя решение, очевидно, не полно. Значит, мы написали недостаточно тестов. Напишем ещё один

[TestMethod()]
public void IsPalindromeTest6() => Assert.IsFalse(IsPalindrome("abcddfba"));

Запустим этот тест. Решение на нём действительно провалилось. Допишем код IsPalindrome и запустим тесты.

public static bool IsPalindrome(string s)
{
    if (s.Length < 2)
        return true;
    return (s.First() == s.Last()) && IsPalindrome(s.Substring(1, s.Length - 2));
}


Теперь пройдены все тесты. Но мы забыли про проверку предусловия - если нам передадут null, мы никак об этом не сообщим, а просто упадём с исключением NullReferenceException, так как мы пытаемся узнать длину null.


*** Продолжение следует

Последнее изменение: Saturday, 21 April 2018, 10:38