Ресурсы Xaml (например, таблица строк) в модульном тестировании WPF - PullRequest
0 голосов
/ 23 января 2019

У меня есть StringTable.xaml в моем проекте WPF с <system:String x:Key="IDS_DATA_HEADER_TIMED_TEST_RECORD_NUMBER">Record Number</system:String>.Моя модель использует этот StringTable с public static string foobar = (string)Application.Current.FindResource("PLACEHOLDER_TEXT"); Таким образом, я не могу провести модульное тестирование моей модели в MSTest, не зная о xaml.

Этот вопрос занимает нишу, потому что многие вопросы о xaml связаны с тем, как подключиться к GUI.Да, хорошей практикой является использование шаблона MVVM для отделения модели от GUI и только для тестирования модели.Да, у меня, вероятно, слишком тесно связана модель с каркасом GUI, и поэтому я не смогу легко переключиться с WPF на другой.

В моих модульных тестах, если я пытаюсь использовать функцию, которая использует StringTable.xaml, я заметил одну из трех ошибок:

System.Windows.ResourceReferenceKeyNotFoundException: 'PLACEHOLDER_TEXT'ресурс не найден.

или

Исключение нулевого указателя при попытке использовать переменную foobar

или

Ошибка приведения / преобразования при попытке преобразовать необнаруженный ресурс в строку с помощью (string) Application.Current.FindResource ("PLACEHOLDER_TEXT");

Для пояснения, StringTable.xaml добавляется какобъединенный словарь в моем файле App.xaml:

  <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="PlaceholderNamespace/SomeStyles.xaml"/>
                <ResourceDictionary Source="PlaceholderNamespace/NumberTable.xaml"/>
                <ResourceDictionary Source="PlaceholderNamespace/StringTable.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
  </Application.Resources>

Я проследил за публикацией Уэсли в аналогичном переполнении стека и добавил (фрагмент кода точно дублирован):

var app = new App(); //magically sets Application.Current
app.InitializeComponent(); //parses the app.xaml and loads the resources

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

Невозможно создать более одного экземпляра System.Windows.Application в одном и том же домене приложений

Таким образом, я должен запустить каждый модульный тест.Один.В.Время.Это раздражает, поэтому я либо начну пихать весь свой тестовый код в как можно меньшее количество тестов (что отрицательно сказывается на цели), либо буду выполнять их реже, чем нужно, потому что это займет много времени, чтобы пройти мой набор тестов (какой вид побеждает цель).Если у меня есть только в начале первого модульного теста в последовательности, я заметил ранее заявленные ошибки.Это подразумевает, что ресурсы, казалось, выгружались после первого теста.Но AppDomain все еще там.

Я использую MSTest, но прочитал, что NUnit также страдает той же проблемой, что и не создание новых доменов приложений. Ответ Джереми Вибе на переполнение стека о том, что MSTest упоминает, что создание нового домена приложений стоит дорого.Это ответило бы, почему это не создано больше чем один раз, но не как обойти это для моего случая.

Интересно, что я на самом деле вижу, как первый тест прошел успешно, пока второй обрабатывает.Когда оба теста завершены, они оба не пройдут.Это как если бы тест мог задним числом провалиться, потому что домен приложения пытался измениться.

Кто-нибудь знает, как я могу загрузить все ресурсы StringTable.xaml в Application.Current, и сохранить ли его все модульные тесты в последовательности?

1 Ответ

0 голосов
/ 23 января 2019

«Волшебный» метод Один

Аналогично Ответу Маслоу по ссылке в вопросе, вы можете использовать:

        App app = (App) Application.Current;
        if (app == null)
        {
            app = new App();
            app.InitializeComponent();
        }

вверху каждого модульного теста. Это означает, что будет создано и инициализировано только одно приложение. Интересно, что даже если вы возьмете app.InitializeComponent () из оператора if для запуска в каждом тесте, он, похоже, будет иметь внутреннюю проверку, чтобы предотвратить инициализацию одних и тех же ресурсов несколько раз.

«Надежный» метод второй

Я не всегда заставлял Волшебный Метод Один работать, но я не мог отследить, почему это не удалось по непротиворечивой причине. Я создал вспомогательный метод. В каждом модульном тесте используется синтаксическая логика для анализа вашего файла xaml и Application.Current.Resources.Add(key, value) для загрузки каждого значения в домен вашего приложения.

 [TestMethod]
    public async Task whenMethodTwoThenNoError()
    {
        // Arrange
        App app = (App) Application.Current;
        if (app == null)
        {
            app = new App();
        }
        string path = @"C:\Projects\software\PlaceholderProject\PlaceholderNamespace\StringTable.xaml";
        loadXamlResourcesFromFile("string", path);
        string path = @"C:\Projects\software\PlaceholderProject\PlaceholderNamespace\NumberTable.xaml";
        loadXamlResourcesFromFile("double", path);

        // Act
        // Nothing

        // Assert
        Assert.AreEqual(true, true);

    }


  public void loadXamlResourcesFromFile(string currentType, string path)
    {
        string line;
        try
        {
            using (StreamReader sr = new StreamReader(path))
            {
                line = sr.ReadLine();
                while (line != null)
                {
                    try
                    {
                        string keyPrefix = "system:String x:Key=\"";
                        string keySuffix = "\">";
                        string valueSuffix = "</system:String";

                        switch (currentType)
                        {
                            case "double":
                                keyPrefix = keyPrefix.Replace("String", "Double");
                                valueSuffix = valueSuffix.Replace("String", "Double");
                                break;
                            case "OtherType":
                                // TODO: replace text
                                break;
                        }

                        int keyPrefixLength = keyPrefix.Length;
                        int keySuffixLength = keySuffix.Length;


                        int indexBeginKey = line.IndexOf(keyPrefix) + keyPrefixLength;
                        int indexEndKey = line.IndexOf(keySuffix);
                        int indexBeginValue = line.IndexOf(keySuffix) + keySuffixLength;
                        int indexEndValue = line.IndexOf(valueSuffix);


                        if (indexEndValue < 0 && indexBeginKey >= 0) // If we see a key but not the end of a value...
                        { // I read in another line
                            line += sr.ReadLine();
                            indexBeginKey = line.IndexOf(keyPrefix) + keyPrefixLength;
                            indexEndKey = line.IndexOf(keySuffix);
                            indexBeginValue = line.IndexOf(keySuffix) + keySuffixLength;
                            indexEndValue = line.IndexOf(valueSuffix);
                        }

                        if (indexEndValue < 0 && indexBeginKey >= 0) // If we still do not see the end of a value...
                        { // I read in a third line
                            line += sr.ReadLine();
                            indexBeginKey = line.IndexOf(keyPrefix) + keyPrefixLength;
                            indexEndKey = line.IndexOf(keySuffix);
                            indexBeginValue = line.IndexOf(keySuffix) + keySuffixLength;
                            indexEndValue = line.IndexOf(valueSuffix);
                        }

                        // My string table entries are a maximum of three lines. Example:
                        //  < system:String x:Key = "NOTIFICATION_OF_ERROR_REGIONAL_SETTINGS_FAILED_TO_FIND" >
                        // Failed to find regional settings of the current computer. Setting to Invariant Culture. 
                        // Program will use a period (.) for decimal point. </ system:String >

                        int keyLength = indexEndKey - indexBeginKey;
                        int valueLength = indexEndValue - indexBeginValue;

                        string key = line.Substring(indexBeginKey, keyLength);
                        string value = line.Substring(indexBeginValue, valueLength);


                        switch (currentType)
                        {
                            // If this not present, StaticResource.cs may throw TypeInitializationException on line:
                            // public static double FALSE_DOUBLE = (double)Application.Current.FindResource("FALSE_DOUBLE");
                            case "string":
                                Application.Current.Resources.Add(key, value);
                                break;
                            case "double":
                                Application.Current.Resources.Add(key, double.Parse(value));
                                break;
                            case "OtherType":
                                // TODO: add resource
                                break;
                        }

                    }
                    catch (Exception e)
                    {
                        // This catches errors such as if the above code reads in boiler plate text
                        // ( < ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" ),
                        // blank lines, or lines with commentary to myself.

                        // Note: This will not catch commented out lines that have the correct syntax, i.e.,
                        // <!--<system:String x:Key="CALLOUT_CALCULATIONS_IN_PROGRESS">Running calculations...</system:String>-->

                    }
                    finally
                    {
                        line = sr.ReadLine(); // to prepare for the next while loop
                    }
                }
            }
        }
        catch (Exception e)
        {
            throw; // This exception is most likely because of a wrong file path 
        }
    }
...