Тестування на Devnet — це перевірка вашої on-chain програми на реальному кластері Solana з тестовими токенами. На відміну від локального validator, Devnet дає справжню мережеву затримку, реальну послідовність транзакцій та справжнє середовище виконання. Нижче — покрокова інструкція, яка доведе вас від підготовки середовища до автоматизованих тестів.

Версії для прикладів у «Як тестувати навчальний проєкт на Devnet» перевірено 2 серпня 2026 року. Стабільна гілка Anchor v1 має релізи 1.0.x і орієнтується на Solana 3.x; Anchor v2 у документації позначений як alpha. Приклади для Anchor 0.29–0.32 залишаються лише відтворюваними прикладами для зафіксованого legacy-середовища: їх не слід переносити в новий проєкт без міграції залежностей і повторного тестування. Клієнт @anchor-lang/core сумісний із legacy @solana/web3.js v1, а не з v2.

Передумови: встановлені Solana CLI, Anchor, Rust та Node.js; готовий навчальний проєкт з програмою, яка успішно проходить локальні тести командою anchor test. Середовище: Devnet-кластер Solana. Ризик: витрата тестових SOL на комісії та rent-exempt депозити — кошти не повертаються, якщо ви не закриваєте акаунти після тестів.

Підготовка тестового середовища

розгортання програми на Devnet

Перед тестуванням програма має бути розгорнута на Devnet. Переконайтеся, що Solana CLI підключено до правильного кластера:

solana config get

Очікуваний результат: поле RPC URL має вказувати на https://api.devnet.solana.com. Якщо ні — виконайте:

solana config set --url devnet

Зберіть програму та виконайте розгортання:

anchor build
anchor deploy --provider.cluster devnet

Після розгортання запишіть Program ID — він знадобиться в тестах. Знайти його можна у файлі target/deploy/your_program-keypair.json або у виводі команди розгортання. Перевірте, що програма з'явилася на кластері:

solana program show <PROGRAM_ID>

Очікуваний результат: виводиться адреса програми та її розмір у байтах.

Отримання тестових SOL для тестових акаунтів

Кожна транзакція на Devnet потребує SOL для комісій та rent-exempt резервів. Отримайте тестові токени на ваш гаманець:

solana airdrop 2

Перевірте баланс:

solana balance

Очікуваний результат: баланс має становити не менше 2 SOL. Якщо airdrop не спрацював, повторіть спробу через кілька секунд — Devnet має ліміт на кількість запитів з одної IP-адреси. Альтернативно використовуйте веб-фацет для airdrop (перевірте актуальну адресу на офіційному сайті Solana).

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

Написання тестів для Devnet

Конфігурація Anchor.toml для Devnet-тестів

Команда anchor test завжди запускає локальний validator, ігноруючи налаштування кластера. Для тестування на Devnet потрібен окремий підхід — скрипт, який підключається до Devnet безпосередньо.

У Anchor.toml переконайтеся, що секція [provider] налаштована для Devnet. Це потрібно для коректного розгортання та генерації IDL:

[provider]
cluster = "devnet"
wallet = "~/.config/solana/id.json"

Ця конфігурація використовується командами anchor build та anchor deploy, але не anchor test. Тому для Devnet-тестів створюйте окремий файл, наприклад tests/devnet-test.ts, та запускайте його через npx ts-node tests/devnet-test.ts.

Використання реальних підключень замість локального validator

Створіть підключення до Devnet через Anchor Provider. Приклад для TypeScript-середовища з використанням пакета @coral-xyz/anchor (перевірте, який пакет відповідає вашій версії Anchor — @coral-xyz/anchor або @project-serum/anchor):

import { AnchorProvider, Program, BN } from "@coral-xyz/anchor";
import { Connection, Keypair } from "@solana/web3.js";
import fs from "fs";
import idl from "../target/idl/your_program.json";

const PROGRAM_ID = new PublicKey("ваш_program_id_з_деплою");

const connection = new Connection(
"https://api.devnet.solana.com",
"confirmed"
);

const walletKeypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(
fs.readFileSync("/path/to/test-wallet.json", "utf-8")
))
);

const provider = new AnchorProvider(connection, walletKeypair, {
commitment: "confirmed",
});

Ключова різниця від локальних тестів: ви ініціалізуєте AnchorProvider з реальним RPC-ендпоінтом Devnet, а не покладаєтесь на автоматичний локальний validator. Program ID має збігатися з тим, що було задеплоєно на попередньому кроці. IDL підключається з файлу, який генерується при anchor build.

Спосіб перевірки: виведіть provider.connection.rpcEndpoint — має показати devnet-адресу. Спробуйте виконати connection.getBalance(walletKeypair.publicKey) — має повернути реальний баланс гаманця на Devnet.

Тестовий сценарій

Створення стану → виклик instruction → перевірка результату

Розглянемо базовий сценарій на прикладі програми-лічильника. Логіка тесту складається з трьох кроків:

  1. Створіть акаунт стану через instruction initialize.
  2. Викличте instruction increment.
  3. Зчитайте стан акаунта та перевірте, що значення лічильника дорівнює 1.
const program = new Program(idl, PROGRAM_ID, provider);

// 1. Initialize
const stateKeypair = Keypair.generate();
await program.methods
.initialize()
.accounts({
state: stateKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.signers([stateKeypair])
.rpc();

// 2. Increment
await program.methods
.increment()
.accounts({
state: stateKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.rpc();

// 3. Verify
const state = await program.account.state.fetch(stateKeypair.publicKey);
console.log("Counter:", state.count.toString());
assert(state.count.eq(new BN(1)));

Очікуваний результат: тест проходить без помилок, у консолі виводиться Counter: 1. Якщо отримуєте помилку транзакції — перевірте логи через solana logs <tx_signature> на Devnet. Це покаже конкретну причину відхилення: недостатньо SOL, неправильний акаунт, порушення обмежень програми тощо.

Тестування edge cases

На Devnet варто перевірити сценарії, які локальний validator може обробляти м'якше або інакше:

  • Повторний виклик initialize на вже існуючий акаунт — має повернути помилку.
  • Виклик instruction з недостатнім балансом для rent-exempt депозиту.
  • Передача неправильного авторитету (wrong authority) — транзакція має відхилитися на рівні програми.

Приклад перевірки повторної ініціалізації:

await assert.rejects(
program.methods
.initialize()
.accounts({
state: stateKeypair.publicKey,
authority: provider.wallet.publicKey,
})
.signers([stateKeypair])
.rpc(),
/already in use/
);

Очікуваний результат: другий виклик initialize генерує помилку, тест фіксує це через assert.rejects і проходить успішно. Регулярний вираз у другому аргументі має відповідати тексту помилки з вашої програми — перевірте його за логами.

Автоматизація

CI-скрипт для Devnet-тестування

Для автоматичного запуску Devnet-тестів у CI використовуйте окремий робочий процес. Приклад структури для GitHub Actions:

name: Devnet Tests

on: [push, pull_request]

jobs:
devnet-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Solana CLI
run: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
- name: Install Anchor
run: cargo install anchor-cli --locked
- name: Build and deploy
run: |
anchor build
anchor deploy --provider.cluster devnet
env:
ANCHOR_WALLET: ${{ secrets.DEVNET_WALLET_JSON_PATH }}
- name: Run Devnet tests
run: npx ts-node tests/devnet-test.ts
- name: Cleanup
if: always()
run: npx ts-node tests/devnet-cleanup.ts

Ключові моменти: гаманець із SOL на Devnet має бути доступний у CI через secrets, а Program ID має бути фіксованим. Щоб не генерувати новий ключ при кожному розгортання, використовуйте --program-id з існуючим keypair-файлом. Перевірте актуальний спосіб встановлення Solana CLI — адреса скрипта може змінюватися.

Очищення тестових даних після тестів

Тестові акаунти на Devnet споживають SOL як rent-exempt депозит. Після тестів закривайте їх, щоб повернути кошти. Приклад для програми, що має instruction close:

await program.methods
.close()
.accounts({
state: stateKeypair.publicKey,
authority: provider.wallet.publicKey,
receiver: provider.wallet.publicKey,
})
.rpc();

Очікуваний результат: акаунт закривається, SOL повертається на вказаний адресат. Перевірте:

solana account <account_pubkey>

Має повернути «Account not found». У CI додавайте крок очищення з умовою if: always(), щоб він виконувався навіть якщо тести впали. Якщо ваша програма не має instruction для закриття акаунтів, реалізуйте його окремо — це стандартна практика для навчальних проєктів.

Типові помилки

Тести залежать від стану Devnet

Devnet — спільне середовище. Інші розробники можуть змінити стан програми або створити конфліктні акаунти. Якщо ваші тести створюють PDA (Program Derived Address — детермінована адреса, похідна від програми та сидів), переконайтеся, що сиди унікальні для вашого тестового набору.

Рішення: використовуйте унікальні сиди для кожного запуску тестів, додаючи часовий штамп або випадковий рядок:

import { PublicKey } from "@solana/web3.js";

const runId = `test-${Date.now()}`;
const [statePda] = PublicKey.findProgramAddressSync(
[Buffer.from(runId)],
PROGRAM_ID
);

Це гарантує, що кожен запуск тестів працює з чистим станом, незалежно від попередніх запусків чи дій інших користувачів Devnet. Зберігайте runId у змінну та передавайте в усі instruction, де потрібен цей сид.

Rate limit RPC під час тестування

Публічні RPC-ендпоінти Devnet мають обмеження на кількість запитів за одиницю часу. Якщо ваші тести виконують багато транзакцій послідовно, ви можете отримати помилку 429 (Too Many Requests) або таймаут.

Ознаки: періодичні невдачі транзакцій, які проходять при повторному запуску; помилки з текстом «429 Too Many Requests» або «rate limit exceeded».

Рішення:

  • Додайте затримку між транзакціями: await new Promise(r => setTimeout(r, 1000));
  • Використовуйте власний RPC-провайдер замість публічного ендпоінту — перевірте актуальні умови та ліміти на сайті обраного провайдера.
  • Групуйте кілька instructions в одну транзакцію за допомогою об'єкта Transaction, щоб зменшити кількість окремих RPC-викликів.
  • Реалізуйте повторні спроби з експоненційною затримкою (exponential backoff) для транзакцій, що завершилися помилкою rate limit.

Після успішного тестування на Devnet наступний логічний крок — стабільний розгортання навчального проєкту на Devnet з фіксацією Program ID та підготовкою до інтеграції з frontend. Ця тема розкрита в наступному розділі «Як деплоїти навчальний проєкт на Devnet».

Джерела