вторник, 11 января 2011 г.

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

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

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

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

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

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

using System;
using System.Text;
using System.IO;
using System.Windows.Forms;

namespace Drivers.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.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.ReadAllTextFile(path);
            
Assert.AreEqual(expected, actual);
            
Assert.Inconclusive("Verify the correctness of this test method.");
        }

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

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

        /// <summary>
        
///A test for Create
        
///</summary>
        [
TestMethod()]
        
public void CreateTest()
        {
            
string path = "D:\\test_file.txt";
            
bool expected = true;
            
bool actual;
            actual = 
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.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.

Комментариев нет:

Отправить комментарий