Создание юнит тестов. C#
Имеется программа с графическим редактором. Программа позволяет создавать вершины, соединять их дорогами и считать кратчайший путь. Прикрепляю кусочек кода. Я в юнит тестах полный 0, но требуют обязательно. Сам код:
public StepInfo Step()
{
if (this.queue.Count > 0)
{
/// Берем новую вершину, если с текущей уже все обошли
if (lastVisitedVertex == this.vertexCount)
{
currentVertex = this.GetNextVertex();
}
/// Проверяем текущую вершину
if (this.G[currentVertex, lastVisitedVertex] < 0.0)
throw new ArgumentException("Граф содержит отрицательные дуги!");
if (this.G[currentVertex, lastVisitedVertex] > 0.0 && this.dist[lastVisitedVertex] > this.dist[currentVertex] + this.G[currentVertex, lastVisitedVertex])
{
this.dist[lastVisitedVertex] = this.dist[currentVertex] + G[currentVertex, lastVisitedVertex];
this.path[lastVisitedVertex] = currentVertex;
}
/// Создаем структуру, в которой будем хранить информацию о текущем шаге
StepInfo stepInfo = new StepInfo()
{
From = currentVertex,
To = lastVisitedVertex,
Dist = new double[this.dist.Length]
};
/// Копируем текущее состояние путей в историю шага
Array.Copy(dist, stepInfo.Dist, this.dist.Length);
/// Переходим к следующей вершине
lastVisitedVertex++;
/// Возвращаем информацию о текущем шаге
return stepInfo;
}
return new StepInfo()
{
From = -1,
To = -1,
Dist = new double[dist.Length]
};
}
}
}
Сделал такой тестик, но как понял, сформирован он неправильно:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Step1
{
[TestClass]
public class Step1Test
{
[TestMethod]
public void Doroga_20and40_60returned()
{
//arrange
int x = 20;
int y = 40;
int expected = 60;
//act
Step1Test c = new Step1Test();
int actual = c.Sum(x, y);
//assert
Assert.AreEqual(expected, actual);
}
}
}
По итогу имею такой вид
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ProgLineal;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ProgLineal.Tests
{
[TestClass()]
public class DijkstraTests
{
[TestMethod()]
public void StepTest()
{
//arrange
int x = 20;
int y = 40;
int expected = 60;
//StepTest c = new StepTest();
int actual = ...
Assert.AreEqual(expected, actual);
}
}
}
Что именно мне нужно написать в actual, чтобы получить сумму 20 и 40? И нужно ли писать StepTest c = new StepTest(); или там что-то другое должно быть?
Моя конструкция теста, если так можно сказать, скорее подходит для проверки данного блока:
{
this.dist[lastVisitedVertex] = this.dist[currentVertex] + G[currentVertex, lastVisitedVertex];
this.path[lastVisitedVertex] = currentVertex;
}
Решил добавить весь файлик:
using System;
using System.Collections.Generic;
namespace ProgLineal
{
/// Структура, которая хранит информацию о том, что было сделано в определенном шаге.
public struct StepInfo
{
public int From { get; set; }
public int To { get; set; }
/// Хранит состояние путей в каждом отдельном шаге
public double[] Dist { get; set; }
}
public class Dijkstra
{
private double[,] G;
private int vertexCount;
/// Содержит последнюю просмотренную вершину
private int lastVisitedVertex = 0;
/// Отвечает за текущую вершину, в которой сейчас находится алгоритм
private int currentVertex;
private List<int> queue = new List<int>();
/// Массивы с расстояниями до узлов и как туда добраться
public double[] dist { get; private set; }
public int[] path { get; private set; }
/// Начальные "настройки"
private void Initialize(int s, int len)
{
this.dist = new double[len];
this.path = new int[len];
for (int index = 0; index < len; ++index)
{
this.dist[index] = double.PositiveInfinity;
this.queue.Add(index);
}
/// Устанавливаем расстояние 0 для начальной точки
this.dist[s] = 0.0;
this.path[s] = -1;
}
/// Извлекает из очереди следующий узел для оценки
private int GetNextVertex()
{
double num1 = double.PositiveInfinity;
int num2 = -1;
/// Поиск в очереди, чтобы найти следующий узел с наименьшим расстоянием
foreach (int index in this.queue)
{
if (this.dist[index] <= num1)
{
num1 = this.dist[index];
num2 = index;
}
}
this.queue.Remove(num2);
lastVisitedVertex = 0;
return num2;
}
public Dijkstra(double[,] G, int s)
{
/// Проверяем график что он действительно что-то содержит
int len = G.GetLength(0) >= 1 && G.GetLength(0) == G.GetLength(1) ? G.GetLength(0) : throw new ArgumentException("Error, Нет узлов для расчёта");
this.G = G;
this.vertexCount = len;
this.Initialize(s, len);
// получаем первую вершину для обсмотра
currentVertex = this.GetNextVertex();
}
public StepInfo Step()
{
if (this.queue.Count > 0)
{
/// Берем новую вершину, если с текущей уже все обошли
if (lastVisitedVertex == this.vertexCount)
{
currentVertex = this.GetNextVertex();
}
/// Проверяем текущую вершину
if (this.G[currentVertex, lastVisitedVertex] < 0.0)
throw new ArgumentException("Граф содержит отрицательные дуги!");
if (this.G[currentVertex, lastVisitedVertex] > 0.0 && this.dist[lastVisitedVertex] > this.dist[currentVertex] + this.G[currentVertex, lastVisitedVertex])
{
this.dist[lastVisitedVertex] = this.dist[currentVertex] + G[currentVertex, lastVisitedVertex];
this.path[lastVisitedVertex] = currentVertex;
}
/// Создаем структуру, в которой будем хранить информацию о текущем шаге
StepInfo stepInfo = new StepInfo()
{
From = currentVertex,
To = lastVisitedVertex,
Dist = new double[this.dist.Length]
};
/// Копируем текущее состояние путей в историю шага
Array.Copy(dist, stepInfo.Dist, this.dist.Length);
/// Переходим к следующей вершине
lastVisitedVertex++;
/// Возвращаем информацию о текущем шаге
return stepInfo;
}
return new StepInfo()
{
From = -1,
To = -1,
Dist = new double[dist.Length]
};
}
}
}
Ответы (1 шт):
Для того, чтобы протестировать метод, набор тестов должен
а) подавать что-то на вход. А для этого вход должен быть: у вас сейчас на вход ничего не подаётся, поэтому тест можно написать только один. Поэтому если вы хотите, чтобы метод стал тестируемым, вам нужно переделать метод так, чтобы на вход подавалась скажем queue
б) смотреть что выдаётся на выходе. Это у вас уже есть.
Поэтому самый тривиальный вариант когда вы будете тестировать метод -- это когда у вас метод будет выглядеть как:
public int Step(int[] queue, int x, int y)
А ваш тест будет выглядеть так:
[TestMethod()]
public void StepTest()
{
//arrange
int x = 20;
int y = 40;
int[] queue = new int[] { 42, 0 };
int expected = 60;
// act
var tested = new MyClassWithMethodStep();
StepTest actual = tested .Step(queue, x, y);
// assert
Assert.AreEqual(expected, actual);
}
Также надо понимать, что в реальности либо у вас на выходе более сложная структура, чем int поэтому Assert должен сравнить все внутренности полученного:
public StepInfo Step(int x, int y)
и ваш тест имеет примерный вид:
[TestMethod()]
public void StepTest()
{
//arrange
int x = 20;
int y = 40;
StepInfo expected = new StepInfo { From = x, To = y, Dist = 60 } ;
// act
var tested = new MyClassWithMethodStep();
StepInfo actual = tested.Step(queue, x, y);
// assert
Assert.AreEqual(expected, actual);
}
В подобных случаях в StepInfo должны быть сгененированы equality members чтобы не писать в каждом тесте:
// assert
Assert.AreEqual(expected.From, actual.From);
Assert.AreEqual(expected.To, actual.To );
Assert.AreEqual(expected.Dist, actual.Dist );
Я оставил за рамками вопрос о том, что нужно передать на конструктор, предположив что "ничего не надо". Но вероятно ваш класс MyClassWithMethodStep в котором содержится тестируемый метод Step может иметь конструктор -- поэтому число тестов может увеличиться.
Итого.
Что именно мне нужно написать в actual
Вам нужно понять, что в текущем виде ничего вы в actual не напишете: класс не пригоден для тестирования, вероятно без некоторой переделки класса не обойтись. Также внимательно почитайте приведённый мной код, он должен натолкнуть вас на мысли как должно быть сделано.
Вы не привели код вашего класса целиком, но глядя на this.queue, this.G, this.dist, this.path я понимаю, что у вас будут проблемы с тестированием.
Потому что либо:
ваш метод должен не зависеть от
this.queue,this.G,this.dist,this.pathи тогда вы сможете протестировать его;вы передадите эти переменные как входные параметры;
либо они все должны быть рассчитаны при вызове конструктора класса;
(обычно дополняет предыдущий пункт) либо они должны быть рассчитаны после вызова дополнительных методов типа
MyClassWithMethodStep.Init(.
и тут вам сразу пригодятся навыки проектирования красивых чистых классов, в первую очередь принцип единой ответственности, когда каждый метод (ещё лучше - класс с одним публичным методом) отвечат за что-то одно и делает это просто и хорошо; навыки декомпозиции сложного алгоритма на более простые и инкапсуляции, чтобы у вас лишнее не светилось за рамками класса.