Добрый день.
Чем больше костылей и подпорок скрывается в коде приложения, тем больше шансов на то, что этот код будет трудно поддерживать и развивать. То же самое относится к коду автоматизированных тестов, но иногда простые решения, пусть не всегда изящные, помогают сэкономить время и помочь в анализе, отладке и запуске тестовых сценариев.
Как известно, правилом хорошего тона является атомарность теста. Тест должен быть простым, информативным, надежным как скала и не зависеть от других тестов. В этом случае тесты можно запускать в любых комбинациях и рассматривать их результаты независимо друг от друга. В таком подходе прослеживается немало преимуществ, но есть два “но”:
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, процесс прожил бы очень недолго.
Всего доброго и до встречи.
Что такое качество программного обеспечения и как его улучшить: теория и практика, задачи и решения, подводные камни и обходные пути.