Что такое модульный тест? [дубликат]

Это - невоспитанность для использования this в операторах блокировки, потому что это обычно находится вне контроля, кто еще мог бы соединять тот объект.

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

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

Наконец, существует распространенное заблуждение, которое lock(this) на самом деле изменяет объект, передал в качестве параметра, и в некотором роде делает его только для чтения или недоступным. Это ложь . Объект передал в качестве параметра lock, просто служит ключ . Если блокировка уже прикрепляется, что ключ, блокировка не может быть сделана; иначе блокировка позволяется.

Поэтому это плохо для использования строк в качестве ключей lock операторы, так как они являются неизменными и являются общими/доступными через части приложения. Необходимо использовать частную переменную вместо этого, Object, экземпляр сделает приятно.

Выполнение следующие C# кодируют как пример.

public class Person
{
    public int Age { get; set;  }
    public string Name { get; set; }

    public void LockThis()
    {
        lock (this)
        {
            System.Threading.Thread.Sleep(10000);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var nancy = new Person {Name = "Nancy Drew", Age = 15};
        var a = new Thread(nancy.LockThis);
        a.Start();
        var b = new Thread(Timewarp);
        b.Start(nancy);
        Thread.Sleep(10);
        var anotherNancy = new Person { Name = "Nancy Drew", Age = 50 };
        var c = new Thread(NameChange);
        c.Start(anotherNancy);
        a.Join();
        Console.ReadLine();
    }

    static void Timewarp(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // A lock does not make the object read-only.
        lock (person.Name)
        {
            while (person.Age <= 23)
            {
                // There will be a lock on 'person' due to the LockThis method running in another thread
                if (Monitor.TryEnter(person, 10) == false)
                {
                    Console.WriteLine("'this' person is locked!");
                }
                else Monitor.Exit(person);
                person.Age++;
                if(person.Age == 18)
                {
                    // Changing the 'person.Name' value doesn't change the lock...
                    person.Name = "Nancy Smith";
                }
                Console.WriteLine("{0} is {1} years old.", person.Name, person.Age);
            }
        }
    }

    static void NameChange(object subject)
    {
        var person = subject as Person;
        if (person == null) throw new ArgumentNullException("subject");
        // You should avoid locking on strings, since they are immutable.
        if (Monitor.TryEnter(person.Name, 30) == false)
        {
            Console.WriteLine("Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string \"Nancy Drew\".");
        }
        else Monitor.Exit(person.Name);

        if (Monitor.TryEnter("Nancy Drew", 30) == false)
        {
            Console.WriteLine("Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!");
        }
        else Monitor.Exit("Nancy Drew");
        if (Monitor.TryEnter(person.Name, 10000))
        {
            string oldName = person.Name;
            person.Name = "Nancy Callahan";
            Console.WriteLine("Name changed from '{0}' to '{1}'.", oldName, person.Name);
        }
        else Monitor.Exit(person.Name);
    }
}

Консоль произвела

'this' person is locked!
Nancy Drew is 16 years old.
'this' person is locked!
Nancy Drew is 17 years old.
Failed to obtain lock on 50 year old Nancy, because Timewarp(object) locked on string "Nancy Drew".
'this' person is locked!
Nancy Smith is 18 years old.
'this' person is locked!
Nancy Smith is 19 years old.
'this' person is locked!
Nancy Smith is 20 years old.
Failed to obtain lock using 'Nancy Drew' literal, locked by 'person.Name' since both are the same object thanks to inlining!
'this' person is locked!
Nancy Smith is 21 years old.
'this' person is locked!
Nancy Smith is 22 years old.
'this' person is locked!
Nancy Smith is 23 years old.
'this' person is locked!
Nancy Smith is 24 years old.
Name changed from 'Nancy Drew' to 'Nancy Callahan'.
7
задан Community 23 May 2017 в 12:33
поделиться

7 ответов

Я сейчас Java, до этого C ++, до этого C. Я полностью убежден, что каждая часть работы, которую я проделал, за которую мне теперь не стыдно, была улучшена стратегиями тестирования Я выбрал. Больно воздерживаться от тестирования.

Я уверен, что вы протестируете код, который пишете. Какие техники вы используете? Например, вы можете сидеть в отладчике, проходить код и смотреть, что происходит. Вы можете выполнить код с некоторыми тестовыми данными, которые вам кто-то дал. Вы можете разработать определенные входные данные, потому что знаете, что ваш код имеет некоторые интересные особенности поведения для определенных входных значений. Предположим, в вашем материале используется чужой материал, который еще не готов, вы создаете макет его кода, чтобы ваш код мог работать хотя бы с некоторыми ложными ответами

Во всех случаях вы можете в какой-то степени проходить модульное тестирование. Последнее особенно интересно - вы очень много тестируете изолированно, тестируете свой UNIT, даже если их еще не готов.

Мое мнение:

1). Очень полезны тесты, которые можно легко повторить, - выявляйте нескончаемые поздние дефекты. Напротив, тестирование в отладчике ошеломляет.

2). Построение интересных тестов во время написания кода или ПЕРЕД написанием кода заставляет вас сосредоточиться на второстепенных случаях. Эти раздражающие нулевые и нулевые входы, те, которые «сбиты с толку». Я считаю, что лучший код получается в результате хороших модульных тестов.

3). Есть расходы на поддержание тестов. В общем, оно того стоит, но не стоит недооценивать усилия по поддержанию их работы.

4). Может быть тенденция к чрезмерному увеличению количества модульных тестов. По-настоящему интересные ошибки, как правило, не появляются при интеграции частей. Вы заменяете библиотеку, над которой вы издевались, настоящей, и Ло! Это не совсем то, что написано на банке. Также существует роль ручного или экспериментального тестирования. Проницательный человек-испытатель находит особые недостатки.

9
ответ дан 6 December 2019 в 15:25
поделиться

По пунктам:

1) Что такое модульный тест?

Модульный тест - это тест программного обеспечения, предназначенный для проверки одной отдельной единицы функциональности программного обеспечения.

2) Я понимаю, что по сути вы пытаетесь изолировать атомарные функции, но как вы это проверяете?

Модульные тесты на самом деле являются хорошим способом обеспечить соблюдение определенных принципов проектирования; один из их аспектов заключается в том, что они действительно оказывают тонкое, но значительное влияние на дизайн кода. Дизайн для тестирования - важная вещь; возможность тестировать (или нет) определенный фрагмент кода может быть очень важным; когда используются модульные тесты, дизайн имеет тенденцию перемещаться в сторону «более атомарной» стороны спектра.

3) Когда это необходимо?

По этому поводу есть много разных мнений. Некоторые говорят, что это всегда необходимо, некоторые говорят это ' совершенно не нужно. Я утверждаю, что большинство разработчиков, имеющих опыт работы с модульным тестированием, скажут, что модульные тесты необходимы для любого кода критического пути, дизайн которого поддается модульному тестированию (я знаю, что это немного замкнутый круг, но см. № 2 выше). 128] Когда это смешно? Вы можете привести пример?

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

  1. В основном я слышу об этом от разработчиков Java на этом сайте, может быть, это относится к объектно-ориентированным языкам?

Нет, это действительно применимо к любому программному обеспечению. Методология модульного тестирования достигла зрелости в среде Java, но она действительно применима к любому языку или среде.

  1. О чем все это?

Модульное тестирование, на самом базовом уровне, заключается в проверке и подтверждении того, что поведение, которое ожидается от единицы кода, ДЕЙСТВИТЕЛЬНО является тем, что делает код.

1
ответ дан 6 December 2019 в 15:25
поделиться

Думаю, главный вопрос сейчас в том, проверяете ли вы на этом этапе свой код или компилятор?

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

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

Несколько более простой подход может заключаться в том, чтобы сохранить каталог с плохим кодом и заставить сценарий компилировать каждый файл по одному. Имейте там флаг '#ifdef MAKEFAIL', который включает точное условие, которое должно завершиться ошибкой. Убедитесь, что компилятор возвращает 0, когда вы не t установить этот флаг и ненулевое значение, когда вы это сделаете. Это предполагает, что компилятор возвращает ненулевое значение при ошибке ... Я не знаю, следует ли MSVC этому правилу.

Третий вариант, который я выброшу для решения проблемы переносимости, - это autoconf. Это может быть сложно настроить, но частично его цель состояла в том, чтобы убедиться, что у вас есть нормальная среда разработки перед компиляцией. Вы можете добавить в него такой тест, и он позволит найти компилятор и попробовать его.

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

Метод:

    /// <summary>
    /// 
    /// </summary>
    /// <param name="effectiveDate"></param>
    /// <param name="lastCouponDate"></param>
    /// <returns></returns>
    private Int32 CalculateNumberDaysSinceLastCouponDate(DateTime effectiveDate, DateTime lastCouponDate)
    {
        Int32 result = 0;

        if (lastCouponDate.Month == effectiveDate.Month)
        {
            result = this._Parameters.DayCount.GetDayOfMonth(effectiveDate) - lastCouponDate.Day;
        }
        else
        {
            result = this._Parameters.DayCount.GetNumberOfDaysInMonth(lastCouponDate)
                - lastCouponDate.Day + effectiveDate.Day;
        }

        return result;
    }

Методы тестирования:

Примечание: Я бы сейчас называл их по-другому, вместо цифр я бы в основном помещайте резюме в имя метода.

    /// <summary>
    ///A test for CalculateNumberDaysSinceLastCouponDate
    ///</summary>
    [TestMethod()]
    [DeploymentItem("WATrust.CAPS.DataAccess.dll")]
    public void CalculateNumberDaysSinceLastCouponDateTest1()
    {
        AccruedInterestCalculationMonthly_Accessor target = new AccruedInterestCalculationMonthly_Accessor();
        target._Parameters = new AccruedInterestCalculationMonthlyParameters();
        target._Parameters.DayCount = new DayCount(13);
        DateTime effectiveDate = DateTime.Parse("04/22/2008");
        DateTime lastCouponDate = DateTime.Parse("04/15/2008");
        int expected = 7;
        int actual;

        actual = target.CalculateNumberDaysSinceLastCouponDate(effectiveDate, lastCouponDate);

        Assert.AreEqual(expected, actual);

        WriteToConsole(expected, actual);
    }

    /// <summary>
    ///A test for CalculateNumberDaysSinceLastCouponDate
    ///</summary>
    [TestMethod()]
    [DeploymentItem("WATrust.CAPS.DataAccess.dll")]
    public void CalculateNumberDaysSinceLastCouponDateTest2()
    {
        AccruedInterestCalculationMonthly_Accessor target = new AccruedInterestCalculationMonthly_Accessor();
        target._Parameters = new AccruedInterestCalculationMonthlyParameters();
        target._Parameters.DayCount = new DayCount((Int32)
            DayCount.DayCountTypes.ThirtyOverThreeSixty);

        DateTime effectiveDate = DateTime.Parse("04/10/2008");
        DateTime lastCouponDate = DateTime.Parse("03/15/2008");
        int expected = 25;
        int actual;

        actual = target.CalculateNumberDaysSinceLastCouponDate(effectiveDate, lastCouponDate);

        Assert.AreEqual(expected, actual);

        WriteToConsole(expected, actual);
    }            

Где это смешно?

Каждому свое ... Чем больше вы будете это делать, вы найдете, где это полезно, а где это кажется "смешным", но лично я не использовать его для тестирования моей базы данных так, как это сделали бы большинство хардкорных юнит-тестеров ... В том смысле, что у меня есть скрипты для восстановления схемы базы данных, повторного заполнения базы данных тестовыми данными и т. д. Я обычно пишу метод модульного теста для вызова моего DataAccess и пометьте его суффиксом Debug, например: FindLoanNotes_Debug (), и я помещаю System.Diagnostics.Debugger.Break (), поэтому, если я запустил их в режиме отладки, я мог вручную проверить свои результаты.

2
ответ дан 6 December 2019 в 15:25
поделиться

В компьютерном программировании модульное тестирование - это метод проверки и валидации программного обеспечения, при котором программист проверяет пригодность отдельных единиц исходного кода для использования. Единица - это самая маленькая тестируемая часть приложения. В процедурном программировании единицей может быть отдельная программа, функция, процедура и т. Д., В то время как в объектно-ориентированном программировании A.inverse () * A == Матрица :: Идентичность

0
ответ дан 6 December 2019 в 15:25
поделиться

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

Я мог бы написать программу-калькулятор, которая выглядит красиво, имеет кнопки, выглядит как калькулятор TI-что угодно, и он может дать 2 + 2 = 5. Выглядит неплохо, но вместо того, чтобы отправлять каждую итерацию некоторого кода тестировщику-человеку с длинным списком проверок, я, разработчик, могу запускать некоторые автоматизированные, закодированные модульные тесты для моего кода.

По сути, модульный тест должен быть протестированным самим, коллегами или другим внимательным обзором, чтобы ответить: «Это тестирование то, что я хочу?»

Модульный тест будет иметь набор «Данных» или «Входных данных» и сравнить их с ожидаемыми » Результаты ».

Конечно, существуют разные методологии того, как, когда, и сколько использовать модульные тесты (проверьте SO на некоторые вопросы по этим строкам). Однако в самом простом случае это программа или загружаемый модуль какой-либо другой программы, которая делает утверждения .

Стандартная грамматика для модульного теста может состоять в том, чтобы иметь строку кода который выглядит следующим образом: Assert.AreEqual (a, b) .

Тело метода модульного тестирования может настраивать входы и фактический выход и сравнивать его с ожидаемым выходом.

HelloWorldExample helloWorld = new HelloWorldExample();
string expected = "Hello World!";
string actual = helloWorld.GetString();

Assert.AreEqual( expected, actual );

Если ваш модульный тест написан на языке определенного фреймворка ( например jUnit, NUnit и т. Д.), Результаты каждого метода, отмеченного как часть «тестового прогона», будут объединены в набор результатов тестирования, таких как красивый график из красных точек для сбоя и зеленых точек для успеха, и / или файл XML и т. д.

В ответ на ваши последние комментарии, «Теория» может дать некоторое представление о реальном мире. TDD, Test Driven Development, много говорит о том, когда и как часто использовать тесты. В моем последнем проекте мы не придерживались TDD, но мы обязательно использовали модульные тесты, чтобы убедиться, что наш код выполняет то, что должен был делать.

Допустим, вы выбрали реализацию интерфейса Car. Интерфейс Car выглядит следующим образом:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}

Вы выбираете реализацию интерфейса Car в классе FordTaurus:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}

Вы предполагаете, что для замедления FordTaurus необходимо передать отрицательное значение. Однако предположим, что у вас есть набор модульных тестов, написанных для интерфейса Car, и они выглядят следующим образом:

public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}

Тест говорит вам, что, возможно, вы неправильно реализовали интерфейс автомобиля.

Разработка через тестирование много говорит о том, когда и как часто использовать тесты. В моем последнем проекте мы не придерживались TDD, но мы обязательно использовали модульные тесты, чтобы убедиться, что наш код выполняет то, что должен был делать.

Допустим, вы выбрали реализацию интерфейса Car. Интерфейс Car выглядит следующим образом:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}

Вы выбираете реализацию интерфейса Car в классе FordTaurus:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}

Вы предполагаете, что для замедления FordTaurus необходимо передать отрицательное значение. Однако предположим, что у вас есть набор модульных тестов, написанных для интерфейса Car, и они выглядят следующим образом:

public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}

Тест говорит вам, что, возможно, вы неправильно реализовали интерфейс автомобиля.

Разработка через тестирование много говорит о том, когда и как часто использовать тесты. В моем последнем проекте мы не придерживались TDD, но мы обязательно использовали модульные тесты, чтобы убедиться, что наш код выполняет то, что должен был делать.

Допустим, вы выбрали реализацию интерфейса Car. Интерфейс Car выглядит следующим образом:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}

Вы решили реализовать интерфейс Car в классе FordTaurus:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}

Вы предполагаете, что для замедления FordTaurus необходимо передать отрицательное значение. Однако предположим, что у вас есть набор модульных тестов, написанных для интерфейса Car, и они выглядят следующим образом:

public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}

Тест говорит вам, что, возможно, вы неправильно реализовали интерфейс автомобиля.

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

Допустим, вы выбрали реализацию интерфейса Car. Интерфейс Car выглядит следующим образом:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}

Вы решили реализовать интерфейс Car в классе FordTaurus:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}

Вы предполагаете, что для замедления FordTaurus необходимо передать отрицательное значение. Однако предположим, что у вас есть набор модульных тестов, написанных для интерфейса Car, и они выглядят следующим образом:

public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}

Тест говорит вам, что, возможно, вы неправильно реализовали интерфейс автомобиля.

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

Допустим, вы выбрали реализацию интерфейса Car. Интерфейс Car выглядит следующим образом:

interface ICar
{
    public void Accelerate( int delta );
    public void Decelerate( int delta );
    public int GetCurrentSpeed();
}

Вы выбираете реализацию интерфейса Car в классе FordTaurus:

class FordTaurus : ICar
{
    private int mySpeed;
    public Accelerate( int delta )
    {
        mySpeed += delta;
    }
    public Decelerate( int delta )
    {
        mySpeed += delta;
    }
    public int GetCurrentSpeed()
    {
        return mySpeed;
    }
}

Вы предполагаете, что для замедления FordTaurus необходимо передать отрицательное значение. Однако предположим, что у вас есть набор модульных тестов, написанных для интерфейса Car, и они выглядят следующим образом:

public static void TestAcceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Accelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed > oldSpeed );
}
public static void TestDeceleration( ICar car )
{
    int oldSpeed = car.GetCurrentSpeed();
    car.Decelerate( 5 );
    int newSpeed = car.GetCurrentSpeed();
    Assert.IsTrue( newSpeed < oldSpeed );
}

Тест говорит вам, что, возможно, вы неправильно реализовали интерфейс автомобиля.

1
ответ дан 6 December 2019 в 15:25
поделиться

Так вам нужны примеры? В прошлом семестре я прошел курс составителей. В нем нам пришлось написать распределитель регистров. Проще говоря, мою программу можно резюмировать следующим образом:

Входные данные: Файл, написанный на ILOC, языке псевдо-ассемблера, который был создан для моего учебника. Инструкции в файле имеют имена регистров, например «r <номер> ». Проблема в том, что программа использует столько регистров, сколько нужно, что обычно превышает количество регистров на целевой машине.

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

Чтобы написать эту программу, мне пришлось создать класс, который мог бы анализировать файл ILOC. Я написал кучу тестов для этого класса. Ниже приведены мои тесты (на самом деле у меня их было больше, но я избавился от них, чтобы сократить это. Я также добавил несколько комментариев, чтобы помочь вам прочитать его). Я сделал проект на C ++, поэтому я использовал платформу тестирования C ++ Google (googletest), расположенную здесь .

Прежде чем показать вам код ... позвольте мне сказать кое-что о базовой структуре. По сути, есть тестовый класс. Вы можете добавить кучу общих настроек в тестовый класс. Затем есть тестовые макросы, называемые TEST_F. Среда тестирования улавливает их и понимает, что их нужно запускать как тесты. Каждый TEST_F имеет 2 аргумента, имя тестового класса и имя теста (которое должно быть очень информативным ... таким образом, если тест не пройден, вы точно знаете, что не удалось). Вы увидите, что структура каждого теста аналогична: (1) настроить некоторые начальные параметры, (2) запустите метод, который вы тестируете, (3) проверьте правильность вывода. Вы проверяете (3) с помощью макросов вроде EXPECT_ *. EXPECT_EQ (ожидаемый, результат) проверяет, что результат равен ожидаемому . Если это не так, вы получите полезное сообщение об ошибке типа «результат был бла, но ожидался бла».

Вот код (я надеюсь, что это не очень сбивает с толку ... это, конечно, не короткий или простой пример , но если вы потратите время, вы сможете проследить и получить общее представление о том, как это работает).

// Unit tests for the iloc_parser.{h, cc}

#include <fstream>
#include <iostream>
#include <gtest/gtest.h>
#include <sstream>
#include <string>
#include <vector>

#include "iloc_parser.h"

using namespace std;

namespace compilers {
// Here is my test class
class IlocParserTest : public testing::Test {
 protected:
  IlocParserTest() {}
  virtual ~IlocParserTest() {}

  virtual void SetUp() {
    const testing::TestInfo* const test_info =
      testing::UnitTest::GetInstance()->current_test_info();
    test_name_ = test_info->name();
  }

  string test_name_;
};

// Here is a utility function to help me test
static void ReadFileAsString(const string& filename, string* output) {
  ifstream in_file(filename.c_str());
  stringstream result("");
  string temp;
  while (getline(in_file, temp)) {
    result << temp << endl;
  }
  *output = result.str();
}

// All of these TEST_F things are macros that are part of the test framework I used.
// Just think of them as test functions. The argument is the name of the test class.
// The second one is the name of the test (A descriptive name so you know what it is
// testing).
TEST_F(IlocParserTest, ReplaceSingleInstanceOfSingleCharWithEmptyString) {
  string to_replace = "blah,blah";
  string to_find = ",";
  string replace_with = "";
  IlocParser::FindAndReplace(to_find, replace_with, &to_replace);
  EXPECT_EQ("blahblah", to_replace);
}

TEST_F(IlocParserTest, ReplaceMultipleInstancesOfSingleCharWithEmptyString) {
  string to_replace = "blah,blah,blah";
  string to_find = ",";
  string replace_with = "";
  IlocParser::FindAndReplace(to_find, replace_with, &to_replace);
  EXPECT_EQ("blahblahblah", to_replace);
}

TEST_F(IlocParserTest,
       ReplaceMultipleInstancesOfMultipleCharsWithEmptyString) {
  string to_replace = "blah=>blah=>blah";
  string to_find = "=>";
  string replace_with = "";
  IlocParser::FindAndReplace(to_find, replace_with, &to_replace);
  EXPECT_EQ("blahblahblah", to_replace);
}

// This test was suppsoed to strip out the "r" from register
// register names in the ILOC code.
TEST_F(IlocParserTest, StripIlocLineLoadI) {
  string iloc_line = "loadI\t1028\t=> r11";
  IlocParser::StripIlocLine(&iloc_line);
  EXPECT_EQ("loadI\t1028\t 11", iloc_line);
}

// Here I make sure stripping the line works when it has a comment
TEST_F(IlocParserTest, StripIlocLineSubWithComment) {
  string iloc_line = "sub\tr12, r10\t=> r13  // Subtract r10 from r12\n";
  IlocParser::StripIlocLine(&iloc_line);
  EXPECT_EQ("sub\t12 10\t 13  ", iloc_line);
}


// Here I make sure I can break a line up into the tokens I wanted.
TEST_F(IlocParserTest, TokenizeIlocLineNormalInstruction) {
  string iloc_line = "sub\t12 10\t 13\n";  // already stripped
  vector<string> tokens;
  IlocParser::TokenizeIlocLine(iloc_line, &tokens);
  EXPECT_EQ(4, tokens.size());
  EXPECT_EQ("sub", tokens[0]);
  EXPECT_EQ("12", tokens[1]);
  EXPECT_EQ("10", tokens[2]);
  EXPECT_EQ("13", tokens[3]);
}


// Here I make sure I can create an instruction from the tokens
TEST_F(IlocParserTest, CreateIlocInstructionLoadI) {
  vector<string> tokens;
  tokens.push_back("loadI");
  tokens.push_back("1");
  tokens.push_back("5");
  IlocInstruction instruction(IlocInstruction::NONE);
  EXPECT_TRUE(IlocParser::CreateIlocInstruction(tokens,
                                                &instruction));
  EXPECT_EQ(IlocInstruction::LOADI, instruction.op_code());
  EXPECT_EQ(2, instruction.num_operands());
  IlocInstruction::OperandList::const_iterator it = instruction.begin();
  EXPECT_EQ(1, *it);
  ++it;
  EXPECT_EQ(5, *it);
}

// Making sure the CreateIlocInstruction() method fails when it should.
TEST_F(IlocParserTest, CreateIlocInstructionFromMisspelledOp) {
  vector<string> tokens;
  tokens.push_back("ADD");
  tokens.push_back("1");
  tokens.push_back("5");
  tokens.push_back("2");
  IlocInstruction instruction(IlocInstruction::NONE);
  EXPECT_FALSE(IlocParser::CreateIlocInstruction(tokens,
                                            &instruction));
  EXPECT_EQ(0, instruction.num_operands());
}

// Make sure creating an empty instruction works because there
// were times when I would actually have an empty tokens vector.
TEST_F(IlocParserTest, CreateIlocInstructionFromNoTokens) {
  // Empty, which happens from a line that is a comment.
  vector<string> tokens;
  IlocInstruction instruction(IlocInstruction::NONE);
  EXPECT_TRUE(IlocParser::CreateIlocInstruction(tokens,
                                                &instruction));
  EXPECT_EQ(IlocInstruction::NONE, instruction.op_code());
  EXPECT_EQ(0, instruction.num_operands());
}

// This was a function that helped me generate actual code
// that I could output as a line in my output file.
TEST_F(IlocParserTest, MakeIlocLineFromInstructionAddI) {
  IlocInstruction instruction(IlocInstruction::ADDI);
  vector<int> operands;
  operands.push_back(1);
  operands.push_back(2);
  operands.push_back(3);
  instruction.CopyOperandsFrom(operands);
  string output;
  EXPECT_TRUE(IlocParser::MakeIlocLineFromInstruction(instruction, &output));
  EXPECT_EQ("addI r1, 2 => r3", output);
}

// This test actually glued a bunch of stuff together. It actually
// read an input file (that was the name of the test) and parsed it
// I then checked that it parsed it correctly.
TEST_F(IlocParserTest, ParseIlocFileSimple) {
  IlocParser parser;
  vector<IlocInstruction*> lines;
  EXPECT_TRUE(parser.ParseIlocFile(test_name_, &lines));
  EXPECT_EQ(2, lines.size());

  // Check first line
  EXPECT_EQ(IlocInstruction::ADD, lines[0]->op_code());
  EXPECT_EQ(3, lines[0]->num_operands());
  IlocInstruction::OperandList::const_iterator operand = lines[0]->begin();
  EXPECT_EQ(1, *operand);
  ++operand;
  EXPECT_EQ(2, *operand);
  ++operand;
  EXPECT_EQ(3, *operand);

  // Check second line
  EXPECT_EQ(IlocInstruction::LOADI, lines[1]->op_code());
  EXPECT_EQ(2, lines[1]->num_operands());
  operand = lines[1]->begin();
  EXPECT_EQ(5, *operand);
  ++operand;
  EXPECT_EQ(10, *operand);

  // Deallocate memory
  for (vector<IlocInstruction*>::iterator it = lines.begin();
       it != lines.end();
       ++it) {
    delete *it;
  }
}

// This test made sure I generated an output file correctly.
// I built the file as an in memory representation, and then
// output it. I had a "golden file" that was supposed to represent
// the correct output. I compare my output to the golden file to
// make sure it was correct.
TEST_F(IlocParserTest, WriteIlocFileSimple) {
  // Setup instructions
  IlocInstruction instruction1(IlocInstruction::ADD);
  vector<int> operands;
  operands.push_back(1);
  operands.push_back(2);
  operands.push_back(3);
  instruction1.CopyOperandsFrom(operands);
  operands.clear();
  IlocInstruction instruction2(IlocInstruction::LOADI);
  operands.push_back(17);
  operands.push_back(10);
  instruction2.CopyOperandsFrom(operands);
  operands.clear();
  IlocInstruction instruction3(IlocInstruction::OUTPUT);
  operands.push_back(1024);
  instruction3.CopyOperandsFrom(operands);

  // Propogate lines with the instructions
  vector<IlocInstruction*> lines;
  lines.push_back(&instruction1);
  lines.push_back(&instruction2);
  lines.push_back(&instruction3);

  // Write out the file
  string out_filename = test_name_ + "_output";
  string golden_filename = test_name_ + "_golden";
  IlocParser parser;
  EXPECT_TRUE(parser.WriteIlocFile(out_filename, lines));

  // Read back output file and verify contents are as expected.
  string golden_file;
  string out_file;
  ReadFileAsString(golden_filename, &golden_file);
  ReadFileAsString(out_filename, &out_file);
  EXPECT_EQ(golden_file, out_file);
}
}  // namespace compilers

int main(int argc, char** argv) {
  // Boiler plate, test initialization
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}

В конце концов, все сказано и сделано ... ПОЧЕМУ Я ДЕЛАЛ ЭТО !? Ну во-первых. Я писал тесты постепенно, когда готовился написать каждый фрагмент кода. Это помогло мне быть уверенным в том, что код, который я уже написал, работает правильно. Было бы безумием написать весь свой код, а затем просто попробовать его в файле и посмотреть, что получилось. Было так много слоев, как я мог узнать, откуда взялась ошибка, если я не тестировал каждый маленький кусочек изолированно?

НО ... САМОЕ ВАЖНО !!! На самом деле тестирование - это не обнаружение начальных ошибок в вашем коде ... это защита от случайного взлома кода. Каждый раз, когда я реорганизовывал или изменял свой класс IlocParser, я был уверен, что не изменил его плохим способом, потому что я мог запустить свои тесты (за считанные секунды) и убедиться, что весь код по-прежнему работает должным образом. В ЭТОМ великолепное применение модульных тестов.

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

1
ответ дан 6 December 2019 в 15:25
поделиться

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

http: //en.wikipedia. org / wiki / Unit_testing

0
ответ дан 6 December 2019 в 15:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: