Добавлен: 28.03.2023
Просмотров: 82
Скачиваний: 2
Листинг 1
1 |
public void Parse() |
|
2 |
{ |
|
3 |
using (FileStream stream = new FileStream(_filePath, FileMode.Open, FileAccess.Read)) |
1 |
4 |
{ |
|
5 |
StreamReader reader = new StreamReader(stream); |
2 |
6 |
while (!reader.EndOfStream) |
3 |
7 |
{ |
|
8 |
string line = reader.ReadLine().ToLower(); |
4 |
9 |
int openBracket; |
|
10 |
string initialForm = ""; |
|
11 |
try |
|
12 |
{ |
|
13 |
openBracket = line.IndexOf('('); |
5 |
14 |
if (openBracket == -1) |
6 |
15 |
throw new DictionaryException(_dictionaryType, |
7 |
16 |
DictionaryExceptionReason. НевозможноПроанализироватьВариантыФорм); |
|
17 |
initialForm = string.Concat(line .Where((c, i) => i < openBracket)).Trim().ToUpper(); |
8 |
18 |
} |
|
19 |
catch |
9 |
20 |
{ |
|
21 |
throw new DictionaryException(_dictionaryType, |
|
22 |
DictionaryExceptionReason. НеОбнаруженаНачальнаяФорма); |
|
23 |
} |
|
24 |
Dictionary[initialForm] = new List<string>() {initialForm}; |
10 |
25 |
Dictionary[initialForm].AddRange(line.Substring(openBracket + 1) |
|
26 |
.Split(new string[] {",", ")"}, |
|
27 |
StringSplitOptions. RemoveEmptyEntries) |
|
28 |
.Select(s => s.Trim().ToUpper()) |
|
29 |
.ToList()); |
|
30 |
} |
11 |
31 |
reader.Close(); |
12 |
32 |
} |
13 |
33 |
} |
Приведенный алгоритм использует язык интегрированных запросов для облегчения чтения и преобразования данных. Все считанные данные хранятся в свойстве Dictionary класса.
Подготовка тестовых вариантов
Для тестирования каждого из независимых путей были созданы тестовые варианты исходных данных. Сами данные (ИД), ожидаемый от них результат (ОЖ. РЕЗ.) и результат, полученный фактически при выполнении программы (Ф. РЕЗ.), приведены в Таблица 5. Тестирование выполняется для справочника «Субъекты».
Таблица 5. Тестовые варианты для метода базового пути
№ |
ИД |
ОЖ. РЕЗ. |
1 |
Файл Субъекты.txt отсутствует |
Выброшено исключение FileNotFoundException, которое затем перехвачено в методе Program.Main. |
2 |
Файл Субъекты.txt не содержит данных |
Свойство DictionaryParser.Dictionary инициализировано пустым словарем. |
3 |
Файл Субъекты.txt содержит одну или несколько корректных строк: Студент (Студента, Студенту, Студентом, Студенте) Студенты (Студентов, Студентам, Студентами, Студентах) |
Свойство DictionaryParser.Dictionary содержит словарь: СТУДЕНТ -> [0]: "СТУДЕНТ" [1]: "СТУДЕНТА" [2]: "СТУДЕНТУ" [3]: "СТУДЕНТОМ" [4]: "СТУДЕНТЕ" СТУДЕНТЫ -> [0]: "СТУДЕНТЫ" [1]: "СТУДЕНТОВ" [2]: "СТУДЕНТАМ" [3]: "СТУДЕНТАМИ" [4]: "СТУДЕНТАХ" |
4 |
Файл Субъекты.txt содержит одну или несколько некорректных строк: Студент Студента, Студенту, Студентом, Студенте) Примечание: отсутствует символ ‘(‘ |
Выброшено исключение DictionaryException, которое затем перехвачено в методе Program.Main. |
Тестирование ветвей и операторов отношений.
В рассматриваемом модуле есть три условных оператора, условия для которых записаны операторами 1, 3, 6. Метод ветвей и операторов отношений будем выполнять только в последнем случае, так как первый реализован средствами .NET Framework, а второй задает условие цикла и будет протестирован позднее:
if (openBracket == -1)
throw new DictionaryException(_dictionaryType,
DictionaryExceptionReason.НевозможноПроанализироватьВариантыФорм);
В этом случае, очевидно, достаточно рассмотреть два случая:
-
-
-
- Переменная openBracket имеет значение -1, то есть строка в файле имеет неверный формат (отсутствует символ ‘(‘ как разделитель начальной формы слова и используемых форм). Тогда будет сгенерировано исключение DictionaryException, которое затем будет обработано в главном цикле программы.
- Переменная openBracket имеет значение, отличное от -1. Тогда программа будет успешно продолжена.
-
-
Тестирование потоков данных.
Определим DU-цепочки и представим их в виде списка цепочек.
Получены следующие цепочки:
-
-
-
- [line, 4, 8]
- [line, 4, 10]
- [openBracket, 5, 6]
- [openBracket, 5, 10]
- [initialForm, 8, 10]
-
-
Исходя из полученного списка цепочек, необходимо проанализировать поведение трех переменных (локальных переменных метод). Так как значения переменной openBracket зависит от line, а значения initialForm – от line и openBracket, то достаточно рассмотреть все требуемые варианты переменной line:
-
-
-
- null
- “”
- Студент (Студента, Студенту, Студентом, Студенте)
- Студент Студента Студенту Студентом Студенте
-
-
Тестирование циклов.
В модуле представлен единственный цикл типа «ПОКА <условие> ВЫПОЛНЯТЬ <действие>».
Для тестировании этого цикла будем записывать в файл различные варианты справочников форм и проверим следующие варианты:
-
-
-
- Единственная строка в файле
- Две строки в файле
- Более двух строк в файле.
-
-
Корректность записанных строк не принципиальна, так как условие некорректной записи было протестировано выше.
Набор модульных тестов.
Каждый тест сопровождается XML-комментарием, который описывает ситуацию и требуемое поведение программы.
#region Parser itself
/// <summary>
/// Проверяет случай отсутствия файла со справочником: все исключения перехвачены
/// </summary>
[Test]
public static void Dictionary_FileNotFound_ExceptionCaught()
{
File.Move("Data/Субъект.txt", "Data/Субъект1.txt");
Assert.Throws<DictionaryException>(() => new DictionaryParser(DictionaryType.Субъект));
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай пустого файла: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_EmptyFile_NoExceptions()
{
File.Move("Data/Субъект.txt", "Data/Субъект1.txt");
File.Delete("Data/Субъект.txt");
var writer = File.CreateText("Data/Субъект.txt");
writer.Close();
DictionaryParser parser = null;
Assert.DoesNotThrow(() => parser = new DictionaryParser(DictionaryType.Субъект));
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
CollectionAssert.AreEquivalent(parser.Dictionary, dictionary);
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай любого числа корректных строк в файле: корректное заверешение работы
/// </summary>
[Test]
public static void Dictionary_AllLinesParsedSuccessfully_NoExceptions()
{
CreateFake();
DictionaryParser parser = null;
Assert.DoesNotThrow(() => parser = new DictionaryParser(DictionaryType.Субъект));
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
dictionary["СТУДЕНТ"] = new List<string>() {"СТУДЕНТ", "СТУДЕНТА", "СТУДЕНТУ", "СТУДЕНТОМ", "СТУДЕНТЕ"};
dictionary["СТУДЕНТЫ"] = new List<string>()
{"СТУДЕНТЫ", "СТУДЕНТОВ", "СТУДЕНТАМ", "СТУДЕНТАМИ", "СТУДЕНТАХ"};
foreach (var key in dictionary.Keys)
{
CollectionAssert.AreEquivalent(parser.Dictionary[key].OrderBy(x => x),
dictionary[key].OrderBy(x => x));
}
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай некорректной записи строки в файле: все исключения перехвачены
/// </summary>
[Test]
public static void Dictionary_InvalidRecord_ExceptionCaught()
{
File.Move("Data/Субъект.txt", "Data/Субъект1.txt");
File.Delete("Data/Субъект.txt");
var writer = File.CreateText("Data/Субъект.txt");
writer.WriteLine("Студент Студента, Студенту, Студентом, Студенте)");
writer.Close();
DictionaryParser parser = null;
Assert.Throws<DictionaryException>(() => parser = new DictionaryParser(DictionaryType.Субъект));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
#endregion
#region Containing
/// <summary>
/// Проверяет случай наличия формы слова в словаре: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_ContainsSpecificForm_NoExceptions()
{
CreateFake();
DictionaryParser parser = new DictionaryParser(DictionaryType.Субъект);
Assert.AreEqual(true, parser.Contains("студентов"));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай отсутствия формы слова в словаре: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_DoesNotContainSpecificForm_ExceptionCaught()
{
CreateFake();
DictionaryParser parser = new DictionaryParser(DictionaryType.Субъект);
Assert.AreEqual(false, parser.Contains("стьюдентс"));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
#endregion
#region Initial form
/// <summary>
/// Проверяет случай наличия начальной формы слова в словаре: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_InitialFormRevealed_NoExceptions()
{
CreateFake();
DictionaryParser parser = new DictionaryParser(DictionaryType.Субъект);
Assert.AreEqual("студенты".ToUpper(), parser.GetInitialForm("студентов"));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай отсутствия начальной формы слова в словаре: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_InitialFormDidNotReveal_ExceptionCaught()
{
CreateFake();
DictionaryParser parser = new DictionaryParser(DictionaryType.Субъект);
Assert.AreEqual("стьюдентов", parser.GetInitialForm("стьюдентов"));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
/// <summary>
/// Проверяет случай отсутствия начальной формы слова в пустом файле: корректное завершение работы
/// </summary>
[Test]
public static void Dictionary_InitialFormDidNotRevealFromEmptyFile_ExceptionCaught()
{
File.Move("Data/Субъект.txt", "Data/Субъект1.txt");
File.Delete("Data/Субъект.txt");
var writer = File.CreateText("Data/Субъект.txt");
writer.Close();
DictionaryParser parser = new DictionaryParser(DictionaryType.Субъект);
Assert.AreEqual("стьюдентов", parser.GetInitialForm("стьюдентов"));
File.Delete("Data/Субъект.txt");
File.Move("Data/Субъект1.txt", "Data/Субъект.txt");
}
#endregion
/// <summary>
/// Вспомогательный метод для подготовки стандартных файлов
/// </summary>
private static void CreateFake()
{
File.Move("Data/Субъект.txt", "Data/Субъект1.txt");
File.Delete("Data/Субъект.txt");
var writer = File.CreateText("Data/Субъект.txt");
writer.WriteLine("Студент (Студента, Студенту, Студентом, Студенте)");
writer.WriteLine("Студенты (Студентов, Студентам, Студентами, Студентах)");
writer.Close();
}
Модуль String.Capitalize()
Алгоритм и код
Программный код модуля приведен в листинге 2.
Листинг 2
1 |
public static string Capitalize(this string s) |
|
2 |
{ |
|
3 |
if (String.IsNullOrEmpty(s)) |
1 |
4 |
return ""; |
2 |
5 |
||
6 |
string res = ""; |
3 |
7 |
res += s[0]; |
|
8 |
||
9 |
for (int i = 1; i < s.Length; i++) |
4 |
10 |
{ |
|
11 |
res += (_delimeters.Contains(s[i - 1]) |
5 |
12 |
? Char.ToUpper(s[i]) |
6 |
13 |
: s[i]); |
7 |
14 |
} |
8 |
15 |
res = res.Remove(" "); |
9 |
16 |
||
16 |
return res; |
10 |
16 |
} |
11 |
Перебирая строку по символам, этот метод исключает все пробелы и знаки препинания из строки и заменяет последующие за ними символы их заглавными вариациями.
Например, строка «Это строка из НЕСКОЛЬКИХ слов» будет преобразована к виду «ЭтоСтрокаИзНесколькихСтрок».
Данный метод является методом расширения для стандартного класса String из .NET Framework. Он используется в качестве вспомогательного для работы с перечислением Quntifier (Квантор) – см. полный программный код в Приложении (или электронном приложении к отчету).
Тестирование базового пути.
Подготовка тестовых вариантов
Для тестирования каждого из независимых путей были созданы тестовые варианты исходных данных. Сами данные (ИД), ожидаемый от них результат (ОЖ. РЕЗ.) и результат, полученный фактически при выполнении программы (Ф. РЕЗ.), приведены в Таблица 6.
Таблица 6. Тестовые варианты для метода базового пути
№ |
ИД |
ОЖ. РЕЗ. |
1 |
null |
Пустая строка |
2 |
Пустая строка |
Пустая строка |
3 |
Это другая строка |
ЭтоДругаяСтрока |
4 |
Это Еще Одна Строка |
ЭтоЕщеОднаСтрока |
Тестирование ветвей и операторов отношений.
В рассматриваемом модуле есть три условных оператора, условия для которых записаны операторами 1, 4 (условие выхода из цикла) и 5 (в формате тернарного оператора – логической операции). Выполним тестирование для последнего оператора:
res += (_delimeters.Contains(s[i - 1])
? Char.ToUpper(s[i]) : s[i]);
В этом случае, очевидно, достаточно рассмотреть два случая (для некоторого индекса символа в строке i):
-
-
-
- Предыдущий символ есть символ-разделитель, то есть пробел или знак препинания. Тогда к результирующей строке добавляем текущий символ, выведенный в верхний регистр.
- Иначе, добавляем сам символ.
-
-
Достаточными будут следующие тест со строкой, в которой присутствуют как символы-разделители, так и буквенно-числовые символы.
Тестирование потоков данных.
Выполнение тестов потоков данных не представляется целесообразным, так как единственная используемая локальная переменная (кроме переменной итерации) изменяется на каждой итерации цикла и будет протестирована позднее (см. раздел «Тестирование циклов»).
Тестирование циклов.
В модуле представлен единственный цикл типа «ДЛЯ КАЖДОГО <переменная_итерации> ВЫПОЛНЯТЬ <действие>».