Як зробити TDD для чогось із багатьма перестановками?


15

Під час створення такої системи, як AI, яка може пройти багато різних шляхів дуже швидко, або насправді будь-який алгоритм, який має кілька різних входів, можливий набір результатів може містити велику кількість перестановок.

Який підхід слід використовувати для використання TDD під час створення системи, яка видає багато, багато різних перестановок результатів?


1
Загальна доброта системи AI зазвичай вимірюється за допомогою тесту Precision-Recall з набором базового вводу. Цей тест приблизно відповідає «інтеграційним тестам». Як уже згадували інші, це скоріше скоріше "тестово-керований алгоритм дослідження", а не "тестово-керований дизайн ".
rwong

Будь ласка, визначте, що ви маєте на увазі під "AI". Це сфера вивчення більше, ніж будь-який конкретний тип програми. Для певної реалізації AI, як правило, ви не можете перевірити деякі типи речей (наприклад, поведінку, що виникає) через TDD.
Стівен Еверс

@SnOrfus Я маю на увазі це в найбільш загальному, рудиментарному сенсі, механізмі прийняття рішень.
Ніколь

Відповіді:


7

Більш практичний підхід до відповіді pdr . TDD - це все про дизайн програмного забезпечення, а не про тестування. Ви використовуєте одиничні тести, щоб перевірити свою роботу під час роботи.

Отже, на рівні тестування одиниці потрібно сконструювати одиниці, щоб вони могли бути випробувані повністю детерміновано. Це можна зробити, взявши все, що робить одиницю недетермінованою (наприклад, генератор випадкових чисел) та абстрагує її. Скажімо, у нас є наївний приклад способу вирішити, чи хороший хід чи ні:

class Decider {

  public boolean decide(float input, float risk) {

      float inputRand = Math.random();
      if (inputRand > input) {
         float riskRand = Math.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider();
d.decide(0.1337f, 0.1337f);

Цей метод дуже важко перевірити, і єдине, що ви дійсно можете перевірити в одиничних тестах, це його межі ... але для цього потрібно багато спроб дістатися до меж. Тож замість цього давайте абстрагуємо рандомізовану частину, створивши інтерфейс та конкретний клас, який охоплює функціональність:

public interface IRandom {

   public float random();

}

public class ConcreteRandom implements IRandom {

   public float random() {
      return Math.random();
   }

}

Тепер Deciderкласу необхідно використовувати клас конкретного через його абстрагування, тобто інтерфейс. Такий спосіб роботи називається ін'єкцією залежності (приклад нижче - приклад конструкторської інжекції, але це можна зробити і за допомогою сетера):

class Decider {

  IRandom irandom;

  public Decider(IRandom irandom) { // constructor injection
      this.irandom = irandom;
  }

  public boolean decide(float input, float risk) {

      float inputRand = irandom.random();
      if (inputRand > input) {
         float riskRand = irandom.random();
      }
      return false;

  }

}

// The usage:
Decider d = new Decider(new ConcreteRandom);
d.decide(0.1337f, 0.1337f);

Ви можете запитати себе, для чого потрібен цей "код". Ну, а для початку ви зараз можете знущатися над поведінкою випадкової частини алгоритму, оскільки Deciderтепер існує залежність, яка відповідає IRandoms "контракту". Ви можете використовувати для цього глузливі рамки, але цей приклад досить простий, щоб кодувати себе:

class MockedRandom() implements IRandom {

    public List<Float> floats = new ArrayList<Float>();
    int pos;

   public void addFloat(float f) {
     floats.add(f);
   }

   public float random() {
      float out = floats.get(pos);
      if (pos != floats.size()) {
         pos++;
      }
      return out;
   }

}

Найкраще, що це може повністю замінити "фактичну" конкретну реалізацію. Код легко перевірити так:

@Before void setUp() {
  MockedRandom mRandom = new MockedRandom();

  Decider decider = new Decider(mRandom);
}

@Test
public void testDecisionWithLowInput_ShouldGiveFalse() {

  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandButLowRiskRand_ShouldGiveFalse() {

  mRandom.addFloat(1f);
  mRandom.addFloat(0f);

  assertFalse(decider.decide(0.1337f, 0.1337f));
}

@Test
public void testDecisionWithHighInputRandAndHighRiskRand_ShouldGiveTrue() {

  mRandom.addFloat(1f);
  mRandom.addFloat(1f);

  assertTrue(decider.decide(0.1337f, 0.1337f));
}

Сподіваємось, це дає вам ідеї щодо того, як розробити додаток, щоб перестановки могли бути примусовими, щоб ви могли протестувати всі крайові випадки та інше.


3

Суворий TDD, як правило, дещо руйнує складніші системи, але це практично не має великого значення на практиці - як тільки ви не зможете виділити окремі входи, просто виберіть кілька тестових випадків, які забезпечують розумне охоплення та їх використовувати.

Для цього потрібні певні знання про те, що реалізація буде успішною, але це є більш теоретичним питанням - ви навряд чи будуєте AI, який детально вказали нетехнічні користувачі. Це в тій же категорії, що і проходження тестів шляхом жорсткого кодування до тестових випадків - офіційно тест є специфікацією, а реалізація є правильним і швидким можливим рішенням, але насправді це ніколи не відбувається.


2

TDD - це не тестування, а дизайн.

Далеко не розпадаючись на складність, це перевершує в цих умовах. Це змусить вас розглянути більшу проблему меншими шматками, що призведе до кращого дизайну.

Не намагайтеся перевірити кожну перестановку вашого алгоритму. Просто складіть тест після тесту, напишіть найпростіший код, щоб тест працював, поки ви не будете охоплені вашими базами. Ви повинні побачити, що я маю на увазі щодо усунення проблеми, оскільки вам буде запропоновано підробити частину проблеми під час тестування інших частин, щоб заощадити себе, щоб написати 10 мільярдів тестів на 10 мільярдів перестановок.

Редагувати: Я хотів додати приклад, але раніше не встиг.

Розглянемо алгоритм сортування на місці. Ми можемо продовжувати і писати тести, які охоплюють верхній кінець масиву, нижній кінець масиву та всілякі дивні комбінації посередині. Для кожного з них нам би довелося побудувати повний масив якогось об’єкта. На це знадобився б час.

Або ми могли вирішити проблему в чотирьох частинах:

  1. Прокладіть масив.
  2. Порівняйте вибрані елементи.
  3. Перемикайте елементи.
  4. Координуйте вищевказані три.

Перша є єдиною складною частиною проблеми, але, абстрагувавши її від решти, ви зробили її набагато простіше.

Друге майже напевно обробляється самим об’єктом, принаймні необов'язково, у багатьох кадрах статичного типу буде інтерфейс, який показує, чи реалізована ця функціональність. Тому вам не потрібно цього тестувати.

Третій неймовірно простий для тестування.

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

Як ми привели до кращого дизайну тут? Скажімо, що ви просто і просто реалізували міхур. Це працює, але коли ви переходите до виробництва і йому доводиться обробляти мільйон предметів, це занадто повільно. Все, що вам потрібно зробити, - це написати нову функціональність обходу та поміняти її. Не потрібно мати справу зі складністю вирішення інших трьох проблем.

Ви знайдете, що це різниця між тестуванням одиниць та TDD. Тестер приладів скаже, що це зробило ваші тести крихкими, що якби ви протестували прості входи та вихідні дані, вам більше не доведеться писати більше тестів для вашої нової функціональності. TDDer скаже, що я розділив питання належним чином, так що кожен клас, який я маю, робить добре одне і одне.


1

Неможливо перевірити кожну перестановку обчислення з багатьма змінними. Але це нічого нового, це завжди стосувалося будь-якої програми, що перевищує складність іграшок. Суть тестів полягає у перевірці властивості обчислення. Наприклад, сортування списку з 1000 цифрами вимагає певних зусиль, але будь-яке окреме рішення можна перевірити дуже легко. Зараз, хоча їх 1000! можливі (класи) входів для цієї програми, і ви не можете перевірити їх усіх, цілком достатньо просто генерувати 1000 входів випадковим чином і перевірити, що результат справді відсортований. Чому? Тому що майже неможливо написати програму, яка надійно сортує 1000 випадково генерованих векторів, не будучи правильними також загалом (якщо ви навмисно не встановите це для маніпулювання певними магічними введеннями ...)

Зараз загалом справи дещо складніші. Там дійсно є були помилки , коли поштова програма не буде завдавати повідомлення електронної пошти для користувачів , якщо вони мають «F» в їх імені користувача і день тижня п'ятниця. Але я вважаю це марними зусиллями, намагаючись передбачити таку дивність. Ваш тестовий набір повинен забезпечити вам стійку впевненість, що система робить те, що ви очікуєте, на очікувані вами входи. Якщо це виконує фанкі в певних фанк-випадках, ви помітите досить скоро, коли ви спробуєте перший випадок, і тоді ви можете написати тест спеціально проти цього випадку (який, як правило, також охоплюватиме цілий клас подібних випадків).


Якщо ви генеруєте 1000 входів випадковим чином, як ви потім тестуєте виходи? Звичайно, такий тест буде мати певну логіку, яка сама по собі не перевірена. Отже, ви перевірите тест? Як? Справа в тому, що ви повинні перевірити логіку, використовуючи переходи стану - при введенні X вихід повинен бути Y. Тест, що включає логіку, схильний до помилок настільки ж, як і перевірена логікою. З точки зору логіки, обгрунтування аргументу іншим аргументом ставить вас на скептичний шлях регресу - ви повинні зробити деякі твердження. Ці твердження - це ваші тести.
Іжакі

0

Візьміть крайні регістри плюс деякий випадковий ввід.

Для прикладу сортування:

  • Сортуйте кілька випадкових списків
  • Візьміть вже відсортований список
  • Візьміть список у зворотному порядку
  • Візьміть список, який майже відсортований

Якщо для них це швидко працює, ви можете бути повністю впевнені, що він працюватиме для всіх даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.