OpenQuality.ru

Качество программного обеспечения

Качество программного обеспечения: в главных ролях

Лента  Радар  Блог  Опыт  
Разум  Видео  Заметки  Эпизоды


О бедных процессах замолвите слово

Добрый день.

 

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

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

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

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

Безусловно, нет смысла добавлять в систему автотестов сценарий, о котором заранее известно, что он ненадежен, и даже понятно, что является тому причиной. Но если тест в 99,9% случаев отрабатывает успешно, и на текущий момент неясна причина его неработоспособности в 0,1% запусков, то нет смысла от него отказываться. Но что если в этих 0,1% случаев тест зависает и препятствует выполнению других тестов? Здесь не хочется “подталкивать” тест, особенно если это происходит ночью. Вместо этого можно мониторить процессы и убивать их, если становится очевидно, что процесс завис. К примеру, если в тесте используется psexec или plink для выполнения команд на удаленных системах, и эти утилиты зависают в силу непредвиденных причин, то можно убивать эти процессы, оставлять соответствующую запись в логе и расчищать дорогу для следующих тестов (если в этом есть смысл).

Ниже представлен вариант реализации такого наблюдателя за процессами, написанного на C# (без претензий на универсальность и красоту):

 
String KillLog = ConfigurationManager.AppSettings["LogFile"];
 
String keepAliveConfigString = ConfigurationManager.AppSettings["OpKeepAlive"];
 
int.TryParse(keepAliveConfigString, out KeepAlive);
 
int HowManyIntervalsToLive = KeepAlive / IntervalToCheck;
 
ArrayList processNames = new ArrayList();
 
String processNamesConfigLine = ConfigurationManager.AppSettings["OpProcessesToKill"];
 
String[] processNamesFromConfig = processNamesConfigLine.Split(',');
 
foreach (string processName in processNamesFromConfig)
{
	processNames.Add(processName);
}
 
Dictionary<int, int> processDict = new Dictionary<int, int>();
 
while (true)
{
	Process[] processList = Process.GetProcesses();
 
	foreach (Process process in processList)
	{
 
		for (int i = 0; i < processNames.Count; i++)
		{
 
			if (process.ProcessName.Equals(processNames[i]))
			{
 
				if (!processDict.ContainsKey(process.Id))
				{
					processDict.Add(process.Id, 1);
				}
				else
				{
					processDict[process.Id]++;
				}
 
 
                            	if (processDict[process.Id] > HowManyIntervalsToLive)
                            	{
 
                                	String objectQuery = "Select * from Win32_Process Where ProcessId = '" 
											+ process.Id + "'";
 
                                	ManagementObjectSearcher mos = new ManagementObjectSearcher(objectQuery);
 
                                	String processCmdLine = "";
 
                                	foreach (ManagementObject mo in mos.Get())
                                	{
                                    		processCmdLine = mo["CommandLine"].ToString();
                                	}
 
                                	process.Kill();
 
                                	processDict.Remove(process.Id); // Don't forget to remove the key!
 
                                	String LogMessage = "Process: " + process.ProcessName + 
						"; ID: " + process.Id +
                                        	"; Command line: " + processCmdLine +
                                        	"; Killed at " + DateTime.Now.ToString() + "\n";
 
                                	System.IO.File.AppendAllText(KillLog, LogMessage);
 
                            	}
 
			}
		}
 
	}
 
	Thread.Sleep(IntervalToCheck);
}

В app.config задается список процессов (разделены запятой), допустимое время их жизни (лучше с запасом) и путь к логу. Каждые IntervalToCheck секунд получаем список процессов и анализируем, а не пора ли убить зависший процесс.

В ходе создания утилиты какое-то время был жив баг, заключавшийся в неудалении ключа словаря (processDict.Remove(process.Id);) после удаления процесса. Не факт, что баг проявил бы себя, но назначь система новому процессу из нашего списка тот же Id, процесс прожил бы очень недолго.

Всего доброго и до встречи.

Отправить в Twitter, Facebook, FriendFeed, ВКонтакте | Опубликовано 12.06.2013 в рубрике "Автоматизация"

Комментарии


Добавить комментарий

Пожалуйста, исправьте результат: дважды два равно



КРАТКОЕ СОДЕРЖАНИЕ

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


ПУТЕВОДИТЕЛЬ

Список всех статей с краткой аннотацией и разбивкой по рубрикам. Открыть карту.

ПОДПИСКА

Доступ к самым интересным материалам по электропочте и RSS. Подробности.

ИЩЕЙКА