sticky
Использование Unit-тестирования на примере Visual Studio. RRS feed

  • Общие обсуждения

  • Решил добавить  небольшую практическую часть по созданию Unit-тестов в Visual Studio. Думаю, кто первый раз с этим сталкивается, информация очень даже поможет сэкономить время и быстренько реализовать простенькие юнит-тесты для своих методов в программе.

    Как уже было сказано выше, модульное тестирование или юнит-тестирование  — это процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы.

    Идея состоит в том, чтобы писать тесты для каждой нетривиальной функции или метода. Это позволяет достаточно быстро проверить, не привело ли очередное изменение кода к регрессии, то есть к появлению ошибок в уже оттестированных местах программы, а также облегчает обнаружение и устранение таких ошибок.

    Собственно для этого нам понадобится Visual Studio 2010 Professional или более «высокой» редакции и набор методов, который мы хотим проверить.

    Начнем со следующего. Предположим у нас есть класс для работы с файловой системой:

     

    using System;
    using System.Text;
    using System.IO;
    using System.Windows.Forms;
     
    namespace FileManagement
    {
     /// <summary>
     /// Класс для работы с файлами и файловой системой.
     /// </summary>
     public static class FileManagement
     {
     /// <summary>
     /// Сканировать папку и получить все имена файлов в папке.
     /// </summary>
     /// <param name="path">Путь к сканируемой папке.</param>
     /// <param name="type">Тип файтов, которые нужно выбрать.</param>
     /// <returns>Массив с именами файлов, найденных в папке.</returns>
     public static string[] GetFilesNameInDirectory(string path, string type)
     {
      FileInfo[] filesInfo = (new DirectoryInfo(path)).GetFiles(type);
     
      string[] files = new string[filesInfo.Length];
     
      for (int i = 0; i < filesInfo.Length; i++)
      {
      files[i] = filesInfo[i].Name.Substring(0, filesInfo[i].Name.Length - filesInfo[i].Extension.Length);
      }
      return files;
     }
     
     
     /// <summary>
     /// Создать файл.
     /// </summary>
     /// <param name="path">Путь к файлу.</param>
     /// <returns>true - документ успешно создан. false - документ создать не удалось.</returns>
     public static bool Create(string path)
     {
      FileInfo info = new FileInfo(path);
     
      if (info.Exists == false)
      {
      try
      {
       File.Create(path);
       return true;
      }
      catch (Exception ex)
      {
       throw ex;
      }
      }
      else
      {
      switch (MessageBox.Show("This file already exists. Do you want to delete the existing file and create a new?",
         "Question",
         MessageBoxButtons.YesNo, MessageBoxIcon.Question))
      {
       case DialogResult.Yes:
       {
       try
       {
        File.Create(path);
        return true;
       }
       catch (Exception ex)
       {
        throw ex;
       }
       };
       default:
       { 
       } break;
      }
      }
      return false;
     }
     
     
     /// <summary>
     /// Прочитать текст из файла.
     /// </summary>
     /// <param name="path">Путь к файлу.</param>
     /// <returns>Массив строк из файла.</returns>
     public static string[] ReadAllLinesFile(string path)
     {
      FileInfo info = new FileInfo(path);
     
      if (info.Exists == false)
      {
      throw new Exception("This file can not be read. It not exists.\nPath: " + path);
      }
      else
      {
      try
      {
       return File.ReadAllLines(path, Encoding.Default);
      }
      catch (Exception ex)
      {
       throw ex;
      }
      }
     }
     
     
     /// <summary>
     /// Прочитать текст из файла.
     /// </summary>
     /// <param name="path">Путь к файлу.</param>
     /// <returns>Текст из файла в виде строки.</returns>
     public static string ReadAllTextFile(string path)
     {
      FileInfo info = new FileInfo(path);
     
      if (info.Exists == false)
      {
      throw new Exception("This file can not be read. It not exists.\nPath: " + path);
      }
      else
      {
      try
      {
       return File.ReadAllText(path, Encoding.Default);
      }
      catch (Exception ex)
      {
       throw ex;
      }
      }
     }
     
     
     /// <summary>
     /// Сохранить текст в файл.
     /// </summary>
     /// <param name="text">Текст, который нужно сохранить в файл.</param>
     /// <param name="path">Путь к файлу.</param>
     /// <param name="rewrite_question">
     /// Задавать ли вопрос о перезаписи файла.
     /// </param>
     /// <returns>
     /// true - текст успешно сохранен в файл.
     /// false - текст не удалось сохранить в файл.
     /// </returns>
     public static bool SaveFile(string text, string path, bool rewrite_question)
     {
      if (rewrite_question == true)
      {
      FileInfo info = new FileInfo(path);
     
      if (info.Exists == false)
      {
       try
       {
       File.WriteAllText(path, text, Encoding.Default);
       return true;
       }
       catch (Exception ex)
       {
       throw ex;
       }
      }
      else
      {
       switch (MessageBox.Show("This file already exists. Do you want to delete the existing file and create a new?",
          "Question",
          MessageBoxButtons.YesNo, MessageBoxIcon.Question))
       {
       case DialogResult.Yes:
       {
        try
        {
        File.WriteAllText(path, text, Encoding.Default);
        return true;
        }
        catch (Exception ex)
        {
        throw ex;
        }
       };
       default:
       {
       } break;
       }
      }
      }
      else
      {
      try
      {
       File.WriteAllText(path, text, Encoding.Default);
       return true;
      }
      catch (Exception ex)
      {
       throw ex;
      }
      }
      return false;
     }
     
     
     /// <summary>
     /// Копировать файл.
     /// </summary>
     /// <param name="source_file">Путь к копируемому файлу.</param>
     /// <param name="new_file">Путь, куда следует скопировать файл.</param>
     /// <returns></returns>
     public static bool CopyFile(string source_file, string new_file)
     {
      FileInfo s_f_info = new FileInfo(source_file);
     
      if (s_f_info.Exists == false)
      {
      throw new Exception("This file can not be copy. It not exists.\nPath: " + source_file);
      }
      else
      {
      FileInfo n_f_info = new FileInfo(new_file);
     
      if (n_f_info.Exists == true)
      {
       switch (MessageBox.Show("This file already exists. Do you want to delete the existing file and create a new?",
          "Question",
          MessageBoxButtons.YesNo, MessageBoxIcon.Question))
       {
       case DialogResult.Yes:
       {
        try
        {
        File.Copy(source_file, new_file);
        return true;
        }
        catch (Exception ex)
        {
        throw ex;
        }
       };
       default:
       {
       } break;
       }
      }
      else
      {
       try
       {
       File.Copy(source_file, new_file);
       return true;
       }
       catch (Exception ex)
       {
       throw ex;
       }
      }
      }
      return false;
     }
     }
    }
    

     

    Методы у этого класса, как видно из комментариев могут создавать файлы, копировать их, считывать из них текст, записывать и сканировать в паке все, содержащиеся в ней файлы. Предположим, нам надо проверить все-ли методы данного класса работают корректно. В принципе, все действия можно разбить на следующие несколько шагов:

    1.       В коде программы щелкаем правой кнопкой мыши по названию класса и в контекстном меню выбираем пункт “Create UnitTests…” 

    2.       После чего Вы увидите окно в котором следует выбрать те классы и методы которые Вы хотите протестировать. Собственно для них каркас теста и будет сгенерирован средой Visual Studio. Выберем для примера методы Create и ReadAllTextFile.

     

    В выпадающем списке Output project выберем существующий проект (это в том случае, если Вы уже создавали до этого юнит-тесты) или выберем пункт создать новый, и при этом можно выбрать на каком языке программирования будут созданы выбранные юнит-тесты.

     

    3.       Нажимаем ОК. Если был выбран новый проект, то нас попросят ввести имя нового проекта. После чего, ждем пока сгенерируется каркас тестов.

    4.       Из всего сгенерированного кода нас будут интересовать только следующие два метода:

     

      /// <summary>
     ///A test for Create
     ///</summary>
    
     [TestMethod()]
    
     public void CreateTest()
    
     {
    
      string path = string.Empty; // TODO: Initialize to an appropriate value
    
      bool expected = false; // TODO: Initialize to an appropriate value
    
      bool actual;
    
      actual = FileManagement.FileManagement.Create(path);
    
      Assert.AreEqual(expected, actual);
    
      Assert.Inconclusive("Verify the correctness of this test method.");
    
     }
    
     
    
     /// <summary>
     ///A test for ReadAllTextFile
     ///</summary>
    
     [TestMethod()]
    
     public void ReadAllTextFileTest()
    
     {
    
      string path = string.Empty; // TODO: Initialize to an appropriate value
    
      string expected = string.Empty; // TODO: Initialize to an appropriate value
    
      string actual;
    
      actual = FileManagement.FileManagement.ReadAllTextFile(path);
    
      Assert.AreEqual(expected, actual);
    
      Assert.Inconclusive("Verify the correctness of this test method.");
    
     }
    

     

    Собственно они и проверяют наши два метода в классе для работы с файловой системой. Как видно, идея сводится к простому вызову методов в специально отгороженном от основной программы проекте. Это сделано для того, чтобы проверить работу методов не рискуя испортить основное приложение, где эти методы планируется использовать.

    5.       Теперь надо немного отредактировать наши тесты следующим образом:

     

    <span style="white-space:pre">	</span> /// <summary>
     ///A test for Create
     ///</summary>
    
     [TestMethod()]
    
     public void CreateTest()
    
     {
    
      string path = "D:\\test_file.txt";
    
      bool expected = true;
    
      bool actual;
    
      actual = FileManagement.FileManagement.Create(path);
    
      Assert.AreEqual(expected, actual);
    
      Debug.WriteLine("File was created.");
    
     }
    
     
    
     /// <summary>
    
     ///A test for ReadAllTextFile
    
     ///</summary>
    
     [TestMethod()]
    
     public void ReadAllTextFileTest()
    
     {
    
      string path = "D:\\test_file.txt";
    
      string expected = "";
    
      string actual;
    
      actual = FileManagement.FileManagement.ReadAllTextFile(path);
    
      Assert.AreEqual(expected, actual);
    
      Debug.WriteLine("File was readed.");
    
     }
    
        

     

    Здесь:

    Assert.AreEqual(expected, actual)

    Сравнивает предполагаемый результат и полученный на деле. Если результаты не будут равны, то тест будет считаться не пройденным. В противном случае, те если тест будет пройден, то в окне работы данного теста будет выведена строка “File wascreated”. Для использования класса Debug необходимо подключить пространство имен Systems.Diagnostics.

    6.       Теперь можно запустить написанные нами тесты.

    Все созданные тесты помещаются в список тестов. Чтобы вызвать это окно, пройдитесь по следующим меню:

    В появившемся окне Вы увидите наши два теста. Пометьте их галочками. Это будет означать, что их нужно будет выполнить при запуске тестов.

    Для запуска тестов, в этом же окне, нажмем кнопку Run Checked Tests.

    Как видите у меня один из тестов не был завершен успешно:

     

    Если два раза щелкнуть по ошибке, то курсор переместится к тому месту где произошел сбой. Как видно, было выброшено исключение о том, что файл, который мы хотели прочитать не существовал на нашем жестком диске. Как видно, так же это исключение указано в таблице, в столбике Error Message.

    Второй же тест был успешно пройден, о чем свидетельствует стоящая зеленая галочка напротив теста. А это значит, что теперь файл на жестком диске существует и мы его можем прочитать, те теперь первый тест должен успешно быть выполнен. Вы можете убедиться в этом выполнив повторно тесты.

    Чтобы увидеть окно работы каждого теста, то в списке результатов теста нужно щелкнуть два раза по тесту и будет выведено окно, в которое происходили все выводы при работе теста.


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

    В данной статье я хотел показать тем, кто еще не сталкивался на практике с Unit-тестированием, как его использовать и применять в Visual Studio.

     


    E-mail: Svatoslav.Pankratov@gmail.com
    11 ноября 2010 г. 12:56

Все ответы

  • Здравствуйте, Святослав! Полезная статья, спасибо. У меня VS 2010 Exspress, соответственно Unit-тесты мне не доступны. Тем не менее я привык тестировать свое ПО. Я разрабатываю для каждого исходного тестируемого класса соответствующий тестирующий класс, в котором для каждого ключевого варианта исходных данных определяю соответствующий ему вариант результативных данных. При тестировании я задаю исходные данные, а результат сравниваю с результативными, несовпадение - ошибка. Кнопку "Test" я выношу на соответствующую вкладку, а рядом ставлю комбобокс, в котором упаковываю имена классов для тестирования, с включением пункта "All" для пакетного тестирования всех классов. Т.е. по сути делаю то же самое, о чем Вы рассказали в своей статье. Вопрос - что дополнительно могло бы дать мне Unit-тестирование, кроме красивой упаковки, есть ли в Unit-ах какая-либо автоматизация, которая помогла бы мне создавать полноценные тесты? В двух словах, стоит ли заниматься этим глубже?
    1 февраля 2011 г. 14:11
  • ... и тишина... Т.е., насколько я понял, с помощью Unit-тестов можно только лишь организовать тестирование, а собственно тесты каждый программер придумывает сам на свой страх и риск, и каждая более-менее серьезная тема - это космос, вещь в себе, требующая проникновенного внимания. А то мне все говорят, вот используй Unit-тесты и будешь счастлив... Чудес-то не бывает...
    3 февраля 2011 г. 23:11
  • Небольшая автоматизация есть.

    Но Вы правы - чудес не бывает, и если Unit Test написан так, что не охватывает что необходимо, то это не будет охвачено.

    С точки зрения автоматизации - само создание теста (описано в первом посте) происходит бустрее. На каркас можно затратить до 20% времени (а в некоторых случая и больше), а по сути - мартышкин труд.

    Есть автоматизация при коллективной разработке. Например в настройказ TFS сервера можно задать, что нельзя считать код законченным если он не прошел успешно тестирование, более того есть много зависимостей, и есть шанс не получить прохождение теста при изменении в совсем другой части кода. Подобные вещи тоже выявляются (например при ежедневной сборке на сервере) ... в общем плюсы есть, их много.


    Вместо спасибо - помечайте сообщение как полезное, или как ответ. Так будет приятнее.
    Мир Вам! С уважением, Clevelus.
    4 февраля 2011 г. 8:15
  • Clevelus, спасибо! К сожалению не могу пометить Ваш ответ, как полезный, - у меня этот сайт до сих пор глючит. Согласен, если все красиво разложено по полочкам, то одно это - уже польза. А вот такой вопрос - есть ли в Unit-ах возможность отслеживания всех ветвей кода на предмет оттестировано-неоттестировано? Есть инструмент, http://maven.apache.org/index.html, в котором эта возможность есть. Я сам не пробовал, но говорят, что там это есть, кроме прочего. И все наглядно графически представляется - это тоже полезно, и реализовать это самодельно - себе дороже. В общем этим, видимо, стоит заниматься, жаль только, что в VS это доступно только в Profesional версии.
    4 февраля 2011 г. 11:13
  • Всем добрый вечер! Большое спасибо за отзывы. =) Всегда приятно, когда ты что-то делаешь и это кому-то помогает =)

    К сожалению у меня щас очень мало свободного времени, поэтому отвечаю очень мало. 

    Про покрытие кода тестами. Да такая возможность есть. Называется она code coverage. Есть даже видео на сайте TechDays с демонстрацией этой возможности.


    E-mail: Svatoslav.Pankratov@gmail.com Blog: svyatoslavpankratov.blogspot.com
    4 февраля 2011 г. 15:11
  • Святослав, спасибо за информацию! Особенно приятно то, что докладывают наши доморощенные безалаберные программеры - за два года, судя по сообщениям, так и не умудрились решить простой вопрос - добавить громкость, практически ничего не слышно. Но понятно, что в Unit-ах возможность тестового кодового покрытия существует. Будем знать.
    4 февраля 2011 г. 16:44
  • Есть еще так-же хороший материал в библиотеке msdn про тестирование: Проверка кода при помощи модульных тестов
    E-mail: Svatoslav.Pankratov@gmail.com Blog: svyatoslavpankratov.blogspot.com
    4 февраля 2011 г. 17:06
  • В VS есть Code Coverage - индикация протестировано/не протестировано. И даже Test Impact Analysis - анализ изменений в коде, и выявления тестов, которые этими изменениями затронуты. Но это скорее сопутствующие возможности для unit tests. Из бесплатных аналогов - http://sourceforge.net/projects/partcover/ и старые версии nCover (не уверен, давно не проверял).

    На самом деле то, что у вас уже есть - самописная тестовая утилита - это и есть утилита для прогонки unit-тестов. Просто в студии подобный функционал  уже встроен. Кроме студии есть достаточно много сторонних бесплантых тестовых фреймворков - nUnit, xUnit. Для них так же есть интеграция в студию, родная, или в виде утилит вроде Resharper. 


    My blog
    4 февраля 2011 г. 21:45
  • Святослав и PashaPash! Спасибо за очень полезные ссылки! Изучаю!
    4 февраля 2011 г. 23:42
  • > У меня VS 2010 Exspress, соответственно Unit-тесты мне не доступны.

    недоступны встроенные в VS, но есть http://www.nunit.org/ и производные http://www.codeplex.com/site/search?query=nunit&ac=8

    > есть ли в Unit-ах какая-либо автоматизация, которая помогла бы мне создавать полноценные тесты

    http://research.microsoft.com/en-us/projects/pex/

    12 августа 2011 г. 18:19
  • Подскажите пожалуйста, как можно автоматизировать создание unit-тестов в Visual Studio 2012?

    Вот этой команды в VS 2012 просто нет:

    Никакой информации в поиске найти не удалось.

    14 ноября 2012 г. 14:23
  • Согласно этому, такого способа больше нету, теперь unit tests создавать надо так.

    [My blog] [My E-mail]

    15 ноября 2012 г. 5:06