Сразу хочу сказать, что NUnit используется мной при написании автоматических тестов с помощью C# + Selenium WebDriver, но думаю это не играет особой роли при создании инструмента генерации собственного отчёта. Отчёт формируется в формате HTML.
Вот пример отчёта, который генерируется (нажмите на картинку, для увеличения):
Как видим в отчёте имеется информация о том, когда начат тест, когда тест завершён, сколько тестов запущено, сколько выполнено успешно, сколько провалено, каков процент прохождения тестов. Также отчёт имеет ссылку на тест-кейс, на который написан автоматический тест, если тест провален, то есть ссылка на скриншот страницы, который сделан в момент возникновения ошибки. Если вы не используете тесты веба, то вам придётся доработать/изменить отчёт под себя.
Начнём рассматривать код, который нам потребуется для реализации отчёта.
Первым делом не забываем объявить все необходимые переменные.
Далее создадим метод, который будет формировать отчёт.
Создадим «обвязку» для тестов, чтобы формировался отчёт, и чтобы корректно фиксировался результат прохождения теста (успешный/проваленный).
Теперь приведу всю структуру всего работающего кода. Кстати, у меня тесты хранятся в EXE, поэтому структура немного отличается, но явно не запутает тех, кто пишет тесты в виде DLL.
using System; using System.IO; using System.Text.RegularExpressions; using System.Threading; using System.Diagnostics; using System.Collections.Generic; using NUnit.Framework; using Selenium; using OpenQA.Selenium; using OpenQA.Selenium.Interactions; using OpenQA.Selenium.Remote; using OpenQA.Selenium.Firefox; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.PhantomJS; using OpenQA.Selenium.Support.UI; using System.Drawing.Imaging; using System.Reflection; using System.Globalization; namespace TESTING_SPACE { [TestFixture] class Test { public static void Main(string[] args) { string path = Assembly.GetExecutingAssembly().Location; // требуется "using System.Reflection;" NUnit.ConsoleRunner.Program.Main(new[] { path }); } public static IWebDriver driver; public static string iTestNumCurrent = ""; public static bool iExecTestGood = false; public static string iWorkDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); public static string iFolderResultTest = iWorkDir + @"\Report"; public static string iFolderScreen = iFolderResultTest + @"\screen"; public static string iPathReportFile = iFolderResultTest + @"\Report.html"; public static int iTestCountGood = 0; public static int iTestCountFail = 0; [OneTimeSetUp] public void TestFixtureSetUp() { GenerateReport(0, "0", true); ChromeOptions options = new ChromeOptions(); options.AddArguments("--ignore-certificate-errors"); options.AddArguments("--ignore-ssl-errors"); driver = new ChromeDriver(iWorkDir, options); driver.Manage().Window.Maximize(); } [OneTimeTearDown] public void TestFixtureTearDown() { GenerateReport(9, "0", true); driver.Quit(); } [SetUp] public void SetUp() { } [TearDown] public void TearDown() { } [Test] public void TEST_1() { iTestNumCurrent = "WSP-1251"; try { int i = 2 + 2; Assert.True(i > 2); GenerateReport(1, iTestNumCurrent, true); iExecTestGood = true; } catch (Exception ex) { GenerateReport(1, iTestNumCurrent, false, ex.ToString()); iExecTestGood = false; } Assert.True(iExecTestGood); } public static void GenerateReport(int iStep, string iTestNum, bool iResult, string iMessage = "-") { iMessage = iMessage.Replace("<","<").Replace(">", ">"); iMessage = iMessage.Replace("\n", "</br>"); string iTime = String.Format("{0:HH:mm:ss}", DateTime.Now); FileStream fs = new FileStream(iPathReportFile, FileMode.Append, FileAccess.Write); StreamWriter sw = new StreamWriter(fs); if (iStep == 0) { iTestCountGood = 0; iTestCountFail = 0; sw.WriteLine(@"<!DOCTYPE html>" + "\n" + @"<html lang='ru-RU'>" + "\n" + @"<head>" + "\n" + @"<meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />" + "\n" + @"<title>Отчёт о тестировании</title>" + "\n" + @"</head>" + "\n" + @"<body>" + "\n" + @"<div style='font-size:22px;' align='center'><strong>Тест начат: " + String.Format("{0:dd.MM.yyyy HH:mm:ss}", DateTime.Now) + @"</strong></div></br>" + "\n" + @"<table border='1' align='center' cellpadding='5' cellspacing='0' width='100%'>" + "\n" + @"<tr style='text-align:center;'>" + "\n" + @"<td width='100px'><strong>Тест</strong></td>" + "\n" + @"<td width='70px'><strong>Результат</strong></td>" + "\n" + @"<td width='70px'><strong>Время</strong></td>" + "\n" + @"<td width='70px'><strong>Снимок</strong></td>" + "\n" + @"<td><strong>Сообщение</strong></td>" + "\n" + @"</tr>"); } if (iResult == true & iStep == 1) { iTestCountGood = iTestCountGood + 1; sw.WriteLine(@"<tr style='color: green;'>" + "\n" + @"<td><a target='_blank' href='http://jira.ru/browse/" + iTestNum + "'>" + iTestNum + @"</a></td>" + "\n" + @"<td>Успех</td>" + "\n" + @"<td>" + iTime + @"</td>" + "\n" + @"<td>-</td>" + "\n" + @"<td>" + iMessage + @"</td>" + "\n" + @"</tr>" + "\n"); } if (iResult == false & iStep == 1) { iTestCountFail = iTestCountFail + 1; ITakesScreenshot screenshotDriver = Test.driver as ITakesScreenshot; Screenshot screenshot = screenshotDriver.GetScreenshot(); string iNameScreen = iTestNum + "_" + String.Format("{0:yyyy-MM-dd_HH-mm-ss}", DateTime.Now) + ".png"; screenshot.SaveAsFile(iFolderScreen + @"\" + iNameScreen, ImageFormat.Png); sw.WriteLine(@"<tr style='color: red;'>" + "\n" + @"<td><a target='_blank' href='http://jira.ru/browse/" + iTestNum + "'>" + iTestNum + @"</a></td>" + "\n" + @"<td>Провал</td>" + "\n" + @"<td>" + iTime + @"</td>" + "\n" + @"<td><a target='_blank' href='screen/" + iNameScreen + "'>смотреть</a></td>" + "\n" + @"<td>" + iMessage + @"</td>" + "\n" + @"</tr>" + "\n"); } if (iStep == 9) { Decimal iProcent = 0; if (igTestCountGood > 0 || igTestCountFail > 0) { iProcent = ((100 * igTestCountGood) / (igTestCountGood + igTestCountFail)); } sw.WriteLine(@"<tr style='text-align:center;'>" + "\n" + @"<td colspan='5'> </center></td>" + "\n" + @"</tr>" + "\n" + @"<tr style='text-align:center;'>" + "\n" + @"<td colspan='5'>Всего тестов запущено: " + (iTestCountGood + iTestCountFail) + " || <span style='color: green;'>Успешно: " + iTestCountGood + "</span> || <span style='color: red;'>Провалено: " + iTestCountFail + "</span> || Процент пройденных: " + iProcent + "% || Тест завершён: " + String.Format("{0:dd.MM.yyyy HH:mm:ss}", DateTime.Now) + "</td>" + "\n" + @"</tr>" + "\n" + @"</table>" + "\n" + @"</body>" + "\n" + @"</html>"); } sw.Close(); } } }
Обратите внимание, что в первом столбце указывается номер проходимого теста. У нас он привязывается к номеру тест-кейса, которые мы создаём в специальном дополнении к JIRA. Поэтому мы стараемся автотест привязать к тест-кейсу и для этого:
— автотесты именуем как тест-кейсы;
— добавляем ссылку на тест-кейс в отчёте, чтобы с отчёта можно было быстро перейти в тест-кейс, для получения информации о проводимой проверке и исходной информации.
Один из минусов данного метода генерации отчёта — обвязка тестов, которая увеличивает количество строк кода:
iTestNumCurrent = "WSP-1251"; try { GenerateReport(1, iTestNumCurrent, true); iExecTestGood = true; } catch (Exception ex) { GenerateReport(1, iTestNumCurrent, false, ex.ToString()); iExecTestGood = false; } Assert.True(iExecTestGood);
В момент написания и отладки теста я не использую обвязку и только после того как тест отлаживается он помещается в обвязку.
Надо было придумать быстрое решение для корректной визуализации пройденных тестов, поэтому и была придумана генерация отчёта в процессе прохождения тестов.
Возможно позже придумаем парсер стандартных отчётов и будем их парсить не используя обвязки тестов и генераторы отчётов. Возможно интегрировав тесты в систему непрерывной интеграции, мы будем использовать возможности этих систем, тем более судя по отзывам они могут парсить отчёты NUnit, но пока тесты у нас не интегрированы.
Возможно данное решение может кому-нибудь пригодиться.
Обновлено 02.08.2017: система непрерывной интеграции TeamCity не генерирует понятные отчёты, поэтому на данный момент оставили наш самогенерирующийся отчёт, так как он удобен.
Для тех кто использует Selenium WebDriver: в процессе улучшения отчёта я ещё добавил в столбец «Скриншот» вывод URL страницы, на которой тест провалился, для этого надо расширить код там где отображается в отчёте ссылка на скриншот:
<a target='_blank' href='screen/" + iNameScreen + "'>смотреть</a>
заменить на:
<center><a target='_blank' href='screen/" + iNameScreen + @"'>скриншот</a><br/><br/><a href='" + driver.Url + @"' target='_blank'>URL</a></center>