
Попробуйте : Phoenix-Chat .fly.dev
Пошаговое руководство по созданию, тестированию и развертыванию приложения чата в Phoenix!
page_controller_test.exsuser_socket.jsexcoveralls в качестве (развития) зависимости для mix.exscoveralls.jsonPresence , чтобы отслеживать, кто онлайн Приложения чата - это "Hello World" примеров в реальном времени.
К сожалению, большинство примеров приложений показывают несколько оснований , а затем игнорируют остальные ...? ♀
Таким образом, начинающих часто остаются потерянными или смущенными в отношении того, что они должны делать или учиться дальше !
Очень немногие учебные пособия рассматривают вопрос о тестировании, развертывании, документации или других « улучшениях », которые являются частью « реального мира » приложений построения и управления; Таким образом, это темы, которые мы рассмотрим , чтобы « заполнить пробелы ».
Мы написали этот учебник, чтобы быть самым простым способом изучения Phoenix , Ecto и Channels с практическим примером, который может последовать любой человек .
Это пример/учебник, который мы хотели бы иметь, когда изучали Elixir , Phoenix ... если вы найдете его полезным, пожалуйста, спасибо!
Простой пошаговый учебник, показывающий вам, как:
mix phx.new chat «Генератор» )Fly.io , чтобы вы могли показать людям свое творение!Первоначально мы намеренно пропускаем файлы конфигурации и « Внутренние власти », потому что вам ( новичкам ) вам не нужно знать о них, чтобы начать работу . Но не волнуйтесь, мы вернемся к ним, когда это необходимо . Мы предпочитаем « только время » ( когда вам это нужно ), так как это сразу очевидно и практично , почему мы чему-то изучаем.
Этот пример для начинающих в качестве приложения « My Phoenix ».
Мы стараемся предположить как можно меньше, но если вы думаете, что мы « пропустили шаг » или чувствуете себя « застрявшим » по любой причине, или у вас есть какие -либо вопросы ( связанные с этим примером ), пожалуйста, откройте проблему на GitHub!
Как сообщества @dwyl, так и Феникс очень благоприятны для начинающих , так что не бойтесь/застенчивы.
Кроме того, задавая вопросы, вы помогаете всем, кто есть или может застрять в том же !
Эти инструкции показывают, как создать приложение чата с нуля .
brew install elixirПРИМЕЧАНИЕ . Если у вас уже установлен
Elixirна вашем Mac, и просто вы хотите перейти на последнюю версию, запустите:brew upgrade elixir
mix archive.install hex phx_new Основные знания синтаксиса эликсира помогут,
Пожалуйста, смотрите: Dwyl/ Learn-Elixir
Основные знания JavaScript выгодны ( но не очень важны, поскольку код «фронт-энда» довольно базовый и хорошо смягченный ). См.: Dwyl/JavaScript-The-Good-Parts-Notes
Проверьте, у вас есть последняя версия Elixir ( запустите следующую команду в вашем терминале ):
elixir -vВы должны увидеть что -то вроде:
Erlang/OTP 25 [erts-13.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
Elixir 1.14.1 (compiled with Erlang/OTP 25)Проверьте, у вас есть последняя версия Phoenix :
mix phx.new -vВы должны увидеть:
Phoenix installer v1.7.0-rc.2ПРИМЕЧАНИЕ . Если ваша версия
Phoenixболее новее , пожалуйста, не стесняйтесь обновить этот документ! Мы стараемся обновлять это ... но ваши вклады всегда приветствуются!
В этом уроке мы используем Phoenix 1.7-RC2, второго кандидата в релиз для
Phoenix 1.7. На момент написания, если вы установите Phoenix, последняя стабильная версия не являетсяv1.7. Чтобы использовать эту версию, следуйте официальному руководству (не волнуйтесь, она просто запускает одну команду!)-> https://www.phoenixframework.org/blog/phoenix-1.7 сэмплексОднако, если вы читаете это после его выпуска,
v1.7будет установлен для вас, и вы должны увидетьPhoenix installer v1.7.0в вашем терминале.
Подтвердите, что PostgreSQL работает ( поэтому приложение может хранить сообщения чата ) запустить следующую команду:
lsof -i :5432Вы должны увидеть выход, аналогичный следующему:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
postgres 529 Nelson 5u IPv6 0xbc5d729e529f062b 0t0 TCP localhost:postgresql (LISTEN)
postgres 529 Nelson 6u IPv4 0xbc5d729e55a89a13 0t0 TCP localhost:postgresql (LISTEN) Это говорит нам о том, что PostgreSQL « прослушивает » на порту 5432 TCP ( порт по умолчанию )
Если команда lsof не дает никакого результата в вашем терминале, запустите:
pg_isreadyОн должен распечатать следующее:
/tmp:5432 - accepting connectionsСо всеми этими «проверками перед полетом» давайте летим !
Прежде чем попытаться создать приложение чата с нуля, клонировать и запустить готовую рабочую версию, чтобы понять, чего ожидать.
В вашем терминале запустите следующую команду, чтобы клонировать репо:
git clone [email protected]:dwyl/phoenix-chat-example.git Изменить в каталог phoenix-chat-example и установите как зависимости Elixir , так и Node.js с этой командой:
cd phoenix-chat-example
mix setupЗапустите приложение Phoenix с помощью команды:
mix phx.serverЕсли вы откроете приложение Localhost: 4000 в еще двух веб -браузерах, вы увидите сообщения чата, отображаемые во всех из них, как только вы нажмете на ключ Enter :

Теперь, когда вы подтвердили, что готовое приложение Phoenix Chat работает на вашей машине, пришло время построить его с нуля!
Справочник изменения:
cd ..И начните строить!
В вашей терминальной программе на вашем локальном хосте введите следующую команду для создания приложения:
mix phx.new chat --no-mailer --no-dashboard --no-gettext Это создаст структуру каталогов и файлы проекта.
Мы запускаем команду
mix phx.newс аргументами--no-mailer--no-dashboard--no-gettext, потому что мы не хотим, чтобы наш проект генерировал файлы почты, включал в себяPhoenix.LiveDashboardи генерировать файлыgettext(дляi18n).
Когда его попросят « принести и установить зависимости ? [Yn]»,
Введите Y в вашем терминале, за которым следует ключ Enter ( return ).
Вы должны увидеть: 
Изменить каталог в каталог chat , выполнив предложенную команду:
cd chatТеперь запустите следующую команду:
mix setupПримечание : на данный момент уже есть «приложение», оно просто ничего не делает (пока) ...
Вы можете запуститьmix phx.serverв своем терминале - не волнуйтесь, если вы видите ошибку
Сообщения, это потому, что мы еще не создали нашу базу данных.
Мы позаботимся об этом на шаге 6!
На данный момент откройте http: // localhost: 4000 в вашем браузере
И вы увидитеdefaultстраницу «Добро пожаловать в Феникс»:

Выключите сервер Phoenix в вашем терминале с помощью команды Ctrl + C.
В окне вашего терминала запустите следующую команду:
mix test
Вы должны увидеть выход, аналогичный следующему:
Generated chat app
.....
Finished in 0.02 seconds (0.02s async, 0.00s sync)
5 tests, 0 failures
Randomized with seed 84184Теперь, когда мы подтвердили, что все работает (все тесты проходят), давайте продолжим интересную часть!
Создайте канал (WebSocket), который будет использоваться в приложении чата:
mix phx.gen.channel RoomЕсли вам предложено подтвердить установку нового
yобработчика сокета и нажмите клавишу[Enter].
Это создаст три файла :
* creating lib/chat_web/channels/room_channel.ex
* creating test/chat_web/channels/room_channel_test.exs
* creating test/support/channel_case.exВ дополнение к созданию еще двух файлов :
* creating lib/chat_web/channels/user_socket.ex
* creating assets/js/user_socket.js Файл room_channel.ex обрабатывает получение/отправку сообщений и room_channel_test.exs тестирует базовое взаимодействие с каналом. Мы сосредоточимся на socket , созданных впоследствии. ( Пока не беспокойтесь об этом, мы рассмотрим тестовый файл на шаге 14 ниже !)
Нам сообщают, что нам нужно обновить кусок кода в нашем приложении:
Add the socket handler to your ` lib/chat_web/endpoint.ex ` , for example:
socket " /socket " , ChatWeb.UserSocket,
websocket: true,
longpoll: false
For the front-end integration, you need to import the ` user_socket.js `
in your ` assets/js/app.js ` file:
import " ./user_socket.js " Генератор просит нас импортировать клиент -код на фронте. Давайте сделаем это позже. На данный момент откройте файл lib/chat_web/endpoint.ex и следуйте инструкциям.
После этого откройте файл с именем /lib/chat_web/channels/user_socket.ex /user_socket.ex
и изменить линию:
channel "room:*" , ChatWeb.RoomChannelк:
channel "room:lobby" , ChatWeb.RoomChannelПроверьте изменение здесь.
Это гарантирует, что любые сообщения, которые отправляются в "room:lobby" направляются в наш RoomChannel .
Предыдущая «Комната "room" "room.*
Для получения более подробной информации о каналах Phoenix ( мы очень рекомендуем вам ) Читать: https://hexdocs.pm/phoenix/channels.html
Откройте файл /lib/chat_web/controllers/page_html/home.html.heex
и копировать вставку ( или введите ) следующий код:
<!-- The list of messages will appear here: -->
< div class =" mt-[4rem] " >
< ul id =" msg-list " phx-update =" append " class =" pa-1 " > </ ul >
</ div >
< footer class =" bg-slate-800 p-2 h-[3rem] fixed bottom-0 w-full flex justify-center " >
< div class =" w-full flex flex-row items-center text-gray-700 focus:outline-none font-normal " >
< input type =" text " id =" name " placeholder =" Name " required
class =" grow-0 w-1/6 px-1.5 py-1.5 " />
< input type =" text " id =" msg " placeholder =" Your message " required
class =" grow w-2/3 mx-1 px-2 py-1.5 " />
< button id =" send " class =" text-white bold rounded px-3 py-1.5 w-fit
transition-colors duration-150 bg-sky-500 hover:bg-sky-600 " >
Send
</ button >
</ div >
</ footer > Это основная форма, которую мы будем использовать для внедрения сообщений чата.
Классы, например, w-full и items-center являются классами TailwindCSS , чтобы стилизовать форму.
Phoenix включает в себя Tailwind по умолчанию, чтобы вы могли взять на себя с собой приложение/идею/"MVP"!
Если вы новичок в
Tailwind, см. Dwyl/ Learn-TailWindЕсли у вас есть вопросы по поводу любого из используемых
Tailwind, пожалуйста, потратьте 2 минуты в Google или ищите официальные (превосходные!) Документы: tailwindcss.com/docs, а затем, если вы все еще застряли, откройте проблему.
Ваш home.html.heex /lib/chat_web/controllers/page_html/home.html.heex
Откройте файл lib/chat_web/components/layouts/root.html.heex и найдите тег <body> . Замените содержимое <body> на следующий код:
< body class =" bg-white antialiased min-h-screen flex flex-col " >
< header class =" bg-slate-800 w-full h-[4rem] top-0 fixed flex flex-col justify-center z-10 " >
< div class =" flex flex-row justify-center items-center " >
< h1 class =" w-4/5 md:text-3xl text-center font-mono text-white " >
Phoenix Chat Example
</ h1 >
</ div >
</ header >
< %= @inner_content % >
</ body > Ваш шаблонный файл root.html.heex должен выглядеть следующим /lib/chat_web/components/layouts/root.html.heex
В конце этого шага, если вы запустите mix phx.server Phoenix Server, и просмотрите приложение в вашем браузере, он будет выглядеть так:

Так что это уже начинает выглядеть как базовое приложение для чата. К сожалению, с тех пор, как мы изменили копию home.html.heex нашего page_controller_test.exs теперь не удается:
Запустите команду:
mix test 1) test GET / (ChatWeb.PageControllerTest)
test/chat_web/controllers/page_controller_test.exs:4
Assertion with =~ failed
code: assert html_response(conn, 200) =~ "Peace of mind from prototype to production"
К счастью, это легко исправить.
page_controller_test.exs Откройте файл test/chat_web/controllers/page_controller_test.exs и замените строку:
assert html_response ( conn , 200 ) =~ "Peace of mind from prototype to production"С:
assert html_response ( conn , 200 ) =~ "Phoenix Chat Example"Теперь, если вы снова запустите тесты, они пройдут:
mix test
Вывод вывода:
........
Finished in 0.1 seconds (0.09s async, 0.06s sync)
8 tests, 0 failures
Randomized with seed 275786
Open assets/js/app.js , неуверенность и изменить линию:
import socket from "./user_socket.js" Благодаря неудобной строке наше приложение будет импортировать файл socket.js , который предоставит нам функциональность WebSocket.
Затем добавьте следующий код JavaScript («Клиент») в нижнюю часть файла:
/* Message list code */
const ul = document . getElementById ( 'msg-list' ) ; // list of messages.
const name = document . getElementById ( 'name' ) ; // name of message sender
const msg = document . getElementById ( 'msg' ) ; // message input field
const send = document . getElementById ( 'send' ) ; // send button
const channel = socket . channel ( 'room:lobby' , { } ) ; // connect to chat "room"
channel . join ( ) ; // join the channel.
// Listening to 'shout' events
channel . on ( 'shout' , function ( payload ) {
render_message ( payload )
} ) ;
// Send the message to the server on "shout" channel
function sendMessage ( ) {
channel . push ( 'shout' , {
name : name . value || "guest" , // get value of "name" of person sending the message. Set guest as default
message : msg . value , // get message text (value) from msg input field.
inserted_at : new Date ( ) // date + time of when the message was sent
} ) ;
msg . value = '' ; // reset the message input field for next message.
window . scrollTo ( 0 , document . documentElement . scrollHeight ) // scroll to the end of the page on send
}
// Render the message with Tailwind styles
function render_message ( payload ) {
const li = document . createElement ( "li" ) ; // create new list item DOM element
// Message HTML with Tailwind CSS Classes for layout/style:
li . innerHTML = `
<div class="flex flex-row w-[95%] mx-2 border-b-[1px] border-slate-300 py-2">
<div class="text-left w-1/5 font-semibold text-slate-800 break-words">
${ payload . name }
<div class="text-xs mr-1">
<span class="font-thin"> ${ formatDate ( payload . inserted_at ) } </span>
<span> ${ formatTime ( payload . inserted_at ) } </span>
</div>
</div>
<div class="flex w-3/5 mx-1 grow">
${ payload . message }
</div>
</div>
`
// Append to list
ul . appendChild ( li ) ;
}
// Listen for the [Enter] keypress event to send a message:
msg . addEventListener ( 'keypress' , function ( event ) {
if ( event . key === `Enter` && msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// On "Send" button press
send . addEventListener ( 'click' , function ( event ) {
if ( msg . value . length > 0 ) { // don't sent empty msg.
sendMessage ( )
}
} ) ;
// Date formatting
function formatDate ( datetime ) {
const m = new Date ( datetime ) ;
return m . getUTCFullYear ( ) + "/"
+ ( "0" + ( m . getUTCMonth ( ) + 1 ) ) . slice ( - 2 ) + "/"
+ ( "0" + m . getUTCDate ( ) ) . slice ( - 2 ) ;
}
// Time formatting
function formatTime ( datetime ) {
const m = new Date ( datetime ) ;
return ( "0" + m . getUTCHours ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCMinutes ( ) ) . slice ( - 2 ) + ":"
+ ( "0" + m . getUTCSeconds ( ) ) . slice ( - 2 ) ;
}Найдите минутку, чтобы прочитать код JavaScript и подтвердить свое понимание того, что он делает.
Надеемся, что встроенные комментарии являются самоочевидными, но, если что-то неясно, спросите!
На этом этапе ваш файл app.js должен выглядеть следующим образом: /assets/js/app.js
user_socket.js По умолчанию канал Phoenix (клиент) будет подписаться на общую комнату: "topic:subtopic" . Поскольку мы не будем использовать это, мы можем избежать того, чтобы увидеть какие -либо "unable to join: unmatched topic" в нашем браузере/консоли, просто комментируя несколько строк в файле user_socket.js . Откройте файл в вашем редакторе и найдите следующие строки:
let channel = socket . channel ( "room:42" , { } )
channel . join ( )
. receive ( "ok" , resp => { console . log ( "Joined successfully" , resp ) } )
. receive ( "error" , resp => { console . log ( "Unable to join" , resp ) } )Прокомментируйте строки, чтобы они не были выполнены:
//let channel = socket.channel("room:42", {})
//channel.join()
// .receive("ok", resp => { console.log("Joined successfully", resp) })
// .receive("error", resp => { console.log("Unable to join", resp) }) Ваш user_socket.js теперь должен выглядеть следующим образом: /assets/js/user_socket.js
Если вы позже решите поднять свое приложение в чате, вы можете
deleteэти комментированные строки из файла.
Мы просто держим их для справки о том, как присоединиться к каналам и получать сообщения.
Если вы запускаете приложение, попробуйте заполнить поля name и message и нажмите Enter (или нажмите Send ).
Сообщение должно появиться в разных окнах!

С этим мы можем продолжить.
Если бы мы не хотели сохранять историю чата, мы могли бы просто развернуть это приложение немедленно , и мы закончили бы!
На самом деле, это может быть функция « использования »/«», чтобы иметь « эфемерный » чат без истории ... см.: Http://www.psstchat.com/.
Но мы предполагаем , что большинство приложений чата спасают историю, так что
newлюди, присоединяющиеся к «каналу», могли видеть историю, и люди, которые кратко «отсутствуют», могут «наверстать упущенное» в истории.
Запустите следующую команду в вашем терминале:
mix phx.gen.schema Message messages name:string message:stringВы должны увидеть следующий вывод:
* creating lib/chat/message.ex
* creating priv/repo/migrations/20230203114114_create_messages.exs
Remember to update your repository by running migrations:
$ mix ecto.migrateДавайте разберем эту команду для ясности:
mix phx.gen.schema - команда Mix для создания новой схемы (таблица базы данных)Message - единственное имя для записи в наших сообщениях "Коллекция"messages - имя коллекции ( или таблица баз данных )name:string - Имя человека, отправляющего сообщение, хранящееся как string .message:string - сообщение, отправленное человеком, также хранящимся в виде string . Строка creating lib/chat/message.ex создает «схему» для нашей таблицы базы данных сообщений.
Кроме того, создается файл миграции, например: creating priv/repo/migrations/20230203114114_create_messages.exs « миграция » фактически создает таблицу базы данных в нашей базе данных.
В вашем терминале запустите следующую команду для создания таблицы messages :
mix ecto.migrateДля контекста мы рекомендуем читать: hexdocs.pm/ecto_sql/ ecto.migration .html
Вы должны увидеть следующее в своем терминале:
11:42:10.130 [info] == Running 20230203114114 Chat.Repo.Migrations.CreateMessages.change/0 forward
11:42:10.137 [info] create table messages
11:42:10.144 [info] == Migrated 20230203114114 in 0.0s Если вы откроете свой PostgreSQL GUI ( например, PGADMIN ), вы увидите, что таблица сообщений была создана в базе данных chat_dev :

Вы можете просмотреть схему таблицы, щелкнув правой кнопкой мыши »( ctrl + click на Mac ) в таблице messages и выбрав« Свойства »:

Примечание . Для разделах 7, 8 и 9 мы будем разрабатывать, как наш код «обрабатывает» различные события, которые могут произойти в нашем приложении в чате.
Phoenix утеревает большую часть базовой логики, передаваемой сообщениями в процессе общения Elixir (для получения дополнительной информации о том, как процессы Elixir общаются, читают здесь).
В Фениксе события/сообщения, отправляемые из клиента, автоматически направляются на соответствующие функции обработчика на основе имени события, что делает обработку сообщений бесшовным и простым!.
Откройте файл lib/chat_web/channels/room_channel.ex и внутри функции def handle_in("shout", payload, socket) do Добавьте следующую строку:
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert Так что ваша функция в конечном итоге выглядит так:
def handle_in ( "shout" , payload , socket ) do
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert
broadcast socket , "shout" , payload
{ :noreply , socket }
end Если вы заметили ранее, в нашем файле assets/js/app.js мы использовали функцию sendMessage() чтобы доставить наше сообщение на сервер на событии «крик».
Phoenix направляет сообщение в функцию на стороне сервера handle_in("shout", payload, socket) потому что имя события соответствует «крику».
В этой функции мы обрабатываем полезную нагрузку (то есть текст сообщения и любые другие данные) и вставляем ее в нашу базу данных. Аккуратный!
Откройте файл lib/chat/message.ex и импортировать Ecto.Query :
defmodule Chat.Message do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query # add Ecto.Query
Затем добавьте к нему новую функцию:
def get_messages ( limit \ 20 ) do
Chat.Message
|> limit ( ^ limit )
|> order_by ( desc: :inserted_at )
|> Chat.Repo . all ( )
end Эта функция принимает один limit параметра только для возврата фиксированного/максимального количества записей. Он использует all функцию Ecto для извлечения всех записей из базы данных. Message - это имя схемы/таблицы, для которой мы хотим получить записи, а ограничение - это максимальное количество записей для извлечения.
В файле /lib/chat_web/channels/room_channel.ex создать новую функцию:
@ impl true
def handle_info ( :after_join , socket ) do
Chat.Message . get_messages ( )
|> Enum . reverse ( ) # revers to display the latest message at the bottom of the page
|> Enum . each ( fn msg -> push ( socket , "shout" , % {
name: msg . name ,
message: msg . message ,
inserted_at: msg . inserted_at ,
} ) end )
{ :noreply , socket } # :noreply
end и в верхней части обновления файла функция join к следующему:
def join ( "room:lobby" , payload , socket ) do
if authorized? ( payload ) do
send ( self ( ) , :after_join )
{ :ok , socket }
else
{ :error , % { reason: "unauthorized" } }
end
endПримечание . Как и раздел 7, Феникс знает, чтобы вызвать эту функцию, когда сервер отправляет внутреннее сообщение
:after_joinчерез процесс канала.Наша функция
join/3вlib/chat_web/channels/room_channel.exОтправляет это:after_join messageв процесс канала, когда клиент успешно подключается к теме"room:lobby".
Запустите сервер Phoenix ( если он еще не работает ):
mix phx.serverПримечание : для компиляции займет несколько секунд .
В вашем терминале вы должны увидеть:
[info] Running ChatWeb.Endpoint with cowboy 2.8.0 at 0.0.0.0:4000 (http)
[info] Access ChatWeb.Endpoint at http://localhost:4000
webpack is watching the files… Это говорит нам о том, что наш код составил ( как и ожидалось ) и приложение Chat работает на TCP Port 4000 !
Откройте веб -приложение чата в двух отдельных Windows браузера : http: // localhost: 4000
( Если у вашей машины есть только один браузер, попробуйте использовать одну вкладку «Incognito» )
Вы должны быть в состоянии отправлять сообщения между двумя окнами браузера: 
Поздравляю! У вас есть рабочее ( базовое ) приложение чата, написанное в Phoenix!
История чата (сообщения) сохраняется !
Это означает, что вы можете обновить браузер или присоединиться к другому браузеру, и вы все равно увидите историю!
Автоматизированное тестирование является одним из лучших способов обеспечения надежности в ваших веб -приложениях.
ПРИМЕЧАНИЕ . Если вы совершенно новичок в автоматическом тестировании или «разработке испытаний» («TDD»), мы рекомендуем прочитать/следовать «Основному» руководству: github.com/dwyl/ Учитесь
Тестирование в Фениксе быстро ( тесты проводятся параллельно! ) И легко начать! Структура тестирования ExUnit встроена, поэтому нет «решений/дебатов» о том, какие структуры или стиль использовать.
Если вы никогда не видели и не писали тест с ExUnit , не бойтесь, синтаксис должен быть знаком , если вы написали какой -либо автоматический тест в прошлом.
Всякий раз, когда вы создаете новое приложение Phoenix или добавляете новую функцию ( например, канал ), Phoenix генерирует для вас новый тест.
Мы запускаем тесты, используя команду mix test :
... ... ..
Finished in 0.1 seconds ( 0.05 s async , 0.06 s sync )
8 tests , 0 failures
Randomized with seed 157426В этом случае ни один из этих тестов не проходит. ( 8 тестов, 0 сбой )
Стоит занять время ( или до тех пор, пока вам нужно !), Чтобы понять, что происходит в файле /room_channel_test.exs . Откройте его, если у вас еще нет, прочитайте описания тестов и код.
Для немного контекста мы рекомендуем читать: https://hexdocs.pm/phoenix/ testing_channels .html
Давайте посмотрим на первый тест в /тест/chat_web/channels/room_channel_test.exs:
test "ping replies with status ok" , % { socket: socket } do
ref = push socket , "ping" , % { "hello" => "there" }
assert_reply ref , :ok , % { "hello" => "there" }
end Тест получает socket из функции setup ( в строке 6 файла ) и присваивает результат вызова функции push к переменной ref push просто толкает сообщение ( карта %{"hello" => "there"} ) на socket к теме "ping" .
Пункт функции handle_in , который обрабатывает тему "ping" :
def handle_in ( "ping" , payload , socket ) do
{ :reply , { :ok , payload } , socket }
end Просто отвечает на полезную нагрузку, которую вы отправили, поэтому в нашем тесте мы можем использовать макрос assert_reply , чтобы утверждать, что ref равна :ok, %{"hello" => "there"}
Примечание . Если у вас есть вопросы или вам нужна помощь в понимании других тестов, откройте проблему на GitHub, мы рады расширить это дальше!
( Мы просто стараемся сохранить этот учебник разумно «кратким», чтобы новички не «ошеломлены» чем -либо ...)
Часто мы можем многое узнать о приложении ( или API ) от прочтения тестов и наблюдения, где находятся «пробелы» в тестировании.
К счастью, мы можем достичь этого только с парой шагов:
excoveralls в качестве (развития) зависимости для mix.exs Откройте файл mix.exs и найдите функцию «DEPS»:
defp deps do
Добавьте запятую в конце последней строки, затем добавьте следующую строку в конце списка:
{ :excoveralls , "~> 0.15.2" , only: [ :test , :dev ] } # tracking test coverage Кроме того, найдите раздел def project do ( к вершине mix.exs ) и добавьте следующие строки в список:
test_coverage : [ tool : E xCoveralls ] ,
preferred_cli_env: [
coveralls : :test ,
"coveralls.detail": :test ,
"coveralls.post": :test ,
"coveralls.html": :test
] Затем установите зависимость от excoveralls мы только что добавили в mix.exs :
mix deps.getВы должны увидеть:
Resolving Hex dependencies...
Dependency resolution completed:
* Getting excoveralls (Hex package)
... etc.coveralls.json В «root» ( базовый каталог ) проекта чата создайте новый файл с названием coveralls.json и копировать вставку следующее:
{
"coverage_options" : {
"minimum_coverage" : 100
},
"skip_files" : [
" test/ " ,
" lib/chat/application.ex " ,
" lib/chat_web.ex " ,
" lib/chat_web/telemetry.ex " ,
" lib/chat_web/components/core_components.ex " ,
" lib/chat_web/channels/user_socket.ex "
]
}
Этот файл довольно простой, он инструктирует приложение coveralls потребовать minimum_coverage 100% ( то есть все проверено 1 ) и игнорировать файлы в test/ каталоге для проверки покрытия. Мы также игнорируем такие файлы, как application.ex , telemetry.ex , core_components.ex и user_socket.ex поскольку они не имеют отношения к функциональности нашего проекта.
1 Мы считаем, что инвестирование немного времени на время для написания тестов для всего нашего кода стоит того , чтобы позже было меньше ошибок .
Ошибки дороги , тесты дешевы , а уверенность / надежность бесценна .
Чтобы запустить тесты с покрытием, копируйте вставку следующую команду в ваш терминал:
MIX_ENV = test mix do coveralls . json
Для использования Windows:
$ env :MIX_ENV = "test" ; mix do coveralls . json
Вы должны увидеть:
Randomized with seed 527109
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
70.0% lib/chat_web/channels/room_channel.ex 46 10 3
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
66.7% lib/chat_web/router.ex 27 3 1
[TOTAL] 80.0%
----------------
Как мы можем здесь, только 80% строк кода в /lib «покрываются» тестами, которые мы написали.
Для просмотра освещения в веб -браузере запустите следующее:
MIX_ENV = test mix coveralls . html ; open cover / excoveralls . html Это откроет отчет о покрытии (HTML) в вашем веб -браузере по умолчанию:

Откройте файл test/chat_web/channels/room_channel_test.exs и добавьте следующий тест:
test ":after_join sends all existing messages" , % { socket: socket } do
# insert a new message to send in the :after_join
payload = % { name: "Alex" , message: "test" }
Chat.Message . changeset ( % Chat.Message { } , payload ) |> Chat.Repo . insert ( )
{ :ok , _ , socket2 } = ChatWeb.UserSocket
|> socket ( "person_id" , % { some: :assign } )
|> subscribe_and_join ( ChatWeb.RoomChannel , "room:lobby" )
assert socket2 . join_ref != socket . join_ref
end Наконец, внутри lib/chat_web/router.ex прокомментируйте следующий кусок кода.
pipeline :api do
plug :accepts , [ "json" ]
end Поскольку мы не используем это :api в этом проекте, нет необходимости проверять его.
Теперь, когда вы запускаете MIX_ENV=test mix do coveralls.json вы должны увидеть:
Randomized with seed 15920
----------------
COV FILE LINES RELEVANT MISSED
100.0% lib/chat.ex 9 0 0
100.0% lib/chat/message.ex 26 4 0
100.0% lib/chat/repo.ex 5 0 0
100.0% lib/chat_web/channels/room_channel.ex 46 10 0
100.0% lib/chat_web/components/layouts.ex 5 0 0
100.0% lib/chat_web/controllers/error_html.ex 19 1 0
100.0% lib/chat_web/controllers/error_json.ex 15 1 0
100.0% lib/chat_web/controllers/page_controller 9 1 0
100.0% lib/chat_web/controllers/page_html.ex 5 0 0
100.0% lib/chat_web/endpoint.ex 49 0 0
100.0% lib/chat_web/router.ex 27 2 0
[TOTAL] 100.0%
----------------
Этот тест просто создает сообщение перед subscribe_and_join , поэтому в базе данных есть сообщение для отправки любому клиену, который присоединяется к чату.
Таким образом :after_join имеет по крайней мере одно сообщение, и Enum.each будет вызвано хотя бы один раз.
С этим наше приложение полностью протестировано!
Мы можем расширить этот проект для поддержки базовой аутентификации. Если вы хотите понять, как реализуется аутентификация простым/быстрым способом, см. Auth.md
Presence , чтобы отслеживать, кто онлайн Одним из отличных преимуществ использования Phoenix является то, что вы можете легко отслеживать процессы и каналы.
Это прокладывает путь к легкому показу, кто онлайн или нет!
Если вы заинтересованы в разработке этой функции, мы создали руководство в presence.md только для вас! ?
Непрерывная интеграция позволяет автоматизировать запуск тестов, чтобы проверить/подтвердить, что ваше приложение работает, как и ожидалось ( до развертывания ). Это предотвращает случайно « нарушать » ваше приложение.
К счастью, шаги довольно просты.
Для примера ci.yml см.
.github/workflows/ci.yml
Развертывание на Fly.io занимает пару минут, мы рекомендуем следовать официальному руководству: fly.io/docs/elixir/
После развертывания вы сможете просмотреть/использовать ваше приложение в любом веб -браузере.
Например: Phoenix-Chat .fly.dev/

Если вы нашли этот пример полезным, пожалуйста, ️ репозиторий GitHub, чтобы мы ( и другие ) знали, что вам понравилось!
Если вы хотите узнать больше Phoenix и Magic of LiveView , подумайте о прочтении учебника нашего начинающего: github.com/dwyl/ phoenix-liveview-counter-tutorial
Для версии приложения чата с помощью LiveView вы можете прочитать следующее репозиторий: github.com/dwyl/ phoenix-liveview-chat-example
Спасибо, что учились с нами! ☀
Этот репо вдохновлен @chrismccord простой пример чата: https://github.com/christriscccord/phoenix_chat_example ❤
На момент написания примера Криса в последний раз обновлялся 20 февраля 2018 года и использует Phoenix 1.3 См.: Проблемы/40.
Существует довольно много различий (нарушение изменений) между Фениксом 1.3 и 1.6 ( последняя версия ).
В нашем уроке используется Phoenix 1.6.2 (последний по состоянию на октябрь 2021 г.). Мы надеемся, что, написав ( и поддержав ) пошаговый учебник для начинающих, который мы вносите вклад в сообщество Elixir/Phoenix, не накапливая PRS на репо Крисе.