Строитель сообщений HL7 и модульные тесты


Мне нужно было создать проект из-за выпускных экзаменов в апреле. Это приложение, которое имитирует больницу. Есть прием, переводы и выписки пациентов в этой больнице. Для каждого из них есть сообщения стандарта HL7 (и в процессе отправки на сервер). Что такое HL7 версии?

По этой причине, я реализовал HL7BuilderImpl класс с HL7Builder интерфейс. Этим разработчиком является частью моего экзамена, и я должен показать его в ревизионную комиссию.

В HL7Builder получает пациент (с данные, такие как дата рождения, пол, имя и прочее), типа (это перечислимый класс, в котором содержится приеме, переводе и увольнении) и формат (труба или XML, но мне достаточно было реализовать Pipe опция), она возвращает строку. Это, как одна из моих сообщений HL7, для приема пациентов будет выглядеть так:

MSH|^~\&|KIS|Testsender|HIS|Testreceiver|201802281055||ADT^A01^ADT_A01|62|T|2.4
EVN|A01|201802281055
PID|||HL7SIM000000041||Doe^John||19780320|M
PV1||I|KAR-2^^^KAR||||||||||||||||HL7SIM000000041C1||||||||||||||||||||KAR|||||201802281055

Я использовал рамки Хапи для сопоставления данных пациентов к полям стандарт HL7-сообщение. Наконец, переданный разбирается в строку и возвращается отправителю (который не является частью меня экзамены, поэтому я не буду спрашивать за рецензирование здесь).

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

  • Что вы думаете о структуре? Я пытался заказать методы от общих к специальным, я тоже пыталась сколько угодно и как можно меньше отдельно.
  • Во-первых, я реализовал 2 метода, которые содержат только некоторые чеки, а затем делегировать дополнительные методы. Из-за моего опыта, это хороший способ получить код на практике?
  • На данный момент, я не знаю много о безопасности с моим кодом. Есть некоторые точки, которые могут быть проблемы с безопасностью с моим код?
  • Я не имею большой опыт работы с юнит-тестов, я написал код и получил охват более 80% моего класса, но я не уверен, если мои тесты вроде "слишком легко"?
  • Я пропустила что-то проверить?

Я был бы очень благодарен за некоторые советы, рекомендации и предложения по улучшению работы с моим кодом. Вот класс HL7BuilderImpl:

package com.hl7sim.hl7;


import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.hl7sim.patient.Patient;
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.AbstractMessage;
import ca.uhn.hl7v2.model.DataTypeException;
import ca.uhn.hl7v2.model.v24.message.ADT_A01;
import ca.uhn.hl7v2.model.v24.message.ADT_A02;
import ca.uhn.hl7v2.model.v24.message.ADT_A03;
import ca.uhn.hl7v2.model.v24.segment.EVN;
import ca.uhn.hl7v2.model.v24.segment.MSH;
import ca.uhn.hl7v2.model.v24.segment.PID;
import ca.uhn.hl7v2.model.v24.segment.PV1;
import ca.uhn.hl7v2.parser.Parser;


public class HL7BuilderImpl implements HL7Builder {


private static AtomicInteger messageControlId; 
private List<String> allHL7s;


public HL7BuilderImpl() {
    HL7BuilderImpl.messageControlId = new AtomicInteger(0);
    this.allHL7s = new ArrayList<String>();
}


public static int getMessageControlId() { 
    return messageControlId.get(); 
}

public static void setMessageControlId(AtomicInteger messageControlId) {
    HL7BuilderImpl.messageControlId = messageControlId;
}

@Override
public List<String> getAllHL7s() {
    return allHL7s;
}

@Override
public void setAllHL7s(List<String> allHL7s) {
    this.allHL7s = allHL7s;
}


@Override
public String createMessage(Patient patient, Type type, Format format) {
    if (patient == null) {
        throw new IllegalArgumentException("No Patient");
    }
    if (type == null) {
        throw new IllegalArgumentException("No Messagetype");
    }
    if (format == null) {
        throw new IllegalArgumentException("Argument 'format' must not be null");
    }
    String message = "";
    if (format == Format.PIPE) {
        message = createPipeMessage(patient, type);
    } else if (format == Format.XML) {

    } else {
        throw new IllegalArgumentException("Format '" + format.name() + "' is not supported.");
    }
    return message;
}

@Override
public String createPipeMessage(Patient patient, Type type) {
    if(type == null) {
        throw new IllegalArgumentException("Type may not be null");
    }
    if(patient == null) {
        throw new IllegalArgumentException("No Patients");
    }
    switch (type) {
    case ADMISSION:
        return createPipeMessageAdmission(patient, type);
    case DISCHARGE:
        return createPipeMessageDischarge(patient, type);
    case TRANSFER:
        return createPipeMessageTransfer(patient, type);
    default:
        throw new IllegalArgumentException("Unknown Type, Builder not working with this Type");
    }
}

@Override
public String createPipeMessageAdmission(Patient patient, Type type) {
    ADT_A01 adt = new ADT_A01();
    patient.setAdmissionDateTime(LocalDateTime.now());
    try {
        adt.initQuickstart("ADT", "A01", "T");
        setMshSegment(adt.getMSH(), patient);
        setPidSegment(adt.getPID(), patient);
        setEvnSegment(adt.getEVN(), type);
        setPv1Segment(adt.getPV1(), patient);
        return parseMessage(adt);
    } catch (IllegalArgumentException e) {
        throw new IllegalArgumentException("Illegal Argument Exception", e);
    } catch (DataTypeException e) {
        throw new HL7BuilderException("Data type exception", e);
    } catch (HL7Exception e) {
        throw new HL7BuilderException("HL7 exception", e);
    } catch (IOException e) {
        throw new HL7BuilderException("IO exception", e);
    }
}

@Override
public String createPipeMessageTransfer(Patient patient, Type type) {
    ADT_A02 adt = new ADT_A02();
    try {
        adt.initQuickstart("ADT", "A02", "T");
        setMshSegment(adt.getMSH(), patient);
        setPidSegment(adt.getPID(), patient);
        setEvnSegment(adt.getEVN(), type);
        setPv1Segment(adt.getPV1(), patient);
        setPriorLocation(adt, patient);
        return parseMessage(adt);
    } catch (DataTypeException e) {
        throw new HL7BuilderException("Data Type Exception", e);
    } catch (HL7Exception e) {
        throw new HL7BuilderException("HL7 Exception", e);
    } catch (IOException e) {
        throw new HL7BuilderException("I/O Exception", e);
    }
}

@Override
public String createPipeMessageDischarge(Patient patient, Type type) {
    ADT_A03 adt = new ADT_A03();
    patient.setDischargeDateTime(LocalDateTime.now());
    try {
        adt.initQuickstart("ADT", "A03", "T");
        setMshSegment(adt.getMSH(), patient);
        setPidSegment(adt.getPID(), patient);
        setEvnSegment(adt.getEVN(), type);
        setPv1Segment(adt.getPV1(), patient);
        return parseMessage(adt);
    } catch (DataTypeException e) {
        throw new HL7BuilderException("Data Type Exception", e);
    } catch (HL7Exception e) {
        throw new HL7BuilderException("HL7 Exception", e);
    } catch (IOException e) {
        throw new HL7BuilderException("I/O Exception", e);
    }
}

private void setMshSegment(MSH msh, Patient patient) throws DataTypeException {
    LocalDateTime now = LocalDateTime.now();
    msh.getSendingFacility().getNamespaceID().setValue("Testsender");
    msh.getSendingApplication().getNamespaceID().setValue("KIS");
    msh.getReceivingFacility().getNamespaceID().setValue("Testreceiver");
    msh.getReceivingApplication().getNamespaceID().setValue("HIS");
    msh.getDateTimeOfMessage().getTimeOfAnEvent().setDateMinutePrecision(now.getYear(), now.getMonthValue(),
            now.getDayOfMonth(), now.getHour(), now.getMinute());
    msh.getMessageControlID().setValue(String.valueOf(messageControlId.incrementAndGet()));
}

private void setEvnSegment(EVN evn, Type type) throws DataTypeException {
    LocalDateTime now = LocalDateTime.now();
    evn.getRecordedDateTime().getTimeOfAnEvent().setDateMinutePrecision(now.getYear(), now.getMonthValue(),
            now.getDayOfMonth(), now.getHour(), now.getMinute());
    if (type == Type.DISCHARGE) {
        evn.getEventTypeCode().setValue("A03");
    } else if (type == Type.TRANSFER) {
        evn.getEventTypeCode().setValue("A02");
    } else if (type == Type.ADMISSION) {
        evn.getEventTypeCode().setValue("A01");
    }
}

private void setPidSegment(PID pid, Patient patient) throws DataTypeException {
    pid.getPatientIdentifierList(0).getCx1_ID().setValue(String.valueOf(patient.getId()));
    pid.getAdministrativeSex().setValue(patient.getGender());
    pid.getPatientName(0).getGivenName().setValue(patient.getFirstname());
    pid.getPatientName(0).getFamilyName().getSurname().setValue(patient.getLastname());
    pid.getDateTimeOfBirth().getTimeOfAnEvent().setDatePrecision(patient.getBirthday().getYear(),
            patient.getBirthday().getMonthValue(), patient.getBirthday().getDayOfMonth());
}

private void setPv1Segment(PV1 pv1, Patient patient) throws DataTypeException {
    setPV1Time(pv1, patient);
    setPV1Location(pv1, patient);
}

private void setPV1Time(PV1 pv1, Patient patient) throws DataTypeException {
    if (patient.getAdmissionDateTime() != null) {
    pv1.getAdmitDateTime().getTimeOfAnEvent().setDateMinutePrecision(patient.getAdmissionDateTime().getYear(),
            patient.getAdmissionDateTime().getMonthValue(), patient.getAdmissionDateTime().getDayOfMonth(),
            patient.getAdmissionDateTime().getHour(), patient.getAdmissionDateTime().getMinute());
    }
    if (patient.getDischargeDateTime() != null) {
        pv1.getDischargeDateTime(0).getTimeOfAnEvent().setDateMinutePrecision(patient.getDischargeDateTime().getYear(),
                patient.getDischargeDateTime().getMonthValue(), patient.getDischargeDateTime().getDayOfMonth(),
                patient.getDischargeDateTime().getHour(), patient.getDischargeDateTime().getMinute());
    }

}

private void setPV1Location(PV1 pv1, Patient patient) throws DataTypeException {
    pv1.getServicingFacility().setValue(patient.getDepartment());
    pv1.getAssignedPatientLocation().getPl1_PointOfCare().setValue(patient.getWard());
    pv1.getAssignedPatientLocation().getPl4_Facility().getNamespaceID().setValue(patient.getDepartment());
    pv1.getPatientClass().setValue(patient.getStatus());
    pv1.getPv119_VisitNumber().getCx1_ID().setValue(String.valueOf(patient.getInstance()));
}

private void setPriorLocation(ADT_A02 adt, Patient patient) throws DataTypeException {
    adt.getPV1().getPriorPatientLocation().getPl1_PointOfCare().setValue(patient.getPriorWard());
    adt.getPV1().getPriorPatientLocation().getPl4_Facility().getNamespaceID()
            .setValue(patient.getPriorDepartment());
}

private String parseMessage(AbstractMessage message) throws HL7Exception {
    try (HapiContext context = new DefaultHapiContext()) {
        context.getExecutorService();
        Parser parser = context.getPipeParser();
        final String result = parser.encode(message);
        allHL7s.add(result);
        return result;
    } catch (IOException e) {
        throw new HL7BuilderException("Error on parsing message.", e);
    }
} 


}

И здесь принадлежность тестов JUnit:

package com.hl7sim;


import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import com.hl7sim.hl7.Format;
import com.hl7sim.hl7.HL7Builder;
import com.hl7sim.hl7.HL7BuilderImpl;
import com.hl7sim.hl7.Type;
import com.hl7sim.patient.Patient;


public class HL7BuilderImplTest {


HL7Builder testHl7builder;
List<String> testAllHl7s;
Patient testPatient;
Patient testPatientTwo;
List<Patient> testBothPatients;


@Before
public void setUp() throws Exception {

    testHl7builder = new HL7BuilderImpl();

    testPatient = new Patient.Builder().build();
    testPatient.setBirthday(LocalDate.of(1911, 11, 11));
    testPatient.setFirstname("Test");
    testPatient.setLastname("Mann");
    testPatient.setGender("M");
    testPatient.setAdmissionDateTime(LocalDateTime.now());
    testPatient.setDischargeDateTime(LocalDateTime.now());

    testPatientTwo = new Patient.Builder().build();
    testPatientTwo = testPatient;

    testBothPatients = new ArrayList<Patient>();

    testAllHl7s = new ArrayList<String>();

    testBothPatients.add(testPatient);
    testBothPatients.add(testPatientTwo);
}


@Test
public void testGetMessageControlId() {

    int result = HL7BuilderImpl.getMessageControlId();

    assertTrue(result == 0);
}

@Test
public void testAllHl7sAtBegin() {

    int result = testAllHl7s.size();

    assertTrue(result == 0);
}

@Test
public void testAddingOneHl7ToAllHl7s() {

    String test = "Test";
    testAllHl7s.add(test);

    int result = testAllHl7s.size();

    assertTrue(result == 1);
}

@Test(expected = IllegalArgumentException.class)
public void testCreateMessageWithNullValuesForOnePatient() {

    testHl7builder.createMessage((Patient)null, null, null);
}

@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithoutType() {

    testHl7builder.createMessage(testPatient, null, Format.PIPE);
}

@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithoutFormat() {

    testHl7builder.createMessage(testPatient, Type.ADMISSION, null);
}

@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithWrongFormat() {

    testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.UNKNOWN);
}

@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithWrongType() {

    testHl7builder.createMessage(testPatient, Type.UNKNOWN, Format.PIPE);
}

@Test(expected = IllegalArgumentException.class)
public void testCreatePipeMessageOnePatientNoType() {

    testHl7builder.createPipeMessage(testPatient, null);
}

@Test(expected = IllegalArgumentException.class)
public void testCreatePipeMessageWithoutOnePatient() {

    testPatient = null;

    testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);
}

@Test
public void testCreatePipeMessageOnePatientWrongType() {

    testHl7builder.createPipeMessage(testPatient, Type.TRANSFER);
}

@Test
public void testPatientSetFirstNameName() {
    // Given
    testPatient.setFirstname("Albert");
    // When
    String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);
    // Then
    assertTrue(result.contains("^Albert|"));        
}

@Test
public void testPatientSetLastName() {

    testPatient.setLastname("Meier");

    String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

    assertTrue(result.contains("|Meier^"));     
}

@Test
public void testcreatePipeMessageAdmission() {

    String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

    assertTrue(result.contains("|ADT^A01^ADT_A01"));
}

@Test 
public void testcreatePipeMessageDischarge() {

    String result = testHl7builder.createMessage(testPatient, Type.DISCHARGE, Format.PIPE);

    assertTrue(result.contains("|ADT^A03^ADT_A03"));
}

@Test
public void testWithIncompletePatientData() {

    testPatient.setLastname("");

    String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);

    System.out.println(result);
}


}


214
3
задан 28 февраля 2018 в 10:02 Источник Поделиться
Комментарии
1 ответ

Добро пожаловать в код комментарий и спасибо за хороший вопрос!

Структура

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

public String createPipeMessage(Patient patient, Type type);
public String createPipeMessageAdmission(Patient patient, Type type);
public String createPipeMessageTransfer(Patient patient, Type type);
public String createPipeMessageDischarge(Patient patient, Type type);

все же роль: создание сообщения.

Поскольку HL7Builder интерфейс в том же пакете, в реализации, я полагаю, что этот интерфейс не приходят от сторонних API и это то, что вы можете определить и изменить.

Я предлагаю определять интерфейс, посвященное строительству трубы сообщение:

public interface PipeMessageBuilder {
public String createPipeMessage(Patient patient, Type type);
}

И там должна быть реализация для каждого типа сообщения:

public class AdmissionMessageBuilder implements PipeMessageBuilder {

@Override
public String createPipeMessage(Patient patient, Type type) {
// contents of current createPipeMessageAdmission() impl
}
}

// and so on for other message types

HL7Builder следует предусмотреть метод, который возвращает трубку сообщение застройщика в зависимости от формата, например:

public class HL7Builder {

public PipeMessageBuilder getBuilderFor(Format format) {
// logic of bulder choice
}

}

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

При таком подходе, создании сообщения будет сокращен до этой цепочки:

String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);

Это дает важные преимущества: 1) гибкость и разделение ответственности; 2) испытания гораздо легче.

Частные методы set*Segmentиспользуется всеми строителями, должны быть сгруппированы в абстрактный класс и быть доступными от реализации, поэтому строители классе заголовки должны, наконец, быть, как public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment должны быть абстрактными и реализуется для каждого конкретного сообщения застройщика, БЕЗ if условия.

Другие Вопросы

Проверка Аргументов

Многократные проверки могут быть извлечены:

private static void ensureNotNull(String value, String argName) {
if (value == null) {
throw new IllegalArgumentException(argName + " must not be null");
}
}

Это позволит существенно сократить код проверки, например:

ensureNotNull(patient, "patient");
ensureNotNull(type, "type");
ensureNotNull(format, "format");

Обработка Исключений

Существует большое загрязнение ненужной информацией или слоев:

catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Illegal Argument Exception", e);
} catch (DataTypeException e) {
throw new HL7BuilderException("Data type exception", e);
} catch (HL7Exception e) {
throw new HL7BuilderException("HL7 exception", e);
} catch (IOException e) {
throw new HL7BuilderException("IO exception", e);
}

IAE не должен быть пойман, потому что он обернут в еще один IAE. Он полезен?

Сообщения в HL7BuilderException не информативно вообще. Тип обернутое исключение известен в любом случае, так что сообщения должны быть изменены или удалены, просто throw new HL7BuilderException(e).

Закон Деметры

Несколько вызовов, как

msh.getSendingApplication().getNamespaceID().setValue("KIS");

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

Тесты JUnit

Тестовый случай класса должны быть в одном пакете, чем тестируемого лица.

Бардак там testPatient* инициализации:

testPatientTwo = new Patient.Builder().build();
testPatientTwo = testPatient;

Я тоже предлагаю убрать Patient поля на всех из тестового класса и создание экземпляров Patient только в те методы, которые нужно использовать этот объект.

Звонки, как assertTrue(result == 0) следует избегать, потому что им не хватает ясности, когда есть тест неудачи. Он более выразителен, когда переформулирована как assertEquals(0, result)или даже с объяснением/данные сообщения.

Если нет никакого способа, чтобы избежать assertTrueкак в assertTrue(result.contains("|ADT^A01^ADT_A01"))должно быть объяснение, сообщение, например:

String expected = "|ADT^A01^ADT_A01";
assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));

В противном случае, провал теста выдаст сообщение "ложная неправда".

И конечно, если вы выберете для извлечения строители сообщения в отдельных классах, реализующих, там должны быть отдельные тестовые классы, соответствующие каждому строителю.

2
ответ дан 28 февраля 2018 в 09:02 Источник Поделиться